AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
BuildDeployPortal.cs
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License. See LICENSE in the project root for license information.
3 
4 using System;
5 using System.Diagnostics;
6 using System.IO;
7 using System.Linq;
8 using System.Text;
9 using System.Threading;
10 using UnityEditor;
11 using UnityEngine;
12 using UnityEngine.Networking;
13 using Debug = UnityEngine.Debug;
14 
15 namespace HoloToolkit.Unity
16 {
20  public static class BuildDeployPortal
21  {
22  private enum AppInstallStatus
23  {
24  Invalid,
25  Installing,
26  InstallSuccess,
27  InstallFail
28  }
29 
30  private const float TimeOut = 10.0f;
31  private const float MaxWaitTime = 20.0f;
32 
33  // Device Portal API Resources
34  // https://docs.microsoft.com/en-us/windows/uwp/debug-test-perf/device-portal-api-hololens#holographic-os
35  // https://docs.microsoft.com/en-us/windows/uwp/debug-test-perf/device-portal-api-core
36  private static readonly string API_GetMachineNameQuery = @"{0}/api/os/machinename";
37  private static readonly string API_ProcessQuery = @"{0}/api/resourcemanager/processes";
38  private static readonly string API_PackagesQuery = @"{0}/api/appx/packagemanager/packages";
39  private static readonly string API_InstallQuery = @"{0}/api/app/packagemanager/package";
40  private static readonly string API_InstallStatusQuery = @"{0}/api/app/packagemanager/state";
41  private static readonly string API_AppQuery = @"{0}/api/taskmanager/app";
42  private static readonly string API_FileQuery = @"{0}/api/filesystem/apps/file?knownfolderid=LocalAppData&filename=UnityPlayer.log&packagefullname={1}&path=%5C%5CTempState";
43  private static readonly string API_IpConfigQuery = @"{0}/api/networking/ipconfig";
44 
53  private static string GetBasicAuthHeader(ConnectInfo connectionInfo, bool isGetRequest = false)
54  {
55  var auth = string.Format("{0}{1}:{2}", BuildDeployPrefs.UseSSL && !isGetRequest ? "auto-" : "", connectionInfo.User, connectionInfo.Password);
56  auth = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(auth));
57  return string.Format("Basic {0}", auth);
58  }
59 
67  private static string WebRequestGet(string query, string auth, bool showProgressDialog = true)
68  {
69  try
70  {
71  using (var webRequest = UnityWebRequest.Get(query))
72  {
73  webRequest.SetRequestHeader("Authorization", auth);
74 #if UNITY_2017_1_OR_NEWER
75  webRequest.timeout = (int)TimeOut;
76 #endif
77 
78 #if UNITY_2017_2_OR_NEWER
79  webRequest.SendWebRequest();
80 #else
81  webRequest.Send();
82 #endif
83 
84  while (!webRequest.isDone)
85  {
86  if (webRequest.downloadProgress > -1 && showProgressDialog)
87  {
88  EditorUtility.DisplayProgressBar("Connecting to Device Portal",
89  "Progress...", webRequest.downloadProgress);
90  }
91  }
92 
93  if (showProgressDialog)
94  {
95  EditorUtility.ClearProgressBar();
96  }
97 
98  if (
99 #if UNITY_2017_1_OR_NEWER
100  webRequest.isNetworkError || webRequest.isHttpError &&
101 #else
102  webRequest.isError &&
103 #endif // UNITY_2017_1_OR_NEWER
104  webRequest.responseCode != 401)
105  {
106  string response = string.Empty;
107  var responseHeaders = webRequest.GetResponseHeaders();
108  if (responseHeaders != null)
109  {
110  response = responseHeaders.Aggregate(string.Empty, (current, header) => string.Format("{0}{1}: {2}\n", current, header.Key, header.Value));
111  }
112 
113  Debug.LogErrorFormat("Network Error: {0}\n{1}", webRequest.error, response);
114  return string.Empty;
115  }
116 
117  switch (webRequest.responseCode)
118  {
119  case 200:
120  case 204:
121  return webRequest.downloadHandler.text;
122  case 401:
123  Debug.LogError("Unauthorized: Access is denied due to invalid credentials.");
124  break;
125  default:
126  Debug.LogError(webRequest.responseCode);
127  break;
128  }
129  }
130  }
131  catch (Exception e)
132  {
133  Debug.LogException(e);
134  }
135 
136  return string.Empty;
137  }
138 
147  private static string WebRequestPost(string query, WWWForm postData, string auth, bool showDialog = true)
148  {
149  try
150  {
151  using (var webRequest = UnityWebRequest.Post(query, postData))
152  {
153  webRequest.SetRequestHeader("Authorization", auth);
154 #if UNITY_2017_1_OR_NEWER
155  webRequest.timeout = (int)TimeOut;
156 #endif
157 
158  // HACK: Workaround for extra quotes around boundary.
159  string contentType = webRequest.GetRequestHeader("Content-Type");
160  if (contentType != null)
161  {
162  contentType = contentType.Replace("\"", "");
163  webRequest.SetRequestHeader("Content-Type", contentType);
164  }
165 
166 #if UNITY_2017_2_OR_NEWER
167  webRequest.SendWebRequest();
168 #else
169  webRequest.Send();
170 #endif
171 
172  while (!webRequest.isDone)
173  {
174  if (webRequest.uploadProgress > -1 && showDialog)
175  {
176  EditorUtility.DisplayProgressBar("Connecting to Device Portal",
177  "Uploading...", webRequest.uploadProgress);
178  }
179  else if (webRequest.downloadProgress > -1 && showDialog)
180  {
181  EditorUtility.DisplayProgressBar("Connecting to Device Portal",
182  "Progress...", webRequest.downloadProgress);
183  }
184  }
185 
186  EditorUtility.ClearProgressBar();
187 
188  if (
189 #if UNITY_2017_1_OR_NEWER
190  webRequest.isNetworkError || webRequest.isHttpError &&
191 #else
192  webRequest.isError &&
193 #endif // UNITY_2017_1_OR_NEWER
194  webRequest.responseCode != 401)
195  {
196  string response = string.Empty;
197  var responseHeaders = webRequest.GetResponseHeaders();
198  if (responseHeaders != null)
199  {
200  response = responseHeaders.Aggregate(string.Empty, (current, header) => string.Format("{0}{1}: {2}\n", current, header.Key, header.Value));
201  }
202 
203  Debug.LogErrorFormat("Network Error: {0}\n{1}", webRequest.error, response);
204  return string.Empty;
205  }
206 
207  switch (webRequest.responseCode)
208  {
209  case 200:
210  case 202:
211  return webRequest.downloadHandler.text;
212  case 401:
213  Debug.LogError("Unauthorized: Access is denied due to invalid credentials.");
214  break;
215  default:
216  Debug.LogError(webRequest.responseCode);
217  break;
218  }
219  }
220  }
221  catch (Exception e)
222  {
223  Debug.LogException(e);
224  }
225 
226  return string.Empty;
227  }
228 
236  private static bool WebRequestDelete(string query, string auth, bool showDialog = true)
237  {
238  try
239  {
240  using (var webRequest = UnityWebRequest.Delete(query))
241  {
242  webRequest.SetRequestHeader("Authorization", auth);
243 #if UNITY_2017_1_OR_NEWER
244  webRequest.timeout = (int)TimeOut;
245 #endif
246 
247 #if UNITY_2017_2_OR_NEWER
248  webRequest.SendWebRequest();
249 #else
250  webRequest.Send();
251 #endif
252 
253  while (!webRequest.isDone)
254  {
255  if (showDialog && webRequest.downloadProgress > -1)
256  {
257  EditorUtility.DisplayProgressBar("Connecting to Device Portal",
258  "Progress...", webRequest.downloadProgress);
259  }
260  }
261 
262  EditorUtility.ClearProgressBar();
263 
264  if (
265 #if UNITY_2017_1_OR_NEWER
266  webRequest.isNetworkError || webRequest.isHttpError &&
267 #else
268  webRequest.isError &&
269 #endif // UNITY_2017_1_OR_NEWER
270  webRequest.responseCode != 401)
271  {
272  string response = string.Empty;
273  var responseHeaders = webRequest.GetResponseHeaders();
274  if (responseHeaders != null)
275  {
276  response = responseHeaders.Aggregate(string.Empty, (current, header) => string.Format("{0}{1}: {2}\n", current, header.Key, header.Value));
277  }
278 
279  Debug.LogErrorFormat("Network Error: {0}\n{1}", webRequest.error, response);
280  return false;
281  }
282 
283  switch (webRequest.responseCode)
284  {
285  case 200:
286  return true;
287  case 401:
288  Debug.LogError("Unauthorized: Access is denied due to invalid credentials.");
289  break;
290  default:
291  Debug.LogError(webRequest.responseCode);
292  break;
293  }
294  }
295  }
296  catch (Exception e)
297  {
298  Debug.LogException(e);
299  }
300 
301  return false;
302  }
303 
308  public static void OpenWebPortal(ConnectInfo targetDevice)
309  {
310  //TODO: Figure out how to pass username and password to browser?
311  Process.Start(FinalizeUrl(targetDevice.IP));
312  }
313 
319  public static MachineName GetMachineName(ConnectInfo targetDevice)
320  {
321  MachineName machineName = null;
322  string query = string.Format(API_GetMachineNameQuery, FinalizeUrl(targetDevice.IP));
323  string response = WebRequestGet(query, GetBasicAuthHeader(targetDevice, true), false);
324 
325  if (!string.IsNullOrEmpty(response))
326  {
327  machineName = JsonUtility.FromJson<MachineName>(response);
328  }
329 
330  return machineName;
331  }
332 
333  [Obsolete("Use IsAppInstalled(string packageFamilyName, ConnectInfo targetDevice)")]
334  public static bool IsAppInstalled(string packageFamilyName, string targetIp)
335  {
336  return QueryAppDetails(packageFamilyName, new ConnectInfo(targetIp, BuildDeployPrefs.DeviceUser, BuildDeployPrefs.DevicePassword)) != null;
337  }
338 
345  public static bool IsAppInstalled(string packageFamilyName, ConnectInfo targetDevice)
346  {
347  return QueryAppDetails(packageFamilyName, targetDevice) != null;
348  }
349 
350  [Obsolete("IsAppRunning(string appName, ConnectInfo targetDevice)")]
351  public static bool IsAppRunning(string appName, string targetDevice)
352  {
353  return IsAppRunning(appName, new ConnectInfo(targetDevice, BuildDeployPrefs.DeviceUser, BuildDeployPrefs.DevicePassword));
354  }
355 
362  public static bool IsAppRunning(string appName, ConnectInfo targetDevice)
363  {
364  string response = WebRequestGet(string.Format(API_ProcessQuery, FinalizeUrl(targetDevice.IP)), GetBasicAuthHeader(targetDevice, true), false);
365 
366  if (!string.IsNullOrEmpty(response))
367  {
368  var processList = JsonUtility.FromJson<ProcessList>(response);
369  for (int i = 0; i < processList.Processes.Length; ++i)
370  {
371  string processName = processList.Processes[i].ImageName;
372 
373  if (processName.Contains(appName))
374  {
375  return true;
376  }
377  }
378  }
379 
380  return false;
381  }
382 
389  private static AppDetails QueryAppDetails(string packageFamilyName, ConnectInfo targetDevice)
390  {
391  string response = WebRequestGet(string.Format(API_PackagesQuery, FinalizeUrl(targetDevice.IP)), GetBasicAuthHeader(targetDevice, true), false);
392 
393  if (!string.IsNullOrEmpty(response))
394  {
395  var appList = JsonUtility.FromJson<AppList>(response);
396  for (int i = 0; i < appList.InstalledPackages.Length; ++i)
397  {
398  string thisAppName = appList.InstalledPackages[i].PackageFamilyName;
399  if (thisAppName.Equals(packageFamilyName, StringComparison.OrdinalIgnoreCase))
400  {
401  return appList.InstalledPackages[i];
402  }
403  }
404  }
405 
406  return null;
407  }
408 
416  public static bool InstallApp(string appFullPath, ConnectInfo targetDevice, bool waitForDone = true)
417  {
418  bool success = false;
419 
420  try
421  {
422  // Calculate the cert and dependency paths
423  string fileName = Path.GetFileName(appFullPath);
424  string certFullPath = Path.ChangeExtension(appFullPath, ".cer");
425  string certName = Path.GetFileName(certFullPath);
426  string depPath = Path.GetDirectoryName(appFullPath) + @"\Dependencies\x86\";
427 
428  // Post it using the REST API
429  var form = new WWWForm();
430 
431  // APPX file
432  Debug.Assert(appFullPath != null);
433  using (var stream = new FileStream(appFullPath, FileMode.Open, FileAccess.Read, FileShare.Read))
434  {
435  using (var reader = new BinaryReader(stream))
436  {
437  form.AddBinaryData(fileName, reader.ReadBytes((int)reader.BaseStream.Length), fileName);
438  }
439  }
440 
441  // CERT file
442  Debug.Assert(certFullPath != null);
443  using (var stream = new FileStream(certFullPath, FileMode.Open, FileAccess.Read, FileShare.Read))
444  {
445  using (var reader = new BinaryReader(stream))
446  {
447  form.AddBinaryData(certName, reader.ReadBytes((int)reader.BaseStream.Length), certName);
448  }
449  }
450 
451  // Dependencies
452  FileInfo[] depFiles = new DirectoryInfo(depPath).GetFiles();
453  foreach (FileInfo dep in depFiles)
454  {
455  using (var stream = new FileStream(dep.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
456  {
457  using (var reader = new BinaryReader(stream))
458  {
459  string depFilename = Path.GetFileName(dep.FullName);
460  form.AddBinaryData(depFilename, reader.ReadBytes((int)reader.BaseStream.Length), depFilename);
461  }
462  }
463  }
464 
465  // Query
466  string query = string.Format(API_InstallQuery, FinalizeUrl(targetDevice.IP));
467  query += "?package=" + WWW.EscapeURL(fileName);
468 
469  var response = WebRequestPost(query, form, GetBasicAuthHeader(targetDevice));
470 
471  if (string.IsNullOrEmpty(response))
472  {
473  Debug.LogErrorFormat("Failed to install {0} on {1}.\n", fileName, targetDevice.MachineName);
474  return false;
475  }
476 
477  // Wait for done (if requested)
478  DateTime waitStartTime = DateTime.Now;
479  while (waitForDone && (DateTime.Now - waitStartTime).TotalSeconds < MaxWaitTime)
480  {
481  EditorUtility.DisplayProgressBar("Connecting to Device Portal", "Installing...", (float)((DateTime.Now - waitStartTime).TotalSeconds / MaxWaitTime));
482  AppInstallStatus status = GetInstallStatus(targetDevice);
483 
484  if (status == AppInstallStatus.InstallSuccess)
485  {
486  Debug.LogFormat("Successfully installed {0} on {1}.", fileName, targetDevice.MachineName);
487  success = true;
488  break;
489  }
490 
491  if (status == AppInstallStatus.InstallFail)
492  {
493  Debug.LogErrorFormat("Failed to install {0} on {1}.\n", fileName, targetDevice.MachineName);
494  break;
495  }
496 
497  // Wait a bit and we'll ask again
498  Thread.Sleep(1000);
499  }
500 
501  EditorUtility.ClearProgressBar();
502  }
503  catch (Exception e)
504  {
505  Debug.LogException(e);
506  success = false;
507  }
508 
509  return success;
510  }
511 
512  private static AppInstallStatus GetInstallStatus(ConnectInfo targetDevice)
513  {
514  string response = WebRequestGet(string.Format(API_InstallStatusQuery, FinalizeUrl(targetDevice.IP)), GetBasicAuthHeader(targetDevice, true), false);
515 
516  if (!string.IsNullOrEmpty(response))
517  {
518  var status = JsonUtility.FromJson<InstallStatus>(response);
519 
520  if (status == null)
521  {
522  return AppInstallStatus.Installing;
523  }
524 
525  if (status.Success)
526  {
527  return AppInstallStatus.InstallSuccess;
528  }
529 
530  Debug.LogError(status.Reason + "(" + status.CodeText + ")");
531  }
532  else
533  {
534  return AppInstallStatus.Installing;
535  }
536 
537  return AppInstallStatus.InstallFail;
538  }
539 
540  [Obsolete("Use UninstallApp(string packageFamilyName, ConnectInfo targetDevice)")]
541  public static bool UninstallApp(string packageFamilyName, string targetIp)
542  {
543  return UninstallApp(packageFamilyName, new ConnectInfo(targetIp, BuildDeployPrefs.DeviceUser, BuildDeployPrefs.DevicePassword));
544  }
545 
553  public static bool UninstallApp(string packageFamilyName, ConnectInfo targetDevice, bool showDialog = true)
554  {
555  AppDetails appDetails = QueryAppDetails(packageFamilyName, targetDevice);
556  if (appDetails == null)
557  {
558  Debug.Log(string.Format("Application '{0}' not found", packageFamilyName));
559  return false;
560  }
561 
562  string query = string.Format("{0}?package={1}",
563  string.Format(API_InstallQuery, FinalizeUrl(targetDevice.IP)),
564  WWW.EscapeURL(appDetails.PackageFullName));
565 
566  bool success = WebRequestDelete(query, GetBasicAuthHeader(targetDevice), showDialog);
567  MachineName targetMachine = GetMachineName(targetDevice);
568 
569  if (success)
570  {
571  Debug.LogFormat("Successfully uninstalled {0} on {1}.", packageFamilyName, targetMachine.ComputerName);
572  }
573  else
574  {
575  Debug.LogErrorFormat("Failed to uninstall {0} on {1}", packageFamilyName, targetMachine.ComputerName);
576  }
577 
578  return success;
579  }
580 
588  public static bool LaunchApp(string packageFamilyName, ConnectInfo targetDevice, bool showDialog = true)
589  {
590  // Find the app description
591  AppDetails appDetails = QueryAppDetails(packageFamilyName, targetDevice);
592 
593  if (appDetails == null)
594  {
595  Debug.LogWarning("Application not found");
596  return false;
597  }
598 
599  string query = string.Format(API_AppQuery, FinalizeUrl(targetDevice.IP)) +
600  string.Format("?appid={0}&package={1}",
601  WWW.EscapeURL(EncodeTo64(appDetails.PackageRelativeId)),
602  WWW.EscapeURL(appDetails.PackageFullName));
603  WebRequestPost(query, null, GetBasicAuthHeader(targetDevice), false);
604 
605  return IsAppRunning(PlayerSettings.productName, targetDevice);
606  }
607 
608  [Obsolete("KillApp(string packageFamilyName, ConnectInfo targetDevice)")]
609  public static bool KillApp(string packageFamilyName, string targetIp)
610  {
611  return KillApp(packageFamilyName, new ConnectInfo(targetIp, BuildDeployPrefs.DeviceUser, BuildDeployPrefs.DevicePassword));
612  }
613 
621  public static bool KillApp(string packageFamilyName, ConnectInfo targetDevice, bool showDialog = true)
622  {
623  AppDetails appDetails = QueryAppDetails(packageFamilyName, targetDevice);
624  if (appDetails == null)
625  {
626  Debug.LogError("Application not found");
627  return false;
628  }
629 
630  string query = string.Format("{0}?package={1}",
631  string.Format(API_AppQuery, FinalizeUrl(targetDevice.IP)),
632  WWW.EscapeURL(EncodeTo64(appDetails.PackageFullName)));
633 
634  bool success = WebRequestDelete(query, GetBasicAuthHeader(targetDevice), showDialog);
635  MachineName targetMachine = GetMachineName(targetDevice);
636 
637  if (success)
638  {
639  Debug.LogFormat("Successfully stopped {0} on {1}.", packageFamilyName, targetMachine.ComputerName);
640  }
641 
642  return success;
643  }
644 
645  [Obsolete("DeviceLogFile_View(string packageFamilyName, ConnectInfo targetDevice)")]
646  public static bool DeviceLogFile_View(string packageFamilyName, string targetIp)
647  {
648  return DeviceLogFile_View(packageFamilyName, new ConnectInfo(targetIp, BuildDeployPrefs.DeviceUser, BuildDeployPrefs.DevicePassword));
649  }
650 
657  public static bool DeviceLogFile_View(string packageFamilyName, ConnectInfo targetDevice)
658  {
659  EditorUtility.DisplayProgressBar("Download Log", "Downloading Log File for " + packageFamilyName, 0.25f);
660 
661  AppDetails appDetails = QueryAppDetails(packageFamilyName, targetDevice);
662  if (appDetails == null)
663  {
664  Debug.LogWarningFormat("{0} not installed on target device", packageFamilyName);
665  EditorUtility.ClearProgressBar();
666  return false;
667  }
668 
669  string logFile = string.Format("{0}/{1}_{2}{3}{4}{5}{6}{7}_deviceLog.txt",
670  Application.temporaryCachePath,
671  targetDevice.MachineName,
672  DateTime.Now.Year,
673  DateTime.Now.Month,
674  DateTime.Now.Day,
675  DateTime.Now.Hour,
676  DateTime.Now.Minute,
677  DateTime.Now.Second);
678 
679  string response = WebRequestGet(string.Format(API_FileQuery, FinalizeUrl(targetDevice.IP), appDetails.PackageFullName), GetBasicAuthHeader(targetDevice, true));
680  bool success = !string.IsNullOrEmpty(response);
681 
682  if (success)
683  {
684  File.WriteAllText(logFile, response);
685  Process.Start(logFile);
686  }
687 
688  EditorUtility.ClearProgressBar();
689 
690  return success;
691  }
692 
698  public static NetworkInfo GetNetworkInfo(ConnectInfo targetDevice)
699  {
700  string response = WebRequestGet(string.Format(API_IpConfigQuery, FinalizeUrl(targetDevice.IP)), GetBasicAuthHeader(targetDevice, true), false);
701  if (!string.IsNullOrEmpty(response))
702  {
703  return JsonUtility.FromJson<NetworkInfo>(response);
704  }
705 
706  return null;
707  }
708 
715  private static string FinalizeUrl(string targetUrl)
716  {
717  string ssl = BuildDeployPrefs.UseSSL ? "s" : string.Empty;
718 
719  if (targetUrl.Contains("Local Machine"))
720  {
721  targetUrl = "127.0.0.1:10080";
722  ssl = string.Empty;
723  }
724  return string.Format(@"http{0}://{1}", ssl, targetUrl);
725  }
726 
727  private static string EncodeTo64(string toEncode)
728  {
729  byte[] toEncodeAsBytes = Encoding.ASCII.GetBytes(toEncode);
730  string returnValue = Convert.ToBase64String(toEncodeAsBytes);
731  return returnValue;
732  }
733 
734  private static string DecodeFrom64(string encodedData)
735  {
736  byte[] encodedDataAsBytes = Convert.FromBase64String(encodedData);
737  string returnValue = Encoding.ASCII.GetString(encodedDataAsBytes);
738  return returnValue;
739  }
740  }
741 }
static bool DeviceLogFile_View(string packageFamilyName, ConnectInfo targetDevice)
Downloads and launches the Log file for the target application on the target device.
static NetworkInfo GetNetworkInfo(ConnectInfo targetDevice)
Returns the NetworkInfo for the target device.
static bool IsAppRunning(string appName, ConnectInfo targetDevice)
Determines if the target application is running on the target device.
AppDetails [] InstalledPackages
Definition: AppList.cs:11
static MachineName GetMachineName(ConnectInfo targetDevice)
Gets the MachineName of the target device.
static bool IsAppInstalled(string packageFamilyName, string targetIp)
static bool InstallApp(string appFullPath, ConnectInfo targetDevice, bool waitForDone=true)
Installs the target application on the target device.
Function used to communicate with the device through the REST API
static bool UninstallApp(string packageFamilyName, string targetIp)
static bool KillApp(string packageFamilyName, string targetIp)
static void OpenWebPortal(ConnectInfo targetDevice)
Opens the Device Portal for the target device.
static bool DeviceLogFile_View(string packageFamilyName, string targetIp)
static bool UninstallApp(string packageFamilyName, ConnectInfo targetDevice, bool showDialog=true)
Uninstalls the target application on the target device.
static bool IsAppInstalled(string packageFamilyName, ConnectInfo targetDevice)
Determines if the target application is currently running on the target device.
static bool IsAppRunning(string appName, string targetDevice)
static bool LaunchApp(string packageFamilyName, ConnectInfo targetDevice, bool showDialog=true)
Launches the target application on the target device.
static bool KillApp(string packageFamilyName, ConnectInfo targetDevice, bool showDialog=true)
Kills the target application on the target device.