diff --git a/App.config b/App.config
new file mode 100644
index 0000000..3c88dd4
--- /dev/null
+++ b/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/DevicePortalTool.csproj b/DevicePortalTool.csproj
new file mode 100644
index 0000000..d83decc
--- /dev/null
+++ b/DevicePortalTool.csproj
@@ -0,0 +1,117 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {A1A457E2-2559-4400-9C37-9ABC3D8D5D35}
+ Exe
+ DevicePortalTool
+ DevicePortalTool
+ v4.5.2
+ 512
+ true
+ true
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+
+
+ DevicePortalTool.Program
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DevicePortalTool.sln b/DevicePortalTool.sln
new file mode 100644
index 0000000..53e5de0
--- /dev/null
+++ b/DevicePortalTool.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28307.168
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevicePortalTool", "DevicePortalTool.csproj", "{A1A457E2-2559-4400-9C37-9ABC3D8D5D35}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A1A457E2-2559-4400-9C37-9ABC3D8D5D35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1A457E2-2559-4400-9C37-9ABC3D8D5D35}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1A457E2-2559-4400-9C37-9ABC3D8D5D35}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1A457E2-2559-4400-9C37-9ABC3D8D5D35}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {5F79B122-3A33-472D-907F-74ABFAF87B99}
+ EndGlobalSection
+EndGlobal
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..63caae8
--- /dev/null
+++ b/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("DevicePortalTool")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Unity Technologies")]
+[assembly: AssemblyProduct("DevicePortalTool")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("a1a457e2-2559-4400-9c37-9abc3d8d5d35")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..057df14
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+# DevicePortalTool
+A command line program to execute Windows Device Portal REST APIs on a remote Windows 10 device, intended for use within an automated toolchain.
+This program was developed using the [WindowsDevicePortalWrapper project](https://github.com/Microsoft/WindowsDevicePortalWrapper) provided by Microsoft.
+
diff --git a/Source/AppOperation.cs b/Source/AppOperation.cs
new file mode 100644
index 0000000..b75f842
--- /dev/null
+++ b/Source/AppOperation.cs
@@ -0,0 +1,734 @@
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Tools.WindowsDevicePortal;
+using static Microsoft.Tools.WindowsDevicePortal.DevicePortal;
+
+namespace DevicePortalTool
+{
+ class AppOperation
+ {
+ public enum Operation
+ {
+ None,
+ ListInstalledApps,
+ Install,
+ Run,
+ Uninstall,
+ }
+
+ public static readonly string AvailableOperationsText =
+ "Supported App operations are the following:\n"
+ + " list\n"
+ + " install\n"
+ + " run\n"
+ + " uninstall\n"
+ ;
+
+ public static readonly string AppOperationUsageText =
+ "Execute an Application operation on the remote device:\n"
+ + " /op: []\n"
+ + " Executes the app operation with operation-specific parameters\n"
+ + "\n"
+ + " /op: /?\n"
+ + " Shows usage for specified operation\n"
+ + "\n"
+ + AvailableOperationsText
+ ;
+
+ public static readonly string ListOpUsageText =
+ "Lists the apps currently installed on the device\n"
+ ;
+
+ public static readonly string InstallOpUsageText =
+ "Installs the specified app package to the device and optionally runs the specified app\n"
+ + " /appx: [/cert:] [/launch]\n"
+ + " Installs the given AppX package and optionally launches the app on the remote device\n"
+ ;
+
+ public static readonly string RunOpUsageText =
+ "Runs the specified app on the device\n"
+ + " /package: /aumid:\n"
+ + " Launches the app installed specified by package and aumid on the remote device\n"
+ ;
+
+ public static readonly string UninstallOpUsageText =
+ "Uninstall the specified app from the device\n"
+ + " /package:\n"
+ + " Uninstall the app matching the specified full package name from the device\n"
+ ;
+
+ public static readonly string ListAppsOpUsageTExt =
+ "Outputs a list of installed app packages on the device to stdout\n"
+ ;
+
+ public static readonly string ParameterAppx = "appx";
+ public static readonly string ParameterCert = "cert";
+ public static readonly string ParameterLaunch = "launch";
+ public static readonly string parameterPackage = "package";
+ public static readonly string ParameterAumid = "aumid";
+
+ public static Operation OperationStringToEnum(string operationName)
+ {
+ if (String.IsNullOrWhiteSpace(operationName))
+ {
+ return Operation.None;
+ }
+ if (operationName.Equals("list", StringComparison.OrdinalIgnoreCase))
+ {
+ return Operation.ListInstalledApps;
+ }
+ if (operationName.Equals("install", StringComparison.OrdinalIgnoreCase))
+ {
+ return Operation.Install;
+ }
+ if (operationName.Equals("run", StringComparison.OrdinalIgnoreCase))
+ {
+ return Operation.Run;
+ }
+ if (operationName.Equals("uninstall", StringComparison.OrdinalIgnoreCase))
+ {
+ return Operation.Uninstall;
+ }
+
+ return Operation.None;
+ }
+
+ public static string OperationSpecificUsageText(Operation op)
+ {
+ switch (op)
+ {
+ case Operation.Install:
+ return InstallOpUsageText;
+
+ case Operation.Run:
+ return RunOpUsageText;
+
+ case Operation.Uninstall:
+ return UninstallOpUsageText;
+
+ default: return String.Empty;
+ }
+ }
+
+ public AppOperation(DevicePortal portal)
+ {
+ if (portal == null)
+ throw new System.ArgumentNullException("Must specify a valid DevicePortal object");
+
+ _portal = portal;
+ _runningOperation = new SemaphoreSlim(1, 1);
+ }
+
+ public void ExecuteOperation(Operation op, ParameterHelper parameters)
+ {
+ ExecuteOperationInternal(op, parameters);
+ }
+
+ public static bool TryExecuteApplicationOperation(DevicePortal portal, ParameterHelper parameters)
+ {
+ try
+ {
+ var appOperation = new AppOperation(portal);
+ appOperation.ExecuteOperation(AppOperation.OperationStringToEnum(parameters.GetParameterValue(ParameterHelper.Operation)), parameters);
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ return true;
+ }
+
+
+ // Class fields
+ private DevicePortal _portal;
+ private SemaphoreSlim _runningOperation;
+
+ // During "install" operation contains the Identity data extracted from appx Manifest
+ private AppPackageIdentity _installedAppId;
+
+ private bool _verbose;
+ private bool _helpFlag;
+
+ private ApplicationInstallStatusEventArgs _lastInstallStatus;
+
+ // Internal methods
+ private void ExecuteOperationInternal(Operation op, ParameterHelper parameters)
+ {
+ if (!_runningOperation.Wait(0))
+ {
+ throw new SemaphoreFullException("Existing operation still running");
+ }
+
+ _verbose = parameters.HasFlag(ParameterHelper.VerboseFlag);
+ _helpFlag = parameters.HasFlag(ParameterHelper.HelpFlag);
+
+ try
+ {
+ if (_helpFlag)
+ {
+ OutputOperationSpecificUsageText(op);
+ return;
+ }
+
+ switch (op)
+ {
+ case Operation.ListInstalledApps:
+ ExecuteListInstalledAppsOperation(parameters);
+ break;
+
+ case Operation.Install:
+ ExecuteInstallOperation(parameters);
+
+ // Optionally run app on the device if "/launch" switch used
+ if (parameters.HasFlag(ParameterLaunch))
+ {
+ ExecuteRunOperation(parameters);
+ }
+ break;
+
+ case Operation.Run:
+ ExecuteRunOperation(parameters);
+ break;
+
+ case Operation.Uninstall:
+ ExecuteUninstallyOperation(parameters);
+ break;
+
+ default:
+ OutputDefaultUsageText();
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ var errorMessage = new StringBuilder();
+ if (_verbose)
+ {
+ errorMessage.Append(ex.ToString());
+ errorMessage.Append("\n\n");
+ }
+ else
+ {
+ string message = ex.Message;
+ if (String.IsNullOrWhiteSpace(message))
+ {
+ message = "No exception message";
+ }
+ errorMessage.Append("App operation '" + op.ToString() + "' failed: " + message + "\n\n");
+ }
+
+ var wdpEx = ex as DevicePortalException;
+ if (wdpEx != null)
+ {
+ errorMessage.Append("DevicePortal exception details: \n");
+ errorMessage.Append("RequestURI: " + wdpEx.RequestUri + "\n");
+ errorMessage.Append("Reason: " + wdpEx.Reason + "\n");
+ errorMessage.Append("HTTP Status: " + wdpEx.StatusCode.ToString() + "\n");
+ errorMessage.Append("\n");
+ }
+
+ Console.Out.WriteLine(errorMessage.ToString());
+ throw;
+ }
+ finally
+ {
+ _runningOperation.Release();
+ }
+ }
+
+ private void OutputOperationSpecificUsageText(Operation op)
+ {
+ switch (op)
+ {
+
+ case Operation.ListInstalledApps:
+ Console.Out.WriteLine(ListAppsOpUsageTExt);
+ break;
+
+ case Operation.Install:
+ Console.Out.WriteLine(InstallOpUsageText);
+ break;
+
+ case Operation.Run:
+ Console.Out.WriteLine(RunOpUsageText);
+ break;
+
+ case Operation.Uninstall:
+ Console.Out.WriteLine(UninstallOpUsageText);
+ break;
+
+ default:
+ OutputDefaultUsageText();
+ break;
+ }
+ }
+
+ private void OutputDefaultUsageText()
+ {
+ string errorMessage = "";
+
+ if (!_helpFlag)
+ {
+ errorMessage = "Invalid App operation\n";
+ }
+
+ Console.Out.WriteLine(errorMessage);
+ Console.Out.WriteLine(AppOperationUsageText);
+ }
+
+ private void ExecuteListInstalledAppsOperation(ParameterHelper parameters)
+ {
+ Task packagesTask = _portal.GetInstalledAppPackagesAsync();
+ packagesTask.Wait();
+
+ var packages = packagesTask.Result;
+ Console.Out.WriteLine(packages.ToString());
+ }
+
+ private void ExecuteInstallOperation(ParameterHelper parameters)
+ {
+ // Parse app and dependency filenames from the parameters
+ string appxFile = parameters.GetParameterValue(ParameterAppx);
+ string certificate = parameters.GetParameterValue(ParameterCert);
+
+ _portal.AppInstallStatus += OnAppInstallStatus;
+ _lastInstallStatus = null;
+ try
+ {
+ if (!String.IsNullOrWhiteSpace(appxFile))
+ {
+ ExecuteInstallAppx(parameters, appxFile, certificate);
+ }
+ else
+ {
+ throw new System.ArgumentNullException("Must specify an appx file to install");
+ }
+ }
+ finally
+ {
+ _portal.AppInstallStatus -= OnAppInstallStatus;
+ _lastInstallStatus = null;
+ }
+ }
+
+ private void ExecuteInstallAppx(ParameterHelper parameters, string appxFile, string certificate)
+ {
+ if (_verbose)
+ {
+ Console.Out.WriteLine("Starting Appx installation...");
+ }
+
+ var file = new FileInfo(Path.GetFullPath(appxFile));
+ if (!file.Exists)
+ {
+ throw new System.IO.FileNotFoundException("Specified appx file '" + appxFile + "' wasn't found");
+ }
+
+ if (!String.IsNullOrWhiteSpace(certificate))
+ {
+ var certFile = new FileInfo(certificate);
+ if (!certFile.Exists)
+ {
+ throw new System.IO.FileNotFoundException("Specified certificate file '" + certFile + "' wasn't found");
+ }
+ certificate = certFile.FullName;
+ }
+ else
+ {
+ // Must pass in null instead of empty string if certificate is omitted
+ certificate = null;
+ }
+
+ // Parse the AppxManfest contained in the Appx file to extract the package name, dependencies, AppID, etc.
+ AppxManifest appxData;
+ try
+ {
+ appxData = AppxManifest.Get(appxFile);
+ }
+ catch (Exception ex)
+ {
+ Console.Out.WriteLine("Failed to parse Appx manifest: " + ex.Message);
+ throw;
+ }
+ if (!appxData.IsValid)
+ {
+ throw new System.ArgumentException("Specified Appx '" + appxFile + "' contains an invalid AppxManifest");
+ }
+
+ // Construct an "identity" object from the AppxManifest data which can be referenced later to launch the installed app
+ var appIdentity = new AppPackageIdentity(appxData);
+
+ // Query for app packages already installed on the device and uninstall them if necessary
+ // NOTE: Check for any uninstall any package matching this appx PackageName and Publisher to
+ // ensure a clean install of the new build
+
+ List matchingPackages;
+ if (TryRetrieveInstalledPackages(appIdentity, out matchingPackages) && matchingPackages.Count() > 0)
+ {
+ if (_verbose)
+ {
+ Console.Out.WriteLine("Uninstalling previous app..");
+ }
+
+ foreach (var package in matchingPackages)
+ {
+ try
+ {
+ if (_verbose)
+ {
+ Console.Out.WriteLine("Uninstalling package: " + package.FullName);
+ }
+ Task uninstallTask = _portal.UninstallApplicationAsync(package.FullName);
+ }
+ catch (AggregateException ex)
+ {
+ // NOTE: We really shouldn't continue with installation if we failed to remove a previous version of the app.
+ // If a version of the app remains on the device, the Install API will NOT replace it but still reports "success",
+ // meaning the user could be running old code and not know it. A hard fail is the only way to ensure this doesn't happen.
+ Console.Out.WriteLine("Uninstall of package '" + package.FullName + "' failed: " + ex.InnerException.Message);
+ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
+ }
+ }
+
+ if (_verbose)
+ {
+ Console.Out.WriteLine("Finished uninstalling previous app packages");
+ }
+ }
+
+ Task installTask = _portal.InstallApplicationAsync(null, file.FullName, appxData.Dependencies, certificate, 500, 1, false);
+ try
+ {
+ installTask.Wait();
+ Console.Out.WriteLine("Installation completed successfully");
+ }
+ catch (AggregateException ex)
+ {
+ Console.Out.WriteLine("Installation of Appx failed!");
+
+ HandleInstallOperationException(ex);
+ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
+ }
+
+ // Save AppIdentity to field after successful installation
+ _installedAppId = appIdentity;
+
+ // If the app Identity is "complete" we have the FullPackageName and AUMID parameters, so we'll
+ // add them to our parameter set to later launch the app; no need to query package info from the device
+ if (_installedAppId.CompleteIdentity)
+ {
+ parameters.AddOrUpdateParameter(parameterPackage, _installedAppId.PackageFullName);
+ parameters.AddOrUpdateParameter(ParameterAumid, _installedAppId.LaunchId);
+ }
+ }
+
+ private void HandleInstallOperationException(AggregateException ex)
+ {
+ if (ex == null) return;
+
+ // If available, log the last status update before the exception
+ if (_lastInstallStatus != null)
+ {
+ string errorMessage;
+ errorMessage = String.Format("Installation failed in phase {0} - last status: {1}", _lastInstallStatus.Phase, _lastInstallStatus.Message);
+ if (_verbose)
+ {
+ Console.Out.WriteLine(errorMessage);
+ }
+ }
+
+ // If multiple exception were encountered we want to log each of them
+ // The "main" exception will be handled by the top-level ExecuteOperation method
+ if (ex.InnerExceptions.Count > 1)
+ {
+ var sb = new StringBuilder();
+ sb.Append("Multiple exception were thrown during operation:\n");
+
+ foreach (var item in ex.InnerExceptions)
+ {
+ sb.Append(" " + item.Message + "\n");
+ }
+
+ if (_verbose)
+ {
+ Console.Out.WriteLine(sb.ToString());
+ }
+ }
+ }
+
+ private void ExecuteRunOperation(ParameterHelper parameters)
+ {
+ string packageName = parameters.GetParameterValue(parameterPackage);
+ string launchId = parameters.GetParameterValue(ParameterAumid);
+
+ // These parameters are required unless we just performed and install operation
+ if (String.IsNullOrWhiteSpace(packageName) && _installedAppId == null)
+ {
+ throw new System.ArgumentException("Must provide full name of app package to launch");
+ }
+ if (String.IsNullOrWhiteSpace(launchId) && _installedAppId == null)
+ {
+ throw new System.ArgumentException("Must provide the AUMID of the app to launch from the specified package");
+ }
+
+ // Just installed an app but unable to retrieve package's full name and/or family name
+ // So we'll try to query the values from the remote device
+ if (_installedAppId != null && !_installedAppId.CompleteIdentity)
+ {
+
+ if (!TryRetrievePackageNameAndLaunchIdFromDevice(_installedAppId, 4, out packageName, out launchId))
+ {
+ throw new System.Exception("Failed to retrieve necessary app package info from the remote device; cannot launch app");
+ }
+ }
+ else if (_installedAppId != null)
+ {
+ // If we just installed the app, must wait until it's fully installed/registered with the OS, otherwise launch operation will fail
+ WaitUnilAppIsFullyInstalled(packageName);
+ }
+
+ Task launchTask = _portal.LaunchApplicationAsync(launchId, packageName);
+ try
+ {
+ launchTask.Wait();
+
+ Console.WriteLine("Application launched");
+ }
+ catch (AggregateException ex)
+ {
+ Console.Out.WriteLine("App launch failed!");
+
+ HandleInstallOperationException(ex);
+ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
+ }
+ }
+
+ private void ExecuteUninstallyOperation(ParameterHelper parameters)
+ {
+ string package = parameters.GetParameterValue(parameterPackage);
+
+ if (String.IsNullOrWhiteSpace(package))
+ {
+ throw new System.ArgumentException("Must provide full name of app package to uninstall");
+ }
+
+ Task installTask = _portal.UninstallApplicationAsync(package);
+ try
+ {
+ installTask.Wait();
+ Console.Out.WriteLine("Uninstall completed successfully");
+ }
+ catch (AggregateException ex)
+ {
+ Console.Out.WriteLine("Uninstall of app failed!");
+
+ HandleInstallOperationException(ex);
+ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
+ }
+ }
+
+ private void OnAppInstallStatus(object sender, ApplicationInstallStatusEventArgs args)
+ {
+ if (_verbose)
+ {
+ Console.Out.WriteLine(args.Message);
+ }
+ _lastInstallStatus = args;
+ }
+
+ private bool TryRetrievePackageNameAndLaunchIdFromDevice(AppPackageIdentity packageId, int numAttempts, out string packageFullName, out string launchId)
+ {
+ // Already have the info locally and don't need to query from device
+ if (packageId.CompleteIdentity)
+ {
+ packageFullName = packageId.PackageFullName;
+ launchId = packageId.LaunchId;
+ return true;
+ }
+
+ if (_verbose)
+ {
+ Console.Out.WriteLine("Attempting to query PackageFullName and AUMID from remote device");
+ }
+
+ packageFullName = String.Empty;
+ launchId = String.Empty;
+ bool successful = false;
+
+ while (numAttempts > 0 && !successful)
+ {
+ numAttempts--;
+
+ try
+ {
+ Task packagesTask = _portal.GetInstalledAppPackagesAsync();
+ packagesTask.Wait();
+
+ // This basic query should provide the matching package in most cases
+ // -PackageName must exactly match
+ // -Publisher must exactly match
+ // -AUMID (called "AppId" in PackageInfo class) must contain our AppId value (registered app entry point)
+ // -Version string must exactly match
+ var matchingPackages =
+ (from package in packagesTask.Result.Packages
+ where
+ package.Name == packageId.PackageName &&
+ package.Publisher == package.Publisher &&
+ package.AppId.Contains(package.AppId) && // AppId from PackageInfo is actually full AUMID
+ package.Version.ToString() == packageId.Version
+ select package).ToList();
+
+ PackageInfo matchingPackage = null;
+
+ if (matchingPackages.Count() == 0)
+ {
+ if (_verbose)
+ {
+ Console.Out.Write("Failed to find '" + packageId.PackageName + "' package installed on the device...");
+ Console.Out.WriteLine((numAttempts > 0) ? "trying again" : "giving up");
+ }
+ }
+ else if (matchingPackages.Count() > 1)
+ {
+ // It's technically possible for the above query to return multiple packages, in which case we need to
+ // disambiguate using the optional package identifiers (CPU architecture and ResourceId).
+ // Since PackageInfo doesn't provide this fields directly, need to split PackageFullName
+ // into component its component parts =>
+ // [0] PackageName
+ // [1] Version
+ // [2] CPU architecture
+ // [3] ResourceId (if present)
+ // [4] Publisher name hash
+
+ foreach (var package in matchingPackages)
+ {
+ var nameParts = package.FullName.Split(new char[] { '_' }, 5);
+ if (nameParts.Length < 5) continue;
+
+ // ResoruceId will be an empty string if not present, which should match our manifest data
+ if (nameParts[2] == packageId.CpuArchitecture && nameParts[3] == packageId.ResourceId)
+ {
+ matchingPackage = package;
+ break;
+ }
+ }
+ }
+ else matchingPackage = matchingPackages.First();
+
+ if (matchingPackage != null)
+ {
+ packageFullName = matchingPackage.FullName;
+ launchId = matchingPackage.AppId;
+ successful = true;
+ }
+
+ // Query attempt may failed because it a few seconds before newly installed apps are reported
+ // So if we have more attempts then wait a bit before trying again
+ if (numAttempts > 0 && !successful)
+ {
+ Thread.Sleep(2000);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (_verbose)
+ {
+ Console.Out.WriteLine("Failed to acquire list of installed apps from device: " + ex.Message);
+ }
+ }
+ }
+
+ if (_verbose)
+ {
+ if (!successful)
+ {
+ Console.Out.WriteLine("Failed to retrieve FullPackageName and AUMID from remote device");
+ }
+ else Console.Out.WriteLine("Successfully retrieved FullPackageName and AUMID from remote device");
+ }
+
+ return successful;
+ }
+
+ private bool TryRetrieveInstalledPackages(AppPackageIdentity packageId, out List matchingPackages)
+ {
+ if (_verbose)
+ {
+ Console.Out.WriteLine("Attempting to query installed packages matching app");
+ }
+
+ matchingPackages = null;
+ bool successful = false;
+
+ try
+ {
+ Task packagesTask = _portal.GetInstalledAppPackagesAsync();
+ packagesTask.Wait();
+
+ // We want to find all packages that *loosely* match our appx in case something like
+ // architecture or configuration changed
+ matchingPackages =
+ (from package in packagesTask.Result.Packages
+ where
+ package.Name == packageId.PackageName &&
+ package.Publisher == package.Publisher
+ select package).ToList();
+
+ successful = true;
+ }
+ catch (Exception ex)
+ {
+ if (_verbose)
+ {
+ Console.Out.WriteLine("Failed to acquire list of installed apps from device: " + ex.Message);
+ }
+ }
+
+ if (_verbose)
+ {
+ if (!successful)
+ {
+ Console.Out.WriteLine("Failed to retrieve installed packages from the device");
+ }
+ else Console.Out.WriteLine("Successfully retrieved installed packages matching app from the device");
+ }
+
+ return successful;
+ }
+
+ private void WaitUnilAppIsFullyInstalled(string fullPackageName)
+ {
+ PackageInfo appPackage = null;
+ int numAttempts = 5;
+
+ // DevicePortal API has an annoying quirk in which the "install" call will return before the app is fully ready
+ // on the remote device. It takes a few extra seconds before Windows can launch it, and attempting to launch the app before
+ // it's ready results in an error. So, wait until we can successfully query the package from the device using the "list"
+ // operation, once we see it in the returned results we know the app is ready and can be launched.
+
+ do
+ {
+ Thread.Sleep(3000);
+
+ try
+ {
+ Task packagesTask = _portal.GetInstalledAppPackagesAsync();
+ packagesTask.Wait();
+
+ appPackage = packagesTask.Result.Packages.FirstOrDefault(package => (package.FullName == fullPackageName));
+ }
+ catch {; }
+
+ numAttempts--;
+
+ } while (appPackage == null && numAttempts > 0);
+ }
+ }
+}
diff --git a/Source/AppxManifest.cs b/Source/AppxManifest.cs
new file mode 100644
index 0000000..de27b9e
--- /dev/null
+++ b/Source/AppxManifest.cs
@@ -0,0 +1,203 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Xml.Linq;
+
+namespace DevicePortalTool
+{
+ public class Dependency
+ {
+ public string Name { get; private set; }
+ public Version MinVersion { get; private set; }
+
+ public Dependency(string name, string minVersion)
+ {
+ Name = name;
+ MinVersion = new Version(minVersion);
+ }
+ }
+
+ public class AppxManifest
+ {
+ private static Dictionary ManifestCache = new Dictionary();
+
+ public string AppxPath { get; private set; }
+ public Guid PhoneProductId { get; private set; }
+ public string PackageName { get; private set; }
+ public string Publisher { get; private set; }
+ public string Version { get; private set; }
+ public string AppId { get; private set; }
+ public List Dependencies { get; private set; }
+ public string CpuArchitecture { get; private set; }
+ public string ResourceId { get; private set; }
+ public bool IsFramework { get; private set; }
+ public bool IsDependency { get; private set; }
+ public bool IsValid { get; private set; }
+
+ public static AppxManifest Get(string appxPath)
+ {
+ if (!ManifestCache.ContainsKey(appxPath))
+ {
+ return new AppxManifest(appxPath); // Constructor will add it to manifest cache midway through construction.
+ }
+
+ return ManifestCache[appxPath];
+ }
+
+ public static AppxManifest Get(string appxPath, string extractedPath)
+ {
+ if (!ManifestCache.ContainsKey(appxPath))
+ {
+ return new AppxManifest(appxPath, extractedPath); // Constructor will add it to manifest cache midway through construction.
+ }
+
+ return ManifestCache[appxPath];
+ }
+
+ private AppxManifest(string appxPath)
+ {
+ AppxPath = appxPath;
+ var extension = Path.GetExtension(appxPath);
+ if (string.Equals(extension, ".xml", StringComparison.OrdinalIgnoreCase))
+ {
+ Parse(XDocument.Load(appxPath));
+ }
+ else
+ {
+ using (var archive = System.IO.Compression.ZipFile.OpenRead(appxPath))
+ {
+ var manifestStream = archive.GetEntry("AppxManifest.xml").Open();
+ Parse(XDocument.Load(manifestStream));
+ }
+ }
+ }
+
+ private AppxManifest(string appxPath, string extractedPath)
+ {
+ AppxPath = appxPath;
+ using (var stream = new StreamReader(Path.Combine(extractedPath, "AppXManifest.xml")))
+ {
+ Parse(XDocument.Load(stream));
+ }
+ }
+
+ private void Parse(XDocument manifest)
+ {
+ ManifestCache.Add(AppxPath, this);
+
+ string namezspace = "http://schemas.microsoft.com/appx/manifest/foundation/windows10";
+ try
+ {
+ var phoneIdentity = manifest.Root.Element(XName.Get("PhoneIdentity", "http://schemas.microsoft.com/appx/2014/phone/manifest"));
+ if (phoneIdentity != null)
+ {
+ PhoneProductId = Guid.Parse(phoneIdentity.Attribute("PhoneProductId").Value);
+ }
+
+ var identity = manifest.Root.Element(XName.Get("Identity", namezspace));
+ if (identity == null)
+ {
+ throw new ArgumentNullException("identity");
+ }
+
+ PackageName = identity.Attribute("Name")?.Value;
+ if (PackageName == null)
+ {
+ throw new ArgumentNullException("packageName");
+ }
+
+ Publisher = identity.Attribute("Publisher")?.Value;
+ if (Publisher == null)
+ {
+ throw new ArgumentNullException("publisher");
+ }
+
+ Version = identity.Attribute("Version")?.Value;
+ if (Version == null)
+ {
+ throw new ArgumentNullException("version");
+ }
+
+ var applications = manifest.Root.Element(XName.Get("Applications", namezspace));
+ if (applications != null)
+ {
+ var applicationElement = applications.Element(XName.Get("Application", namezspace));
+
+ if (applicationElement != null)
+ {
+ AppId = applicationElement.Attribute(XName.Get("Id"))?.Value;
+ if (AppId == null)
+ {
+ throw new ArgumentNullException("appid");
+ }
+ }
+ }
+
+ // Optional identity attributes
+ CpuArchitecture = identity.Attribute("ProcessorArchitecture")?.Value ?? "neutral";
+ ResourceId = identity.Attribute("ResourceId")?.Value ?? "";
+
+ var properties = manifest.Root.Element(XName.Get("Properties", namezspace));
+ var frameworkAttribute = properties.Element(XName.Get("Framework", namezspace));
+
+ IsFramework = frameworkAttribute != null && frameworkAttribute.Value.Equals("True", StringComparison.InvariantCultureIgnoreCase);
+ }
+ catch // This will happen if we happen to try to parse non-UWP app manifest
+ {
+ IsValid = false;
+ return;
+ }
+
+ IsValid = true;
+
+ var dependencies = manifest.Root.Element(XName.Get("Dependencies", namezspace));
+
+ Dependencies = new List();
+ if (dependencies != null)
+ {
+ foreach (var dependencyInfo in dependencies.Descendants())
+ {
+ var dependency = new Dependency(dependencyInfo.Attribute("Name").Value, dependencyInfo.Attribute("MinVersion").Value);
+ AddDependency(dependency, AppxPath);
+ }
+ }
+ }
+
+ private void AddDependency(Dependency dependency, string appxPath)
+ {
+ var appxFolder = Path.GetDirectoryName(appxPath);
+ var dependenciesFolder = Path.Combine(appxFolder, "Dependencies");
+ var searchFolders = new string[] { Path.Combine(dependenciesFolder, CpuArchitecture), dependenciesFolder, appxFolder };
+
+ foreach (var searchFolder in searchFolders)
+ {
+ if (!Directory.Exists(searchFolder))
+ continue;
+
+ foreach (var file in Directory.GetFiles(searchFolder, "*.appx"))
+ {
+ if (AddDependenciesIfMatches(file, dependency))
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ private bool AddDependenciesIfMatches(string path, Dependency dependency)
+ {
+ var dependencyManifest = Get(path);
+
+ if (dependencyManifest.IsValid && dependencyManifest.PackageName.Equals(dependency.Name, StringComparison.InvariantCultureIgnoreCase))
+ {
+ Dependencies.AddRange(dependencyManifest.Dependencies);
+ Dependencies.Add(path);
+ dependencyManifest.IsDependency = true;
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/Source/PackageHelper.cs b/Source/PackageHelper.cs
new file mode 100644
index 0000000..0b631f7
--- /dev/null
+++ b/Source/PackageHelper.cs
@@ -0,0 +1,206 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DevicePortalTool
+{
+ public class AppPackageIdentity
+ {
+ public string PackageName { get; private set; }
+ public string AppId { get; private set; }
+ public string Publisher { get; private set; }
+ public string Version { get; private set; }
+ public Version VersionValue { get; private set; }
+ public string CpuArchitecture { get; private set; }
+ public UInt32 CpuArchitectureValue { get; private set; }
+ public string ResourceId { get; private set; }
+ public string PackageFullName { get; private set; }
+ public string PackageFamilyName { get; private set; }
+ public string LaunchId { get; private set; }
+ public bool CompleteIdentity { get; private set; }
+
+ public AppPackageIdentity(AppxManifest appxData)
+ {
+ InitializeInternal(appxData.PackageName, appxData.AppId, appxData.Publisher, appxData.Version, appxData.CpuArchitecture, appxData.ResourceId);
+ }
+
+ public AppPackageIdentity(string packageName, string appId, string publisher, string version)
+ {
+ InitializeInternal(packageName, appId, publisher, version, "neutral", "");
+ }
+
+ public AppPackageIdentity(string packageName, string appId, string publisher, string version, string cpuArchitecture, string resourceId)
+ {
+ InitializeInternal(packageName, appId, publisher, version, cpuArchitecture, resourceId);
+ }
+
+ private void InitializeInternal(string packageName, string appId, string publisher, string version, string cpuArchitecture, string resourceId)
+ {
+ if (String.IsNullOrWhiteSpace(packageName))
+ throw new System.ArgumentNullException("PackageName is invalid");
+ if (String.IsNullOrWhiteSpace(appId))
+ throw new System.ArgumentNullException("AppId is invalid");
+ if (String.IsNullOrWhiteSpace(publisher))
+ throw new System.ArgumentNullException("Publisher is invalid");
+ if (String.IsNullOrWhiteSpace(version))
+ throw new System.ArgumentNullException("Version is invalid");
+
+ if (cpuArchitecture == null)
+ cpuArchitecture = "neutral";
+ if (resourceId == null)
+ resourceId = "";
+
+ this.PackageName = packageName;
+ this.AppId = appId;
+ this.Publisher = publisher;
+ this.Version = version;
+ this.CpuArchitecture = cpuArchitecture;
+ this.ResourceId = resourceId;
+
+ System.Version verObj;
+ if (System.Version.TryParse(version, out verObj))
+ {
+ this.VersionValue = verObj;
+ }
+
+ this.CpuArchitectureValue = PackageHelper.ProcessorArchitectureStringToEnum(cpuArchitecture);
+
+ this.PackageFamilyName = PackageHelper.TryGetPackageFamilyName(packageName, publisher);
+ this.PackageFullName = PackageHelper.TryGetPackageFullName(packageName, publisher, this.VersionValue, this.CpuArchitectureValue);
+
+ // If PackageFamilyName was successfully retrieved, construct the "LaunchId" (AUMID)
+ if (!String.IsNullOrWhiteSpace(this.PackageFamilyName))
+ {
+ this.LaunchId = this.PackageFamilyName + "!" + this.AppId;
+ }
+ else this.LaunchId = String.Empty;
+
+ // If we successfully retrieve FamilyName and FullName then we have package identity is "complete"
+ // Otherwise one or more properties are missing or invalid
+ if (!String.IsNullOrWhiteSpace(this.PackageFullName) && !String.IsNullOrWhiteSpace(this.PackageFamilyName))
+ {
+ this.CompleteIdentity = true;
+ }
+ }
+ }
+
+ internal class PackageHelper
+ {
+ public static UInt32 ProcessorArchitectureStringToEnum(string architecture)
+ {
+ architecture = architecture.ToLowerInvariant();
+ switch (architecture)
+ {
+ case "x86": return (UInt32)APPX_PACKAGE_ARCHITECTURE.APPX_PACKAGE_ARCHITECTURE_X86;
+ case "x64": return (UInt32)APPX_PACKAGE_ARCHITECTURE.APPX_PACKAGE_ARCHITECTURE_X64;
+ case "arm": return (UInt32)APPX_PACKAGE_ARCHITECTURE.APPX_PACKAGE_ARCHITECTURE_ARM;
+ case "arm64": return (UInt32)APPX_PACKAGE_ARCHITECTURE.APPX_PACKAGE_ARCHITECTURE_ARM64;
+ }
+
+ return (UInt32)APPX_PACKAGE_ARCHITECTURE.APPX_PACKAGE_ARCHITECTURE_NEUTRAL;
+ }
+
+ public static string TryGetPackageFamilyName(string name, string publisherId)
+ {
+ string packageFamilyName = String.Empty;
+
+ try
+ {
+ var packageId = new PACKAGE_ID
+ {
+ name = name,
+ publisher = publisherId,
+ };
+
+ uint packageFamilyNameLength = 0;
+
+ // First get the length of the Package Name -> Pass NULL as Output Buffer
+ if (PackageFamilyNameFromId(packageId, ref packageFamilyNameLength, null) == 122) // ERROR_INSUFFICIENT_BUFFER
+ {
+ var packageFamilyNameBuilder = new StringBuilder((int)packageFamilyNameLength);
+ if (PackageFamilyNameFromId(packageId, ref packageFamilyNameLength, packageFamilyNameBuilder) == 0)
+ {
+ packageFamilyName = packageFamilyNameBuilder.ToString();
+ }
+ }
+ }
+ catch {; }
+
+ return packageFamilyName;
+ }
+
+ public static string TryGetPackageFullName(string name, string publisherId, Version appVersion, UInt32 cpuArchitecture)
+ {
+ string packageFullName = String.Empty;
+
+ try
+ {
+ var major = Convert.ToUInt16(appVersion.Major);
+ var minor = Convert.ToUInt16(appVersion.Minor);
+ var build = Convert.ToUInt16(appVersion.Build);
+ var rev = Convert.ToUInt16(appVersion.Revision);
+
+ UInt64 packedVersion =
+ Convert.ToUInt64(rev) << 0 |
+ Convert.ToUInt64(build) << 16 |
+ Convert.ToUInt64(minor) << 32 |
+ Convert.ToUInt64(major) << 48;
+
+
+ var packageId = new PACKAGE_ID
+ {
+ name = name,
+ publisher = publisherId,
+ processorArchitecture = cpuArchitecture,
+ version = packedVersion
+ };
+
+ uint packageFullNameLength = 0;
+
+ // First get the length of the Package Name -> Pass NULL as Output Buffer
+ if (PackageFullNameFromId(packageId, ref packageFullNameLength, null) == 122) // ERROR_INSUFFICIENT_BUFFER
+ {
+ var packageFullNameBuilder = new StringBuilder((int)packageFullNameLength);
+ if (PackageFullNameFromId(packageId, ref packageFullNameLength, packageFullNameBuilder) == 0)
+ {
+ packageFullName = packageFullNameBuilder.ToString();
+ }
+ }
+ }
+ catch {; }
+
+ return packageFullName;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]
+ internal class PACKAGE_ID
+ {
+ public UInt32 reserved;
+ public UInt32 processorArchitecture;
+ public UInt64 version;
+ public string name;
+ public string publisher;
+ public string resourceId;
+ public string publisherId;
+ };
+
+ enum APPX_PACKAGE_ARCHITECTURE
+ {
+ APPX_PACKAGE_ARCHITECTURE_X86 = 0,
+ APPX_PACKAGE_ARCHITECTURE_ARM = 5,
+ APPX_PACKAGE_ARCHITECTURE_X64 = 9,
+ APPX_PACKAGE_ARCHITECTURE_NEUTRAL = 11,
+ APPX_PACKAGE_ARCHITECTURE_ARM64 = 12
+ };
+
+ // NOTE: These APIs are only available in Windows 8 and later, and If called on Windows 7 we expect an EntryPointNotFoundException will be thrown.
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
+ private static extern uint PackageFamilyNameFromId(PACKAGE_ID packageId, ref uint packageFullNameLength, StringBuilder packageFullName);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
+ private static extern uint PackageFullNameFromId(PACKAGE_ID packageId, ref uint packageFullNameLength, StringBuilder packageFullName);
+ }
+}
diff --git a/Source/ParameterHelper.cs b/Source/ParameterHelper.cs
new file mode 100644
index 0000000..eb06866
--- /dev/null
+++ b/Source/ParameterHelper.cs
@@ -0,0 +1,123 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//
+// Modified under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+
+namespace DevicePortalTool
+{
+ public class ParameterHelper
+ {
+ public static readonly string HelpFlag = "?";
+ public static readonly string VerboseFlag = "v";
+ public static readonly string Operation = "op";
+ public static readonly string DeviceIpAddress = "ip";
+ public static readonly string WdpUser = "user";
+ public static readonly string WdpPassword = "pwd";
+ public static readonly string StdinCredentials = "stdincred";
+
+ public OpperationArea Area { get; private set; } = OpperationArea.None;
+
+ private Dictionary parameters = new Dictionary();
+ private List flags = new List();
+
+ public void AddParameter(string name, string value)
+ {
+ this.parameters.Add(name, value);
+ }
+
+ public void AddOrUpdateParameter(string name, string value)
+ {
+ if (this.parameters.ContainsKey(name))
+ {
+ this.parameters[name] = value;
+ }
+ else this.parameters.Add(name, value);
+ }
+
+ public string GetParameterValue(string key)
+ {
+ if (this.parameters.ContainsKey(key))
+ {
+ return this.parameters[key];
+ }
+ else
+ {
+ return String.Empty;
+ }
+ }
+
+ public bool HasParameter(string key)
+ {
+ return this.parameters.ContainsKey(key);
+ }
+
+ public bool HasFlag(string flag)
+ {
+ return this.flags.Contains(flag);
+ }
+
+ public void ParseCommandLine(string[] args)
+ {
+ // If nothing specified then add "help" flag to display usage
+ if (args.Length == 0)
+ {
+ this.flags.Add(ParameterHelper.HelpFlag);
+ return;
+ }
+
+ // The operation area must always be the 1st parameter
+ // NOTE: In C# args[0] is program name
+ Area = Program.OperationAreaStringToEnum(args[0]);
+
+ // Parse the command line args
+ for (int i = 0; i < args.Length; ++i)
+ {
+ string arg = args[i];
+ if (!arg.StartsWith("/") && !arg.StartsWith("-"))
+ {
+ // We expect the first parameter to be the "area" which isn't prefixed with a slash
+ if (i == 0) continue;
+
+ throw new Exception(string.Format("Unrecognized argument: {0}", arg));
+ }
+
+ arg = arg.Substring(1);
+
+ int valueIndex = arg.IndexOf(':');
+ string value = null;
+
+ // If this contains a colon, separate it into the param and value. Otherwise add it as a flag
+ if (valueIndex > 0)
+ {
+ value = arg.Substring(valueIndex + 1);
+ arg = arg.Substring(0, valueIndex);
+
+ this.parameters.Add(arg.ToLowerInvariant(), value);
+ }
+ else
+ {
+ this.flags.Add(arg.ToLowerInvariant());
+ }
+ }
+ }
+
+ public static string EncodeBase64(string plainText)
+ {
+ var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
+ return Convert.ToBase64String(plainTextBytes);
+ }
+
+ public static string DecodeBase64(string base64Text)
+ {
+ var base64EncodedBytes = System.Convert.FromBase64String(base64Text);
+ return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
+ }
+ }
+}
diff --git a/Source/Program.cs b/Source/Program.cs
new file mode 100644
index 0000000..5ebbcc2
--- /dev/null
+++ b/Source/Program.cs
@@ -0,0 +1,342 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.Tools.WindowsDevicePortal;
+
+namespace DevicePortalTool
+{
+ public enum OpperationArea
+ {
+ None,
+ Application,
+ };
+
+ public enum ProgramErrorCodes : int
+ {
+ Success = 0,
+ SuccessHelp = 1,
+ OperationFailed = -1,
+ InvalidParameters = -2,
+ AuthenticationFailed = -3,
+ ConnectionFailed = -4,
+ UnexpectedError = -5,
+ };
+
+ class Program
+ {
+ private static readonly string GeneralUsageMessage =
+ "Executes a Windows DevicePortal (WDP) operation on a remote Windows 10 device\n"
+ + "\n"
+ + "Usage:\n"
+ + "DevicePortalTool /op: [operation parameters]] /ip: [/stdincred | /user:WDP username /pwd: - must be one of the following values:\n"
+ + " app - execute an Application related operation\n"
+ + " /op - area specific operation value with additional parameters\n"
+ + " /ip - IP address of remote device to operate on\n"
+ + " /stdincred - WDP credentials are read from stdin stream instead of command line (no prompts)\n"
+ + " credentials read as two separate lines of Base64 encoded strings (user name then password)\n"
+ + " /user - Name of WDP user on the device\n"
+ + " /pwd - Password of WDP user on the device\n"
+ + " /v - Verbose logging output\n"
+ + "\n"
+ + "DevicePortalTool /?\n"
+ + " Display area specific usage\n"
+ + "\n"
+ + "DevicePortalTool /?\n"
+ + " Display this usage\n"
+ ;
+
+ public static OpperationArea OperationAreaStringToEnum(string areaName)
+ {
+ if (String.IsNullOrWhiteSpace(areaName))
+ {
+ return OpperationArea.None;
+ }
+ if (areaName.Equals("app", StringComparison.OrdinalIgnoreCase))
+ {
+ return OpperationArea.Application;
+ }
+
+ return OpperationArea.None;
+ }
+
+ public static int Main(string[] args)
+ {
+ var resultCode = ExecuteMain(args);
+
+ Console.Out.WriteLine("ExitCode: " + (int)resultCode + " (" + resultCode.ToString() + ")");
+ return (int)resultCode;
+ }
+
+ private static ProgramErrorCodes ExecuteMain(string[] args)
+ {
+ ParameterHelper parameters;
+ ProgramErrorCodes errorCode;
+ Uri targetDevice = null;
+ bool helpFlag;
+ bool verbose;
+
+ errorCode = ParseParametersAndPerformBasicValidation(args, out parameters, out targetDevice, out verbose, out helpFlag);
+ if (errorCode != ProgramErrorCodes.Success)
+ return errorCode;
+
+ if (!helpFlag && parameters.HasFlag(ParameterHelper.StdinCredentials))
+ {
+ try
+ {
+ string username;
+ string password;
+
+ if (!TryReadCredentialsFromStdin(out username, out password))
+ {
+ Console.Out.WriteLine("Failed to read WDP credentials from stdin");
+ Console.Out.WriteLine();
+
+ return ProgramErrorCodes.InvalidParameters;
+ }
+
+ parameters.AddOrUpdateParameter(ParameterHelper.WdpUser, username);
+ parameters.AddOrUpdateParameter(ParameterHelper.WdpPassword, password);
+ }
+ catch (Exception ex)
+ {
+ Console.Out.WriteLine("Fatal error reading WDP credentials from stdin: " + ex.Message);
+ Console.Out.WriteLine();
+
+ return ProgramErrorCodes.UnexpectedError;
+ }
+ }
+
+ DevicePortal portal;
+ if (!helpFlag)
+ {
+ try
+ {
+ if (!TryOpenDevicePortalConnection(targetDevice, parameters, out portal))
+ {
+ if (portal != null && portal.ConnectionHttpStatusCode == System.Net.HttpStatusCode.Unauthorized)
+ {
+ Console.Out.WriteLine("Aborting due to failed authentication");
+ Console.Out.WriteLine();
+ return ProgramErrorCodes.AuthenticationFailed;
+ }
+ else
+ {
+ Console.Out.WriteLine("Aborting due to failed connection with remote device");
+ Console.Out.WriteLine();
+ return ProgramErrorCodes.ConnectionFailed;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.Out.WriteLine("Fatal error initializing DevicePortal: " + ex.Message);
+ Console.Out.WriteLine();
+
+ return ProgramErrorCodes.UnexpectedError;
+ }
+ }
+ else
+ {
+ // Need to create a dummy DevicePortal so can safely call into area-specific operation processor
+ // The processor can then output operation specific help info
+ portal = new DevicePortal(new DefaultDevicePortalConnection("http://0.0.0.0", "", ""));
+ }
+
+ var operationResult = ProgramErrorCodes.Success;
+ switch (parameters.Area)
+ {
+ case OpperationArea.Application:
+ if (!AppOperation.TryExecuteApplicationOperation(portal, parameters))
+ {
+ operationResult = ProgramErrorCodes.OperationFailed;
+ }
+ break;
+
+ default:
+ // This case should have already been handled by a parameter check above
+ operationResult = ProgramErrorCodes.InvalidParameters;
+ break;
+ }
+
+ // If successful but help switch was set return a different resultCode
+ if (operationResult == ProgramErrorCodes.Success && helpFlag)
+ {
+ operationResult = ProgramErrorCodes.SuccessHelp;
+ }
+
+ // If a debugger is attached, don't close but instead loop here until
+ // closed.
+ while (System.Diagnostics.Debugger.IsAttached)
+ {
+ System.Threading.Thread.Sleep(0);
+ }
+
+ return operationResult;
+ }
+
+ private static ProgramErrorCodes ParseParametersAndPerformBasicValidation(string[] args, out ParameterHelper parameters, out Uri targetDevice, out bool verbose, out bool helpFlag)
+ {
+ parameters = new ParameterHelper();
+ targetDevice = null;
+ verbose = false;
+ helpFlag = false;
+
+ try
+ {
+ parameters.ParseCommandLine(args);
+ }
+ catch (Exception ex)
+ {
+ Console.Out.WriteLine("Fatal error parsing command arguments: " + ex.Message);
+ Console.Out.WriteLine();
+
+ return ProgramErrorCodes.UnexpectedError;
+ }
+
+ verbose = parameters.HasFlag(ParameterHelper.VerboseFlag);
+ helpFlag = parameters.HasFlag(ParameterHelper.HelpFlag);
+
+ if (parameters.Area == OpperationArea.None)
+ {
+ if (helpFlag)
+ {
+ Console.WriteLine(GeneralUsageMessage);
+ Console.WriteLine();
+ }
+ else
+ {
+ Console.Out.WriteLine("Invalid parameters: Must specify a valid operation area as the first parameter");
+ Console.Out.WriteLine();
+ }
+
+ return ProgramErrorCodes.InvalidParameters;
+ }
+
+ if (!helpFlag)
+ {
+ string address = parameters.GetParameterValue(ParameterHelper.DeviceIpAddress);
+ string invalidReason = null;
+
+ if (String.IsNullOrWhiteSpace(address))
+ {
+ invalidReason = "Must specify IP address of remote Windows Device Portal with /ip switch";
+ }
+ else if (!Uri.TryCreate(address, UriKind.Absolute, out targetDevice))
+ {
+ invalidReason = "isn't a proper URI";
+ }
+ else if (targetDevice.Scheme != "http" && targetDevice.Scheme != "https")
+ {
+ invalidReason = "must specify http or https scheme";
+ }
+ else if (targetDevice.IsDefaultPort)
+ {
+ invalidReason = "doesn't specify a WDP port number";
+ }
+
+ if (invalidReason != null)
+ {
+ Console.Out.WriteLine("Invalid parameters: IP address '" + address + "'; " + invalidReason);
+ Console.Out.WriteLine("IP address must be in the following format: http(s)://:");
+ Console.Out.WriteLine("The correct address string can be found under 'Developer' settings on the host device");
+ Console.Out.WriteLine();
+ return ProgramErrorCodes.InvalidParameters;
+ }
+ }
+
+ if (!helpFlag && parameters.HasFlag(ParameterHelper.StdinCredentials))
+ {
+ if (parameters.HasParameter(ParameterHelper.WdpUser) || parameters.HasParameter(ParameterHelper.WdpPassword))
+ {
+ Console.Out.WriteLine("Invalid parameters: Cannot pass WDP credentials on command line (/user /pwd) when using /stdincred switch; use one method or the other");
+ Console.Out.WriteLine();
+ return ProgramErrorCodes.InvalidParameters;
+ }
+ }
+
+ return ProgramErrorCodes.Success;
+ }
+
+ private static bool TryReadCredentialsFromStdin(out string userName, out string password)
+ {
+ userName = String.Empty;
+ password = String.Empty;
+
+ // We expect username/password (base64 encoded) to be passed "piped" in from a calling process
+ // So there's no prompt and we'll fail if don't read anything after a few seconds
+ {
+ var readTask = Console.In.ReadLineAsync();
+ if (!readTask.Wait(5000))
+ return false;
+
+ userName = ParameterHelper.DecodeBase64(readTask.Result);
+ }
+
+ {
+ var readTask = Console.In.ReadLineAsync();
+ if (!readTask.Wait(5000))
+ return false;
+
+ password = ParameterHelper.DecodeBase64(readTask.Result);
+ }
+
+ return true;
+ }
+
+ private static bool TryOpenDevicePortalConnection(Uri targetDevice, ParameterHelper parameters, out DevicePortal portal)
+ {
+ string userName = parameters.GetParameterValue(ParameterHelper.WdpUser);
+ string password = parameters.GetParameterValue(ParameterHelper.WdpPassword);
+
+ bool success = true;
+ portal = new DevicePortal(new DefaultDevicePortalConnection(targetDevice.ToString(), userName, password));
+ try
+ {
+ // We need to handle this event otherwise remote connection will be rejected if
+ // device isn't trusted by local PC
+ portal.UnvalidatedCert += DoCertValidation;
+
+ var connectTask = portal.ConnectAsync(updateConnection: false);
+ connectTask.Wait();
+
+ if (portal.ConnectionHttpStatusCode != System.Net.HttpStatusCode.OK)
+ {
+ if (portal.ConnectionHttpStatusCode == System.Net.HttpStatusCode.Unauthorized)
+ {
+ throw new System.UnauthorizedAccessException("Connection rejected due to missing/incorrect credentials; specify valid credentials with /user and /pwd switches");
+ }
+ else if (!string.IsNullOrEmpty(portal.ConnectionFailedDescription))
+ {
+ throw new System.OperationCanceledException(string.Format("WDP connection failed (HTTP {0}) : {1}", (int)portal.ConnectionHttpStatusCode, portal.ConnectionFailedDescription));
+ }
+ else
+ {
+ throw new System.OperationCanceledException(string.Format("WDP connection failed (HTTP {0}) : no additional information", (int)portal.ConnectionHttpStatusCode));
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ bool verbose = parameters.HasFlag(ParameterHelper.VerboseFlag);
+ Console.Out.WriteLine("Failed to open DevicePortal connection to '" + portal.Address + "'\n" + (verbose ? ex.ToString() : ex.Message));
+
+ success = false;
+ }
+
+ return success;
+ }
+
+ private static bool DoCertValidation(DevicePortal sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
+ {
+ // We're not validating the remote host
+ return true;
+ }
+ }
+}
diff --git a/WindowsDevicePortalWrapper/AppDeployment.cs b/WindowsDevicePortalWrapper/AppDeployment.cs
new file mode 100644
index 0000000..3b47505
--- /dev/null
+++ b/WindowsDevicePortalWrapper/AppDeployment.cs
@@ -0,0 +1,30 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// MOCK implementation of App Deployment methods.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API for getting installation status.
+ ///
+ /// The status
+ public async Task GetInstallStatusAsync()
+ {
+ ApplicationInstallStatus status = ApplicationInstallStatus.Completed;
+
+ return await Task.FromResult(status);
+ }
+ }
+}
diff --git a/WindowsDevicePortalWrapper/ApplicationManager.cs b/WindowsDevicePortalWrapper/ApplicationManager.cs
new file mode 100644
index 0000000..460e829
--- /dev/null
+++ b/WindowsDevicePortalWrapper/ApplicationManager.cs
@@ -0,0 +1,160 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for Application Management.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// IoT device application list API.
+ ///
+ public static readonly string AppsListApi = "api/iot/appx/default";
+
+ ///
+ /// IoT device headless application list API.
+ ///
+ public static readonly string HeadlessAppsListApi = "api/iot/appx/listHeadlessApps";
+
+ ///
+ /// IoT device headless startup application API.
+ ///
+ public static readonly string HeadlessStartupAppApi = "api/iot/appx/startupHeadlessApp";
+
+ ///
+ /// IoT device package activation API.
+ ///
+ public static readonly string ActivatePackageApi = "api/iot/appx/app";
+
+ ///
+ /// Gets List of apps.
+ ///
+ /// Object containing the list of applications.
+ public async Task GetAppsListInfoAsync()
+ {
+ return await this.GetAsync(AppsListApi);
+ }
+
+ ///
+ /// Gets list of headless apps.
+ ///
+ /// Object containing the list of headless applications.
+ public async Task GetHeadlessAppsListInfoAsync()
+ {
+ return await this.GetAsync(HeadlessAppsListApi);
+ }
+
+ ///
+ /// Sets selected app as the startup app.
+ ///
+ /// App Id.
+ /// Task tracking completion of the REST call.
+ public async Task UpdateStartupAppAsync(string appId)
+ {
+ await this.PostAsync(
+ AppsListApi,
+ string.Format("appid={0}", Utilities.Hex64Encode(appId)));
+ }
+
+ ///
+ /// Sets the selected app as the headless startup app.
+ ///
+ /// App Id.
+ /// Task tracking completion of the REST call.
+ public async Task UpdateHeadlessStartupAppAsync(string appId)
+ {
+ await this.PostAsync(
+ HeadlessStartupAppApi,
+ string.Format("appid={0}", Utilities.Hex64Encode(appId)));
+ }
+
+ ///
+ /// Removes the selected app from the headless startup app list.
+ ///
+ /// App Id.
+ /// Task tracking completion of the REST call.
+ public async Task RemoveHeadlessStartupAppAsync(string appId)
+ {
+ await this.DeleteAsync(
+ HeadlessStartupAppApi,
+ string.Format("appid={0}", Utilities.Hex64Encode(appId)));
+ }
+
+ ///
+ /// Activiates the selected app package.
+ ///
+ /// App Id.
+ /// Task tracking completion of the REST call.
+ public async Task ActivatePackageAsync(string appId)
+ {
+ await this.PostAsync(
+ ActivatePackageApi,
+ string.Format("appid={0}", Utilities.Hex64Encode(appId)));
+ }
+ #region Data contract
+
+ ///
+ /// Application list info.
+ ///
+ [DataContract]
+ public class AppsListInfo
+ {
+ ///
+ /// Gets the default application
+ ///
+ [DataMember(Name = "DefaultApp")]
+ public string DefaultApp { get; private set; }
+
+ ///
+ /// Gets the application packages
+ ///
+ [DataMember(Name = "AppPackages")]
+ public List AppPackages { get; private set; }
+ }
+
+ ///
+ /// Application package.
+ ///
+ [DataContract]
+ public class AppPackage
+ {
+ ///
+ /// Gets a value indicating whether the app is the startup app
+ ///
+ [DataMember(Name = "IsStartup")]
+ public bool IsStartup { get; private set; }
+
+ ///
+ /// Gets the complate package name
+ ///
+ [DataMember(Name = "PackageFullName")]
+ public string PackageFullName { get; private set; }
+ }
+
+ ///
+ /// Headless app list information.
+ ///
+ [DataContract]
+ public class HeadlessAppsListInfo
+ {
+ ///
+ /// Gets the list of headless application packages
+ ///
+ [DataMember(Name = "AppPackages")]
+ public List AppPackages { get; private set; }
+ }
+
+ #endregion // Data contract
+ }
+}
diff --git a/WindowsDevicePortalWrapper/CertificateHandling.cs b/WindowsDevicePortalWrapper/CertificateHandling.cs
new file mode 100644
index 0000000..831e523
--- /dev/null
+++ b/WindowsDevicePortalWrapper/CertificateHandling.cs
@@ -0,0 +1,125 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// .net 4.x implementation of device certificate handling methods.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// A manually provided certificate for trust validation.
+ ///
+ private X509Certificate2 manualCertificate = null;
+
+ ///
+ /// Gets or sets handler for untrusted certificate handling
+ ///
+ public event UnvalidatedCertEventHandler UnvalidatedCert;
+
+ ///
+ /// Gets the root certificate from the device.
+ ///
+ /// The device certificate.
+ public async Task GetRootDeviceCertificateAsync()
+ {
+ X509Certificate2 certificate = null;
+
+ Uri uri = Utilities.BuildEndpoint(this.deviceConnection.Connection, RootCertificateEndpoint);
+
+ using (Stream stream = await this.GetAsync(uri))
+ {
+ using (BinaryReader reader = new BinaryReader(stream))
+ {
+ byte[] certData = reader.ReadBytes((int)stream.Length);
+ certificate = new X509Certificate2(certData);
+ }
+ }
+
+ return certificate;
+ }
+
+ ///
+ /// Sets the manual certificate.
+ ///
+ /// Manual certificate
+ private void SetManualCertificate(X509Certificate2 cert)
+ {
+ this.manualCertificate = cert;
+ }
+
+ ///
+ /// Validate the server certificate
+ ///
+ /// The sender object
+ /// The server's certificate
+ /// The cert chain
+ /// Policy Errors
+ /// whether the cert passes validation
+ private bool ServerCertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
+ {
+ if (this.manualCertificate != null)
+ {
+ chain.ChainPolicy.ExtraStore.Add(this.manualCertificate);
+ }
+
+ X509Certificate2 certv2 = new X509Certificate2(certificate);
+ bool isValid = chain.Build(certv2);
+
+ // If chain validation failed but we have a manual cert, we can still
+ // check the chain to see if the server cert chains up to our manual cert
+ // (or matches it) in which case this is valid.
+ if (!isValid && this.manualCertificate != null)
+ {
+ foreach (X509ChainElement element in chain.ChainElements)
+ {
+ foreach (X509ChainStatus status in element.ChainElementStatus)
+ {
+ // Check if this is a failure that should cause the chain to be rejected
+ if (status.Status != X509ChainStatusFlags.NoError &&
+ status.Status != X509ChainStatusFlags.UntrustedRoot &&
+ status.Status != X509ChainStatusFlags.RevocationStatusUnknown)
+ {
+ return false;
+ }
+ }
+
+ // This cert chained to our provided cert. Continue walking
+ // the chain to ensure we don't hit a failure that would
+ // cause our chain to be rejected.
+ if (element.Certificate.Issuer == this.manualCertificate.Issuer &&
+ element.Certificate.Thumbprint == this.manualCertificate.Thumbprint)
+ {
+ isValid = true;
+ break;
+ }
+ }
+ }
+
+ // If this still appears invalid, we give the app a chance via a handler
+ // to override the trust decision.
+ if (!isValid)
+ {
+ bool? overridenIsValid = this.UnvalidatedCert?.Invoke(this, certificate, chain, sslPolicyErrors);
+
+ if (overridenIsValid != null && overridenIsValid == true)
+ {
+ isValid = true;
+ }
+ }
+
+ return isValid;
+ }
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/AppCrashDumpCollection.cs b/WindowsDevicePortalWrapper/Core/AppCrashDumpCollection.cs
new file mode 100644
index 0000000..643efed
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/AppCrashDumpCollection.cs
@@ -0,0 +1,223 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for app crash dump collection methods.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API to retrieve list of the available crash dumps (for sideloaded applications).
+ ///
+ public static readonly string AvailableCrashDumpsApi = "api/debug/dump/usermode/dumps";
+
+ ///
+ /// API to download or delete a crash dump file (for a sideloaded application).
+ ///
+ public static readonly string CrashDumpFileApi = "api/debug/dump/usermode/crashdump";
+
+ ///
+ /// API to control the crash dump settings for a sideloaded application.
+ ///
+ public static readonly string CrashDumpSettingsApi = "api/debug/dump/usermode/crashcontrol";
+
+ ///
+ /// Get a list of app crash dumps on the device.
+ ///
+ /// List of AppCrashDump objects, which represent crashdumps on the device.
+ public async Task> GetAppCrashDumpListAsync()
+ {
+ AppCrashDumpList cdl = await this.GetAsync(AvailableCrashDumpsApi);
+ return cdl.CrashDumps;
+ }
+
+ ///
+ /// Download a sideloaded app's crash dump.
+ ///
+ /// The AppCrashDump to download
+ /// Stream of the crash dump
+ public async Task GetAppCrashDumpAsync(AppCrashDump crashdump)
+ {
+ string queryString = CrashDumpFileApi + string.Format("?packageFullName={0}&fileName={1}", crashdump.PackageFullName, crashdump.Filename);
+ Uri uri = Utilities.BuildEndpoint(
+ this.deviceConnection.Connection,
+ queryString);
+
+ return await this.GetAsync(uri);
+ }
+
+ ///
+ /// Delete an app crash dump stored on the device.
+ ///
+ /// The crashdump to be deleted
+ /// Task tracking completion of the request.
+ public async Task DeleteAppCrashDumpAsync(AppCrashDump crashdump)
+ {
+ await this.DeleteAsync(
+ CrashDumpFileApi,
+ string.Format("packageFullName={0}&fileName={1}", crashdump.PackageFullName, crashdump.Filename));
+ }
+
+ ///
+ /// Get the crash settings for a sideloaded app.
+ ///
+ /// The app to get settings for
+ /// The crash settings for the app
+ public async Task GetAppCrashDumpSettingsAsync(AppPackage app)
+ {
+ return await this.GetAppCrashDumpSettingsAsync(app.PackageFullName);
+ }
+
+ ///
+ /// Get the crash settings for a sideloaded app.
+ ///
+ /// The app to get settings for
+ /// The crash settings for the app
+ public async Task GetAppCrashDumpSettingsAsync(string packageFullname)
+ {
+ return await this.GetAsync(
+ CrashDumpSettingsApi,
+ string.Format("packageFullName={0}", packageFullname));
+ }
+
+ ///
+ /// Set the crash settings for a sideloaded app.
+ ///
+ /// The app to set crash settings for.
+ /// Whether to enable or disable crash collection for the app.
+ /// Task tracking completion of the request.
+ public async Task SetAppCrashDumpSettingsAsync(AppPackage app, bool enable = true)
+ {
+ string pfn = app.PackageFullName;
+ await this.SetAppCrashDumpSettingsAsync(pfn, enable);
+ }
+
+ ///
+ /// Set the crash settings for a sideloaded app.
+ ///
+ /// The app to set crash settings for.
+ /// Whether to enable or disable crash collection for the app.
+ /// Task tracking completion of the request.
+ public async Task SetAppCrashDumpSettingsAsync(string packageFullName, bool enable = true)
+ {
+ if (enable)
+ {
+ await this.PostAsync(
+ CrashDumpSettingsApi,
+ string.Format("packageFullName={0}", packageFullName));
+ }
+ else
+ {
+ await this.DeleteAsync(
+ CrashDumpSettingsApi,
+ string.Format("packageFullName={0}", packageFullName));
+ }
+ }
+
+ #region Data contract
+
+ ///
+ /// Per-app crash dump settings.
+ ///
+ [DataContract]
+ public class AppCrashDumpSettings
+ {
+ ///
+ /// Gets a value indicating whether crash dumps are enabled for the app
+ ///
+ [DataMember(Name = "CrashDumpEnabled")]
+ public bool CrashDumpEnabled
+ {
+ get;
+ private set;
+ }
+ }
+
+ ///
+ /// Represents a crash dump collected from a sideloaded app.
+ ///
+ [DataContract]
+ public class AppCrashDump
+ {
+ ///
+ /// Gets the timestamp of the crash as a string.
+ ///
+ [DataMember(Name = "FileDate")]
+ public string FileDateAsString
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the timestamp of the crash.
+ ///
+ public DateTime FileDate
+ {
+ get
+ {
+ return DateTime.Parse(this.FileDateAsString);
+ }
+ }
+
+ ///
+ /// Gets the filename of the crash file.
+ ///
+ [DataMember(Name = "FileName")]
+ public string Filename
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the size of the crash dump, in bytes
+ ///
+ [DataMember(Name = "FileSize")]
+ public uint FileSizeInBytes
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the package full name of the app that crashed.
+ ///
+ [DataMember(Name = "PackageFullName")]
+ public string PackageFullName
+ {
+ get;
+ private set;
+ }
+ }
+
+ ///
+ /// A list of crash dumps. Internal usage only.
+ ///
+ [DataContract]
+ private class AppCrashDumpList
+ {
+ ///
+ /// Gets a list of crash dumps on the device.
+ ///
+ [DataMember(Name = "CrashDumps")]
+ public List CrashDumps
+ {
+ get;
+ private set;
+ }
+ }
+ #endregion Data contract
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/AppDeployment.cs b/WindowsDevicePortalWrapper/Core/AppDeployment.cs
new file mode 100644
index 0000000..47d14c4
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/AppDeployment.cs
@@ -0,0 +1,411 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+#if !WINDOWS_UWP
+using System.Net;
+using System.Net.Http;
+#endif // !WINDOWS_UWP
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+#if WINDOWS_UWP
+using Windows.Foundation;
+using Windows.Security.Credentials;
+using Windows.Storage.Streams;
+using Windows.Web.Http;
+using Windows.Web.Http.Filters;
+using Windows.Web.Http.Headers;
+#endif
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for App Deployment methods.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API to retrieve list of installed packages.
+ ///
+ public static readonly string InstalledPackagesApi = "api/app/packagemanager/packages";
+
+ ///
+ /// Install state API.
+ ///
+ public static readonly string InstallStateApi = "api/app/packagemanager/state";
+
+ ///
+ /// API for package management.
+ ///
+ public static readonly string PackageManagerApi = "api/app/packagemanager/package";
+
+ ///
+ /// App Install Status handler.
+ ///
+ public event ApplicationInstallStatusEventHandler AppInstallStatus;
+
+ ///
+ /// Gets the collection of applications installed on the device.
+ ///
+ /// AppPackages object containing the list of installed application packages.
+ public async Task GetInstalledAppPackagesAsync()
+ {
+ return await this.GetAsync(InstalledPackagesApi);
+ }
+
+ ///
+ /// Installs an application
+ ///
+ /// Friendly name (ex: Hello World) of the application. If this parameter is not provided, the name of the package is assumed to be the app name.
+ /// Full name of the application package file.
+ /// List containing the full names of any required dependency files.
+ /// Full name of the optional certificate file.
+ /// How frequently we should check the installation state.
+ /// Operation timeout.
+ /// Indicate whether or not the previous app version should be uninstalled prior to installing.
+ /// InstallApplication sends ApplicationInstallStatus events to indicate the current progress in the installation process.
+ /// Some applications may opt to not register for the AppInstallStatus event and await on InstallApplication.
+ /// Task for tracking completion of install initialization.
+ public async Task InstallApplicationAsync(
+ string appName,
+ string packageFileName,
+ List dependencyFileNames,
+ string certificateFileName = null,
+ short stateCheckIntervalMs = 500,
+ short timeoutInMinutes = 15,
+ bool uninstallPreviousVersion = true)
+ {
+ string installPhaseDescription = string.Empty;
+
+ try
+ {
+ FileInfo packageFile = new FileInfo(packageFileName);
+
+ // If appName was not provided, use the package file name
+ if (string.IsNullOrEmpty(appName))
+ {
+ appName = packageFile.Name;
+ }
+
+ // Uninstall the application's previous version, if one exists.
+ if (uninstallPreviousVersion)
+ {
+ installPhaseDescription = string.Format("Uninstalling any previous version of {0}", appName);
+ this.SendAppInstallStatus(
+ ApplicationInstallStatus.InProgress,
+ ApplicationInstallPhase.UninstallingPreviousVersion,
+ installPhaseDescription);
+ AppPackages installedApps = await this.GetInstalledAppPackagesAsync();
+ foreach (PackageInfo package in installedApps.Packages)
+ {
+ if (package.Name == appName)
+ {
+ await this.UninstallApplicationAsync(package.FullName);
+ break;
+ }
+ }
+ }
+
+ // Create the API endpoint and generate a unique boundary string.
+ Uri uri;
+ string boundaryString;
+ this.CreateAppInstallEndpointAndBoundaryString(
+ packageFile.Name,
+ out uri,
+ out boundaryString);
+
+ installPhaseDescription = string.Format("Copying: {0}", packageFile.Name);
+ this.SendAppInstallStatus(
+ ApplicationInstallStatus.InProgress,
+ ApplicationInstallPhase.CopyingFile,
+ installPhaseDescription);
+
+ var content = new HttpMultipartFileContent();
+ content.Add(packageFile.FullName);
+ content.AddRange(dependencyFileNames);
+ content.Add(certificateFileName);
+ await this.PostAsync(uri, content);
+
+ // Poll the status until complete.
+ ApplicationInstallStatus status = ApplicationInstallStatus.InProgress;
+ do
+ {
+ installPhaseDescription = string.Format("Installing {0}", appName);
+ this.SendAppInstallStatus(
+ ApplicationInstallStatus.InProgress,
+ ApplicationInstallPhase.Installing,
+ installPhaseDescription);
+
+ await Task.Delay(TimeSpan.FromMilliseconds(stateCheckIntervalMs));
+
+ status = await this.GetInstallStatusAsync().ConfigureAwait(false);
+ }
+ while (status == ApplicationInstallStatus.InProgress);
+
+ installPhaseDescription = string.Format("{0} installed successfully", appName);
+ this.SendAppInstallStatus(
+ ApplicationInstallStatus.Completed,
+ ApplicationInstallPhase.Idle,
+ installPhaseDescription);
+ }
+ catch (Exception e)
+ {
+ DevicePortalException dpe = e as DevicePortalException;
+
+ if (dpe != null)
+ {
+ this.SendAppInstallStatus(
+ ApplicationInstallStatus.Failed,
+ ApplicationInstallPhase.Idle,
+ string.Format("Failed to install {0}: {1}", appName, dpe.Reason));
+ }
+ else
+ {
+ this.SendAppInstallStatus(
+ ApplicationInstallStatus.Failed,
+ ApplicationInstallPhase.Idle,
+ string.Format("Failed to install {0}: {1}", appName, installPhaseDescription));
+ }
+
+ throw;
+ }
+ }
+
+ ///
+ /// Uninstalls the specified application.
+ ///
+ /// The name of the application package to uninstall.
+ /// Task tracking the uninstall operation.
+ public async Task UninstallApplicationAsync(string packageName)
+ {
+ await this.DeleteAsync(
+ PackageManagerApi,
+ //// NOTE: When uninstalling an app package, the package name is not Hex64 encoded.
+ string.Format("package={0}", packageName));
+ }
+
+ ///
+ /// Builds the application installation Uri and generates a unique boundary string for the multipart form data.
+ ///
+ /// The name of the application package.
+ /// The endpoint for the install request.
+ /// Unique string used to separate the parts of the multipart form data.
+ private void CreateAppInstallEndpointAndBoundaryString(
+ string packageName,
+ out Uri uri,
+ out string boundaryString)
+ {
+ uri = Utilities.BuildEndpoint(
+ this.deviceConnection.Connection,
+ PackageManagerApi,
+ string.Format("package={0}", packageName));
+
+ boundaryString = Guid.NewGuid().ToString();
+ }
+
+ ///
+ /// Sends application install status.
+ ///
+ /// Status of the installation.
+ /// Current installation phase (ex: Uninstalling previous version)
+ /// Optional error message describing the install status.
+ private void SendAppInstallStatus(
+ ApplicationInstallStatus status,
+ ApplicationInstallPhase phase,
+ string message = "")
+ {
+ this.AppInstallStatus?.Invoke(
+ this,
+ new ApplicationInstallStatusEventArgs(status, phase, message));
+ }
+
+ #region Data contract
+ ///
+ /// Object representing a list of Application Packages
+ ///
+ [DataContract]
+ public class AppPackages
+ {
+ ///
+ /// Gets a list of the packages
+ ///
+ [DataMember(Name = "InstalledPackages")]
+ public List Packages { get; private set; }
+
+ ///
+ /// Presents a user readable representation of a list of AppPackages
+ ///
+ /// User readable list of AppPackages.
+ public override string ToString()
+ {
+ string output = "Packages:\n";
+ foreach (PackageInfo package in this.Packages)
+ {
+ output += package;
+ }
+
+ return output;
+ }
+ }
+
+ ///
+ /// Object representing the install state
+ ///
+ [DataContract]
+ public class InstallState
+ {
+ ///
+ /// Gets install state code
+ ///
+ [DataMember(Name = "Code")]
+ public int Code { get; private set; }
+
+ ///
+ /// Gets message text
+ ///
+ [DataMember(Name = "CodeText")]
+ public string CodeText { get; private set; }
+
+ ///
+ /// Gets reason for state
+ ///
+ [DataMember(Name = "Reason")]
+ public string Reason { get; private set; }
+
+ ///
+ /// Gets a value indicating whether this was successful
+ ///
+ [DataMember(Name = "Success")]
+ public bool WasSuccessful { get; private set; }
+ }
+
+ ///
+ /// object representing the package information
+ ///
+ [DataContract]
+ public class PackageInfo
+ {
+ ///
+ /// Gets package name
+ ///
+ [DataMember(Name = "Name")]
+ public string Name { get; private set; }
+
+ ///
+ /// Gets package family name
+ ///
+ [DataMember(Name = "PackageFamilyName")]
+ public string FamilyName { get; private set; }
+
+ ///
+ /// Gets package full name
+ ///
+ [DataMember(Name = "PackageFullName")]
+ public string FullName { get; private set; }
+
+ ///
+ /// Gets package relative Id
+ ///
+ [DataMember(Name = "PackageRelativeId")]
+ public string AppId { get; private set; }
+
+ ///
+ /// Gets package publisher
+ ///
+ [DataMember(Name = "Publisher")]
+ public string Publisher { get; private set; }
+
+ ///
+ /// Gets package version
+ ///
+ [DataMember(Name = "Version")]
+ public PackageVersion Version { get; private set; }
+
+ ///
+ /// Gets package origin, a measure of how the app was installed.
+ /// PackageOrigin_Unknown = 0,
+ /// PackageOrigin_Unsigned = 1,
+ /// PackageOrigin_Inbox = 2,
+ /// PackageOrigin_Store = 3,
+ /// PackageOrigin_DeveloperUnsigned = 4,
+ /// PackageOrigin_DeveloperSigned = 5,
+ /// PackageOrigin_LineOfBusiness = 6
+ ///
+ [DataMember(Name = "PackageOrigin")]
+ public int PackageOrigin { get; private set; }
+
+ ///
+ /// Helper method to determine if the app was sideloaded and therefore can be used with e.g. GetFolderContentsAsync
+ ///
+ /// True if the package is sideloaded.
+ public bool IsSideloaded()
+ {
+ return this.PackageOrigin == 4 || this.PackageOrigin == 5;
+ }
+
+ ///
+ /// Get a string representation of the package
+ ///
+ /// String representation
+ public override string ToString()
+ {
+ return string.Format("\t{0}\n\t\t{1}\n", this.FullName, this.AppId);
+ }
+ }
+
+ ///
+ /// Object representing a package version
+ ///
+ [DataContract]
+ public class PackageVersion
+ {
+ ///
+ /// Gets version build
+ ///
+ [DataMember(Name = "Build")]
+ public int Build { get; private set; }
+
+ ///
+ /// Gets package Major number
+ ///
+ [DataMember(Name = "Major")]
+ public int Major { get; private set; }
+
+ ///
+ /// Gets package minor number
+ ///
+ [DataMember(Name = "Minor")]
+ public int Minor { get; private set; }
+
+ ///
+ /// Gets package revision
+ ///
+ [DataMember(Name = "Revision")]
+ public int Revision { get; private set; }
+
+ ///
+ /// Gets package version
+ ///
+ public Version Version
+ {
+ get { return new Version(this.Major, this.Minor, this.Build, this.Revision); }
+ }
+
+ ///
+ /// Get a string representation of a version
+ ///
+ /// String representation
+ public override string ToString()
+ {
+ return Version.ToString();
+ }
+ }
+ #endregion // Data contract
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/AppFileExplorer.cs b/WindowsDevicePortalWrapper/Core/AppFileExplorer.cs
new file mode 100644
index 0000000..24caa2f
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/AppFileExplorer.cs
@@ -0,0 +1,351 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for App File explorer methods
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API to upload, download or delete a file in a folder.
+ ///
+ public static readonly string GetFileApi = "api/filesystem/apps/file";
+
+ ///
+ /// API to rename a file in a folder.
+ ///
+ public static readonly string RenameFileApi = "api/filesystem/apps/rename";
+
+ ///
+ /// API to retrieve the list of files in a folder.
+ ///
+ public static readonly string GetFilesApi = "api/filesystem/apps/files";
+
+ ///
+ /// API to retrieve the list of accessible top-level folders.
+ ///
+ public static readonly string KnownFoldersApi = "api/filesystem/apps/knownfolders";
+
+ ///
+ /// Gets a list of Known Folders on the device.
+ ///
+ /// List of known folders available on this device.
+ public async Task GetKnownFoldersAsync()
+ {
+ return await this.GetAsync(KnownFoldersApi);
+ }
+
+ ///
+ /// Gets a list of files in a Known Folder (e.g. LocalAppData).
+ ///
+ /// The known folder id for the root of the path.
+ /// An optional subpath to the folder.
+ /// The package full name if using LocalAppData.
+ /// Contents of the requested folder.
+ public async Task GetFolderContentsAsync(
+ string knownFolderId,
+ string subPath = null,
+ string packageFullName = null)
+ {
+ Dictionary payload = this.BuildCommonFilePayload(knownFolderId, subPath, packageFullName);
+
+ return await this.GetAsync(GetFilesApi, Utilities.BuildQueryString(payload));
+ }
+
+ ///
+ /// Gets a file from LocalAppData or another Known Folder on the device.
+ ///
+ /// The known folder id for the root of the path.
+ /// The name of the file we are downloading.
+ /// An optional subpath to the folder.
+ /// The package full name if using LocalAppData.
+ /// Stream to the downloaded file.
+ public async Task GetFileAsync(
+ string knownFolderId,
+ string filename,
+ string subPath = null,
+ string packageFullName = null)
+ {
+ Dictionary payload = this.BuildCommonFilePayload(knownFolderId, subPath, packageFullName);
+
+ filename = WebUtility.UrlEncode(filename);
+ payload.Add("filename", filename);
+
+ Uri uri = Utilities.BuildEndpoint(
+ this.deviceConnection.Connection,
+ GetFileApi,
+ Utilities.BuildQueryString(payload));
+
+ return await this.GetAsync(uri);
+ }
+
+ ///
+ /// Uploads a file to a Known Folder (e.g. LocalAppData)
+ ///
+ /// The known folder id for the root of the path.
+ /// The path to the file we are uploading.
+ /// An optional subpath to the folder.
+ /// The package full name if using LocalAppData.
+ /// Task tracking completion of the upload request.
+ public async Task UploadFileAsync(
+ string knownFolderId,
+ string filepath,
+ string subPath = null,
+ string packageFullName = null)
+ {
+ Dictionary payload = this.BuildCommonFilePayload(knownFolderId, subPath, packageFullName);
+
+ List files = new List();
+ files.Add(filepath);
+
+ await this.PostAsync(GetFileApi, files, Utilities.BuildQueryString(payload));
+ }
+
+ ///
+ /// Deletes a file from a Known Folder.
+ ///
+ /// The known folder id for the root of the path.
+ /// The name of the file we are deleting.
+ /// An optional subpath to the folder.
+ /// The package full name if using LocalAppData.
+ /// Task tracking completion of the delete request.
+ public async Task DeleteFileAsync(
+ string knownFolderId,
+ string filename,
+ string subPath = null,
+ string packageFullName = null)
+ {
+ Dictionary payload = this.BuildCommonFilePayload(knownFolderId, subPath, packageFullName);
+ filename = WebUtility.UrlEncode(filename);
+ payload.Add("filename", filename);
+
+ await this.DeleteAsync(GetFileApi, Utilities.BuildQueryString(payload));
+ }
+
+ ///
+ /// Renames a file in a Known Folder.
+ ///
+ /// The known folder id for the root of the path.
+ /// The name of the file we are renaming.
+ /// The new name for this file.
+ /// An optional subpath to the folder.
+ /// The package full name if using LocalAppData.
+ /// Task tracking completion of the rename request.
+ public async Task RenameFileAsync(
+ string knownFolderId,
+ string filename,
+ string newFilename,
+ string subPath = null,
+ string packageFullName = null)
+ {
+ Dictionary payload = this.BuildCommonFilePayload(knownFolderId, subPath, packageFullName);
+
+ payload.Add("filename", filename);
+ payload.Add("newfilename", newFilename);
+
+ await this.PostAsync(RenameFileApi, Utilities.BuildQueryString(payload));
+ }
+
+ ///
+ /// Do some common parsing and validation of file explorer parameters.
+ ///
+ /// The known folder id.
+ /// The optional subpath for the folder.
+ /// The packagefullname if using LocalAppData.
+ /// Dictionary of param name to value.
+ private Dictionary BuildCommonFilePayload(string knownFolderId, string subPath, string packageFullName)
+ {
+ Dictionary payload = new Dictionary();
+
+ payload.Add("knownfolderid", knownFolderId);
+
+ if (!string.IsNullOrEmpty(subPath))
+ {
+ if (!subPath.StartsWith("/"))
+ {
+ subPath = subPath.Insert(0, "/");
+ }
+
+ payload.Add("path", subPath);
+ }
+
+ if (!string.IsNullOrEmpty(packageFullName))
+ {
+ payload.Add("packagefullname", packageFullName);
+ }
+ else if (string.Equals(knownFolderId, "LocalAppData", StringComparison.OrdinalIgnoreCase))
+ {
+ throw new Exception("LocalAppData requires a packageFullName be provided.");
+ }
+
+ return payload;
+ }
+
+ #region Data contract
+
+ ///
+ /// Known Folders object.
+ ///
+ [DataContract]
+ public class KnownFolders
+ {
+ ///
+ /// Gets the list of known folders.
+ ///
+ [DataMember(Name = "KnownFolders")]
+ public List Folders { get; private set; }
+
+ ///
+ /// Overridden ToString method providing a user readable
+ /// list of known folders.
+ ///
+ /// String representation of the object.
+ public override string ToString()
+ {
+ if (this.Folders == null)
+ {
+ return string.Empty;
+ }
+
+ string contents = string.Empty;
+ foreach (string folder in this.Folders)
+ {
+ contents += folder + '\n';
+ }
+
+ return contents;
+ }
+ }
+
+ ///
+ /// Folder contents object.
+ ///
+ [DataContract]
+ public class FolderContents
+ {
+ ///
+ /// Gets the list of folders and files in this folder.
+ ///
+ [DataMember(Name = "Items")]
+ public List Contents { get; private set; }
+
+ ///
+ /// Overridden ToString method providing a user readable
+ /// display of a folder's contents. Tries to match the formatting
+ /// of regular DIR commands.
+ ///
+ /// String representation of the object.
+ public override string ToString()
+ {
+ if (this.Contents == null)
+ {
+ return string.Empty;
+ }
+
+ string contents = string.Empty;
+ foreach (FileOrFolderInformation fileOrFolder in this.Contents)
+ {
+ contents += fileOrFolder.ToString();
+ }
+
+ return contents;
+ }
+ }
+
+ ///
+ /// Details about a folder or file.
+ ///
+ [DataContract]
+ public class FileOrFolderInformation
+ {
+ ///
+ /// Gets the current directory.
+ ///
+ [DataMember(Name = "CurrentDir")]
+ public string CurrentDir { get; private set; }
+
+ ///
+ /// Gets the current directory.
+ ///
+ [DataMember(Name = "DateCreated")]
+ public long DateCreated { get; private set; }
+
+ ///
+ /// Gets the Id.
+ ///
+ [DataMember(Name = "Id")]
+ public string Id { get; private set; }
+
+ ///
+ /// Gets the Name.
+ ///
+ [DataMember(Name = "Name")]
+ public string Name { get; private set; }
+
+ ///
+ /// Gets the SubPath (equivalent to CurrentDir for files).
+ ///
+ [DataMember(Name = "SubPath")]
+ public string SubPath { get; private set; }
+
+ ///
+ /// Gets the Type.
+ ///
+ [DataMember(Name = "Type")]
+ public int Type { get; private set; }
+
+ ///
+ /// Gets the size of the file (0 for folders).
+ ///
+ [DataMember(Name = "FileSize")]
+ public long SizeInBytes { get; private set; }
+
+ ///
+ /// Gets a value indicating whether the current item is a folder by checking for FILE_ATTRIBUTE_DIRECTORY
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/gg258117(v=vs.85).aspx
+ ///
+ public bool IsFolder
+ {
+ get
+ {
+ return (this.Type & 0x10) == 0x10;
+ }
+ }
+
+ ///
+ /// Overridden ToString method providing a user readable
+ /// display of a file or folder. Tries to match the formatting
+ /// of regular DIR commands.
+ ///
+ /// String representation of the object.
+ public override string ToString()
+ {
+ DateTime timestamp = DateTime.FromFileTime(this.DateCreated);
+
+ // Check if this is a folder.
+ if (this.IsFolder)
+ {
+ return string.Format("{0,-24:MM/dd/yyyy HH:mm tt}{1,-14} {2}\n", timestamp, "", this.Name);
+ }
+ else
+ {
+ return string.Format("{0,-24:MM/dd/yyyy HH:mm tt}{1,14:n0} {2}\n", timestamp, this.SizeInBytes, this.Name);
+ }
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/DeviceManager.cs b/WindowsDevicePortalWrapper/Core/DeviceManager.cs
new file mode 100644
index 0000000..d420b83
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/DeviceManager.cs
@@ -0,0 +1,103 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for device management methods.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API to retrieve list of installed devices.
+ ///
+ public static readonly string InstalledDevicesApi = "api/devicemanager/devices";
+
+ ///
+ /// Get a listing of installed devices
+ ///
+ /// List of installed devices
+ public async Task> GetDeviceListAsync()
+ {
+ DeviceList deviceList = await this.GetAsync(InstalledDevicesApi);
+ return deviceList.Devices;
+ }
+
+ #region Data contract
+ ///
+ /// Object representing a device entry
+ ///
+ [DataContract]
+ public class DeviceList
+ {
+ ///
+ /// Gets the Device Class
+ ///
+ [DataMember(Name = "DeviceList")]
+ public List Devices { get; private set; }
+ }
+
+ ///
+ /// Object representing a device entry
+ ///
+ [DataContract]
+ public class Device
+ {
+ ///
+ /// Gets the Device Class
+ ///
+ [DataMember(Name = "Class")]
+ public string Class { get; private set; }
+
+ ///
+ /// Gets the Device Description
+ ///
+ [DataMember(Name = "Description")]
+ public string Description { get; private set; }
+
+ ///
+ /// Gets the friendly (human-readable) name for the device. Usually more descriptive than Description. Does not apply to all Devices.
+ ///
+ [DataMember(Name = "FriendlyName")]
+ public string FriendlyName { get; private set; }
+
+ ///
+ /// Gets the Device ID
+ ///
+ [DataMember(Name = "ID")]
+ public string ID { get; private set; }
+
+ ///
+ /// Gets the Device Manufacturer
+ ///
+ [DataMember(Name = "Manufacturer")]
+ public string Manufacturer { get; private set; }
+
+ ///
+ /// Gets the Device ParentID, used for pairing
+ ///
+ [DataMember(Name = "ParentID")]
+ public string ParentID { get; private set; }
+
+ ///
+ /// Gets the Device Problem Code
+ ///
+ [DataMember(Name = "ProblemCode")]
+ public int ProblemCode { get; private set; }
+
+ ///
+ /// Gets the Device Status Code
+ ///
+ [DataMember(Name = "StatusCode")]
+ public int StatusCode { get; private set; }
+ }
+ #endregion
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/Dns-Sd.cs b/WindowsDevicePortalWrapper/Core/Dns-Sd.cs
new file mode 100644
index 0000000..82d4bb1
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/Dns-Sd.cs
@@ -0,0 +1,92 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for DNS methods
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API to add or delete a tag to the DNS-SD advertisement.
+ ///
+ public static readonly string TagApi = "api/dns-sd/tag";
+
+ ///
+ /// API to retrieve or delete the currently applied tags for the device.
+ ///
+ public static readonly string TagsApi = "api/dns-sd/tags";
+
+ ///
+ /// Gets a list of DNS-SD tags being broadcast by this device.
+ ///
+ /// Array of strings, each one an individual tag.
+ public async Task> GetServiceTagsAsync()
+ {
+ ServiceTags tags = await this.GetAsync(TagsApi);
+ return tags.Tags;
+ }
+
+ ///
+ /// Adds a tag to this device's DNS-SD broadcast.
+ ///
+ /// The tag to assign to the device.
+ /// Task tracking adding the tag.
+ public async Task AddServiceTagAsync(string tagValue)
+ {
+ await this.PostAsync(
+ TagApi,
+ string.Format("tagValue={0}", tagValue));
+ }
+
+ ///
+ /// Delete all tags from the device's DNS-SD broadcast.
+ ///
+ /// Task tracking deletion of all tags.
+ public async Task DeleteAllTagsAsync()
+ {
+ await this.DeleteAsync(TagsApi);
+ }
+
+ ///
+ /// Delete a specific tag from the device's DNS-SD broadcast.
+ ///
+ /// The tag to delete from the device broadcast.
+ /// Task tracking deletion of the tag.
+ public async Task DeleteTagAsync(string tagValue)
+ {
+ await this.DeleteAsync(
+ TagApi,
+ string.Format("tagValue={0}", tagValue));
+ }
+
+ #region Data contract
+
+ ///
+ /// Service tags object
+ ///
+ [DataContract]
+ public class ServiceTags
+ {
+ ///
+ /// Gets the DNS-SD service tags
+ ///
+ [DataMember(Name = "tags")]
+ public List Tags
+ {
+ get; private set;
+ }
+
+ #endregion Data contract
+ }
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/DumpCollection.cs b/WindowsDevicePortalWrapper/Core/DumpCollection.cs
new file mode 100644
index 0000000..641341e
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/DumpCollection.cs
@@ -0,0 +1,242 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for crash dump collection methods.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API to retrieve list of the available bugcheck minidumps.
+ ///
+ public static readonly string AvailableBugChecksApi = "api/debug/dump/kernel/dumplist";
+
+ ///
+ /// API to download a bugcheck minidump file.
+ ///
+ public static readonly string BugcheckFileApi = "api/debug/dump/kernel/dump";
+
+ ///
+ /// API to control bugcheck minidump settings.
+ ///
+ public static readonly string BugcheckSettingsApi = "api/debug/dump/kernel/crashcontrol";
+
+ ///
+ /// API to retrieve a live kernel dump.
+ ///
+ public static readonly string LiveKernelDumpApi = "api/debug/dump/livekernel";
+
+ ///
+ /// API to retrieve a live dump from a running user mode process.
+ ///
+ public static readonly string LiveProcessDumpApi = "api/debug/dump/usermode/live";
+
+ ///
+ /// Get a list of dumpfiles on the system.
+ ///
+ /// List of Dumpfiles. Use GetDumpFile to download the file.
+ public async Task> GetDumpfileListAsync()
+ {
+ DumpFileList dfl = await this.GetAsync(AvailableBugChecksApi);
+ return dfl.DumpFiles;
+ }
+
+ ///
+ /// Download a dumpfile from the system.
+ ///
+ /// Dumpfile object to be downloaded
+ /// Stream of the dump file
+ public async Task GetDumpFileAsync(Dumpfile crashdump)
+ {
+ string queryString = BugcheckFileApi + string.Format("?filename={0}", crashdump.Filename);
+ Uri uri = Utilities.BuildEndpoint(
+ this.deviceConnection.Connection,
+ queryString);
+
+ return await this.GetAsync(uri);
+ }
+
+ ///
+ /// Takes a live kernel dump of the device. This does not reboot the device.
+ ///
+ /// Stream of the kernel dump
+ public async Task GetLiveKernelDumpAsync()
+ {
+ Uri uri = Utilities.BuildEndpoint(
+ this.deviceConnection.Connection,
+ LiveKernelDumpApi);
+
+ return await this.GetAsync(uri);
+ }
+
+ ///
+ /// Take a live dump of a process on the system.
+ ///
+ /// PID of the process to get a live dump of.
+ /// Stream of the process live dump
+ public async Task GetLiveProcessDumpAsync(int pid)
+ {
+ string queryString = LiveProcessDumpApi + string.Format("?pid={0}", pid);
+ Uri uri = Utilities.BuildEndpoint(
+ this.deviceConnection.Connection,
+ queryString);
+
+ return await this.GetAsync(uri);
+ }
+
+ ///
+ /// Get information on how and when dump files are saved on the device.
+ ///
+ /// Dumpfile settings object. This can be edited and returned to the device to alter the settings.
+ public async Task GetDumpFileSettingsAsync()
+ {
+ return await this.GetAsync(BugcheckSettingsApi);
+ }
+
+ ///
+ /// Set how and when dump files are saved on the device.
+ ///
+ /// Altered DumpFileSettings object to set on the device
+ /// Task tracking completion of the request
+ public async Task SetDumpFileSettingsAsync(DumpFileSettings dfs)
+ {
+ string queryParams = string.Format(
+ "autoreboot={0}&overwrite={1}&dumptype={2}&maxdumpcount={3}",
+ dfs.AutoReboot ? "1" : "0",
+ dfs.Overwrite ? "1" : "0",
+ (int)dfs.DumpType,
+ dfs.MaxDumpCount);
+
+ await this.PostAsync(BugcheckSettingsApi, queryParams);
+ }
+
+ #region Data Contract
+
+ ///
+ /// DumpFileSettings object. Used to get and set how and when a dump is saved on the device.
+ ///
+ [DataContract]
+ public class DumpFileSettings
+ {
+ ///
+ /// The 3 types of dumps that can be saved on the device (or not saved at all).
+ ///
+ public enum DumpTypes
+ {
+ ///
+ /// Don't collect device crash dumps
+ ///
+ Disabled = 0,
+
+ ///
+ /// Collect all in use memory
+ ///
+ CompleteMemoryDump = 1,
+
+ ///
+ /// Don't include usermode memory in the dump
+ ///
+ KernelDump = 2,
+
+ ///
+ /// Limited kernel dump
+ ///
+ Minidump = 3
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the device should restart after a crash dump is taken.
+ ///
+ [DataMember(Name = "autoreboot")]
+ public bool AutoReboot { get; set; }
+
+ ///
+ /// Gets or sets the type of dump to be saved when a bugcheck occurs.
+ ///
+ [DataMember(Name = "dumptype")]
+ public DumpTypes DumpType { get; set; }
+
+ ///
+ /// Gets or sets the max number of dumps to be saved on the device.
+ ///
+ [DataMember(Name = "maxdumpcount")]
+ public int MaxDumpCount { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether new dumps should overwrite older dumps.
+ ///
+ [DataMember(Name = "overwrite")]
+ public bool Overwrite { get; set; }
+ }
+
+ ///
+ /// Gets a list of kernel dumps on the device.
+ ///
+ [DataContract]
+ public class DumpFileList
+ {
+ ///
+ /// Gets a list of kernel dumps on the device.
+ ///
+ [DataMember(Name = "DumpFiles")]
+ public List DumpFiles { get; private set; }
+ }
+
+ ///
+ /// Represents a dumpfile stored on the device.
+ ///
+ [DataContract]
+ public class Dumpfile
+ {
+ ///
+ /// Gets the timestamp of the crash as a string.
+ ///
+ [DataMember(Name = "FileDate")]
+ public string FileDateAsString
+ {
+ get; private set;
+ }
+
+ ///
+ /// Gets the timestamp of the crash.
+ ///
+ public DateTime FileDate
+ {
+ get
+ {
+ return DateTime.Parse(this.FileDateAsString);
+ }
+ }
+
+ ///
+ /// Gets the filename of the crash file.
+ ///
+ [DataMember(Name = "FileName")]
+ public string Filename
+ {
+ get; private set;
+ }
+
+ ///
+ /// Gets the size of the crash dump, in bytes
+ ///
+ [DataMember(Name = "FileSize")]
+ public uint FileSizeInBytes
+ {
+ get; private set;
+ }
+ }
+ #endregion Data Contract
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/Etw.cs b/WindowsDevicePortalWrapper/Core/Etw.cs
new file mode 100644
index 0000000..eef4e61
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/Etw.cs
@@ -0,0 +1,317 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for ETW methods
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API to create a realtime ETW session.
+ ///
+ public static readonly string RealtimeEtwSessionApi = "api/etw/session/realtime";
+
+ ///
+ /// API to get the list of registered custom ETW providers.
+ ///
+ public static readonly string CustomEtwProvidersApi = "api/etw/customproviders";
+
+ ///
+ /// API to get the list of registered ETW providers.
+ ///
+ public static readonly string EtwProvidersApi = "api/etw/providers";
+
+ ///
+ /// Web socket to get ETW events.
+ ///
+ private WebSocket realtimeEventsWebSocket;
+
+ ///
+ /// Determines if the event listener has been registered
+ ///
+ private bool isListeningForRealtimeEvents = false;
+
+ ///
+ /// The ETW event message received handler
+ ///
+ public event WebSocketMessageReceivedEventHandler RealtimeEventsMessageReceived;
+
+ ///
+ /// Returns the current set of registered custom ETW providers.
+ ///
+ /// EtwProviders object containing a list of providers, friendly name and GUID
+ public async Task GetCustomEtwProvidersAsync()
+ {
+ return await this.GetAsync(CustomEtwProvidersApi);
+ }
+
+ ///
+ /// Returns the current set of registered ETW providers.
+ ///
+ /// EtwProviders object containing a list of providers, friendly name and GUID
+ public async Task GetEtwProvidersAsync()
+ {
+ return await this.GetAsync(EtwProvidersApi);
+ }
+
+ ///
+ /// Toggles the listening state of a specific provider on the realtime events WebSocket.
+ ///
+ /// The provider to update the listening state of.
+ /// Determines whether the listening state should be enabled or disabled.
+ /// /// Verbosity level - 1 for least, 5 for most verbose.
+ /// Task for toggling the listening state of the specified provider.
+ public async Task ToggleEtwProviderAsync(EtwProviderInfo etwProvider, bool isEnabled = true, int level = 5)
+ {
+ await this.ToggleEtwProviderAsync(etwProvider.GUID, isEnabled, level);
+ }
+
+ ///
+ /// Toggles the listening state of a specific provider on the realtime events WebSocket.
+ ///
+ /// The GUID of the provider to update the listening state of.
+ /// Determines whether the listening state should be enabled or disabled.
+ /// Verbosity level - 1 for least, 5 for most verbose.
+ /// Task for toggling the listening state of the specified provider.
+ public async Task ToggleEtwProviderAsync(Guid etwProvider, bool isEnabled = true, int level = 5)
+ {
+ string action = isEnabled ? "enable" : "disable";
+ string message = $"provider {etwProvider} {action} {level}";
+
+ await this.InitializeRealtimeEventsWebSocketAsync();
+ await this.realtimeEventsWebSocket.SendMessageAsync(message);
+ }
+
+ ///
+ /// Starts listening for ETW events with it being returned via the RealtimeEventsMessageReceived event handler.
+ ///
+ /// Task for connecting to the WebSocket but not for listening to it.
+ public async Task StartListeningForEtwEventsAsync()
+ {
+ await this.InitializeRealtimeEventsWebSocketAsync();
+
+ if (!this.isListeningForRealtimeEvents)
+ {
+ this.isListeningForRealtimeEvents = true;
+ this.realtimeEventsWebSocket.WebSocketMessageReceived += this.EtwEventsReceivedHandler;
+ }
+
+ await this.realtimeEventsWebSocket.ReceiveMessagesAsync();
+ }
+
+ ///
+ /// Stop listening for ETW events.
+ ///
+ /// Task for stop listening for ETW events and disconnecting from the WebSocket.
+ public async Task StopListeningForEtwEventsAsync()
+ {
+ if (this.isListeningForRealtimeEvents)
+ {
+ this.isListeningForRealtimeEvents = false;
+ this.realtimeEventsWebSocket.WebSocketMessageReceived -= this.EtwEventsReceivedHandler;
+ }
+
+ await this.realtimeEventsWebSocket.CloseAsync();
+ }
+
+ ///
+ /// Creates a new if it hasn't already been initialized.
+ ///
+ /// Task for connecting the ETW realtime event WebSocket.
+ private async Task InitializeRealtimeEventsWebSocketAsync()
+ {
+ if (this.realtimeEventsWebSocket == null)
+ {
+#if WINDOWS_UWP
+ this.realtimeEventsWebSocket = new WebSocket(this.deviceConnection);
+#else
+ this.realtimeEventsWebSocket = new WebSocket(this.deviceConnection, this.ServerCertificateValidation);
+#endif
+ }
+
+ await this.realtimeEventsWebSocket.ConnectAsync(RealtimeEtwSessionApi);
+ }
+
+ ///
+ /// Handler for the ETW received event that passes the event to the RealtimeEventsMessageReceived handler.
+ ///
+ /// The object sending the event.
+ /// The event data.
+ private void EtwEventsReceivedHandler(
+ WebSocket sender,
+ WebSocketMessageReceivedEventArgs args)
+ {
+ if (args.Message != null)
+ {
+ this.RealtimeEventsMessageReceived?.Invoke(
+ this,
+ args);
+ }
+ }
+
+ #region Data contract
+
+ ///
+ /// ETW Events.
+ ///
+ [DataContract]
+ public class EtwEvents
+ {
+ ///
+ /// Saves the downconverted list of events
+ ///
+ private List stashedList;
+
+ ///
+ /// Gets the list of ETW Events that occured in the last second.
+ ///
+ public List Events
+ {
+ get
+ {
+ if (this.stashedList != null)
+ {
+ return this.stashedList;
+ }
+
+ List events = new List();
+ foreach (Dictionary dic in this.RawEvents)
+ {
+ events.Add(new EtwEventInfo(dic));
+ }
+
+ this.stashedList = events;
+ return this.stashedList;
+ }
+ }
+
+ ///
+ /// Gets the event frequency.
+ /// This is always 10 million (10000000) in RS2 devices.
+ ///
+ [DataMember(Name = "Frequency")]
+ public long Frequency { get; private set; }
+
+ ///
+ /// Gets or sets the raw list of events. Not for straight usage, as it's entirely unformatted.
+ ///
+ [DataMember(Name = "Events")]
+ private List> RawEvents { get; set; }
+ }
+
+ ///
+ /// ETW Events Info. Allows strongly typed access to guaranteed fields
+ /// like ID or Timestamp, and raw (as string) access to all other
+ /// payload data, like Latency or PID.
+ ///
+ public class EtwEventInfo : Dictionary
+ {
+ ///
+ /// Initializes a new instance of the class. Used by the DataContract at access time.
+ ///
+ /// Base dictionary used to populate the object.
+ internal EtwEventInfo(IDictionary dictionary) : base(dictionary)
+ {
+ }
+
+ ///
+ /// Gets the event identifer.
+ ///
+ public ushort ID
+ {
+ get
+ {
+ return ushort.Parse(this["ID"]);
+ }
+ }
+
+ ///
+ /// Gets the event keyword.
+ ///
+ public ulong Keyword
+ {
+ get
+ {
+ return ulong.Parse(this["Keyword"]);
+ }
+ }
+
+ ///
+ /// Gets the event level.
+ ///
+ public uint Level
+ {
+ get
+ {
+ return uint.Parse(this["Level"]);
+ }
+ }
+
+ ///
+ /// Gets the event provider name.
+ ///
+ public string Provider
+ {
+ get
+ {
+ return this["ProviderName"];
+ }
+ }
+
+ ///
+ /// Gets the event timestamp.
+ ///
+ public ulong Timestamp
+ {
+ get
+ {
+ return ulong.Parse(this["Timestamp"]);
+ }
+ }
+ }
+
+ ///
+ /// ETW Providers.
+ ///
+ [DataContract]
+ public class EtwProviders
+ {
+ ///
+ /// Gets list of ETW providers
+ ///
+ [DataMember(Name = "Providers")]
+ public List Providers { get; private set; }
+ }
+
+ ///
+ /// ETW Provider Info. Contains the Name and GUID.
+ ///
+ [DataContract]
+ public class EtwProviderInfo
+ {
+ ///
+ /// Gets provider guid.
+ ///
+ [DataMember(Name = "GUID")]
+ public Guid GUID { get; private set; }
+
+ ///
+ /// Gets provider name.
+ ///
+ [DataMember(Name = "Name")]
+ public string Name { get; private set; }
+ }
+
+#endregion // Data contract
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/Networking.cs b/WindowsDevicePortalWrapper/Core/Networking.cs
new file mode 100644
index 0000000..9b34e54
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/Networking.cs
@@ -0,0 +1,166 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for Networking methods.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API for getting IP config data.
+ ///
+ public static readonly string IpConfigApi = "api/networking/ipconfig";
+
+ ///
+ /// Gets the IP configuration data of the device.
+ ///
+ /// object containing details of the device's network configuration.
+ public async Task GetIpConfigAsync()
+ {
+ return await this.GetAsync(IpConfigApi);
+ }
+
+ #region Data contract
+
+ ///
+ /// DHCP object.
+ ///
+ [DataContract]
+ public class Dhcp
+ {
+ ///
+ /// Gets the time at which the lease will expire, in ticks.
+ ///
+ [DataMember(Name = "LeaseExpires")]
+ public long LeaseExpiresRaw { get; private set; }
+
+ ///
+ /// Gets the time at which the lease was obtained, in ticks.
+ ///
+ [DataMember(Name = "LeaseObtained")]
+ public long LeaseObtainedRaw { get; private set; }
+
+ ///
+ /// Gets the name.
+ ///
+ [DataMember(Name = "Address")]
+ public IpAddressInfo Address { get; private set; }
+
+ ///
+ /// Gets the lease expiration time.
+ ///
+ public DateTimeOffset LeaseExpires
+ {
+ get { return new DateTimeOffset(new DateTime(this.LeaseExpiresRaw)); }
+ }
+
+ ///
+ /// Gets the lease obtained time.
+ ///
+ public DateTimeOffset LeaseObtained
+ {
+ get { return new DateTimeOffset(new DateTime(this.LeaseObtainedRaw)); }
+ }
+ }
+
+ ///
+ /// IP Address info
+ ///
+ [DataContract]
+ public class IpAddressInfo
+ {
+ ///
+ /// Gets the address
+ ///
+ [DataMember(Name = "IpAddress")]
+ public string Address { get; private set; }
+
+ ///
+ /// Gets the subnet mask
+ ///
+ [DataMember(Name = "Mask")]
+ public string SubnetMask { get; private set; }
+ }
+
+ ///
+ /// IP Configuration object
+ ///
+ [DataContract]
+ public class IpConfiguration
+ {
+ ///
+ /// Gets the list of networking adapters
+ ///
+ [DataMember(Name = "Adapters")]
+ public List Adapters { get; private set; }
+ }
+
+ ///
+ /// Networking adapter info
+ ///
+ [DataContract]
+ public class NetworkAdapterInfo
+ {
+ ///
+ /// Gets the description
+ ///
+ [DataMember(Name = "Description")]
+ public string Description { get; private set; }
+
+ ///
+ /// Gets the hardware address
+ ///
+ [DataMember(Name = "HardwareAddress")]
+ public string MacAddress { get; private set; }
+
+ ///
+ /// Gets the index
+ ///
+ [DataMember(Name = "Index")]
+ public int Index { get; private set; }
+
+ ///
+ /// Gets the name
+ ///
+ [DataMember(Name = "Name")]
+ public Guid Id { get; private set; }
+
+ ///
+ /// Gets the type
+ ///
+ [DataMember(Name = "Type")]
+ public string AdapterType { get; private set; }
+
+ ///
+ /// Gets DHCP info
+ ///
+ [DataMember(Name = "DHCP")]
+ public Dhcp Dhcp { get; private set; }
+
+ // TODO - WINS
+
+ ///
+ /// Gets Gateway info
+ ///
+ [DataMember(Name = "Gateways")]
+ public List Gateways { get; private set; }
+
+ ///
+ /// Gets the list of IP addresses
+ ///
+ [DataMember(Name = "IpAddresses")]
+ public List IpAddresses { get; private set; }
+ }
+ #endregion // Data contract
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/OsInformation.cs b/WindowsDevicePortalWrapper/Core/OsInformation.cs
new file mode 100644
index 0000000..9ef6ccb
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/OsInformation.cs
@@ -0,0 +1,272 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for OS Information.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API for getting the device family.
+ ///
+ public static readonly string DeviceFamilyApi = "api/os/devicefamily";
+
+ ///
+ /// API for getting the machine name.
+ ///
+ public static readonly string MachineNameApi = "api/os/machinename";
+
+ ///
+ /// API for getting the OS information.
+ ///
+ public static readonly string OsInfoApi = "api/os/info";
+
+ ///
+ /// Device portal platforms
+ ///
+ public enum DevicePortalPlatforms
+ {
+ ///
+ /// Unknown platform
+ ///
+ Unknown = -1,
+
+ ///
+ /// Windows platform
+ ///
+ Windows = 0,
+
+ ///
+ /// Mobile platform
+ ///
+ Mobile,
+
+ ///
+ /// HoloLens platform
+ ///
+ HoloLens,
+
+ ///
+ /// Xbox One platform
+ ///
+ XboxOne,
+
+ ///
+ /// Windows IoT on Dragonboard 410c
+ ///
+ IoTDragonboard410c,
+
+ ///
+ /// Windows IoT on Minnowboard Max
+ ///
+ IoTMinnowboardMax,
+
+ ///
+ /// Windows IoT on Raspberry Pi 2
+ ///
+ IoTRaspberryPi2,
+
+ ///
+ /// Windows IoT on Raspberry Pi 3
+ ///
+ IoTRaspberryPi3,
+
+ ///
+ /// A virtual machine. This may or may not be an emulator.
+ ///
+ VirtualMachine
+ }
+
+ ///
+ /// Gets the family name (ex: Windows.Holographic) of the device.
+ ///
+ /// String containing the device's family.
+ public async Task GetDeviceFamilyAsync()
+ {
+ DeviceOsFamily deviceFamily = await this.GetAsync(DeviceFamilyApi).ConfigureAwait(false);
+ return deviceFamily.Family;
+ }
+
+ ///
+ /// Gets the name of the device.
+ ///
+ /// String containing the device's name.
+ public async Task GetDeviceNameAsync()
+ {
+ DeviceName deviceName = await this.GetAsync(MachineNameApi);
+ return deviceName.Name;
+ }
+
+ ///
+ /// Gets information about the device's operating system.
+ ///
+ /// OperatingSystemInformation object containing details of the installed operating system.
+ public Task GetOperatingSystemInformationAsync()
+ {
+ return this.GetAsync(OsInfoApi);
+ }
+
+ ///
+ /// Sets the device's name
+ ///
+ /// The name to assign to the device.
+ /// The new name does not take effect until the device has been restarted.
+ /// Task tracking setting the device name completion.
+ public Task SetDeviceNameAsync(string name)
+ {
+ return this.PostAsync(
+ MachineNameApi,
+ string.Format("name={0}", Utilities.Hex64Encode(name)));
+ }
+
+ #region Data contract
+
+ ///
+ /// Device name object.
+ ///
+ [DataContract]
+ public class DeviceName
+ {
+ ///
+ /// Gets the name.
+ ///
+ [DataMember(Name = "ComputerName")]
+ public string Name { get; private set; }
+ }
+
+ ///
+ /// Device family object.
+ ///
+ [DataContract]
+ public class DeviceOsFamily
+ {
+ ///
+ /// Gets the device family name.
+ ///
+ [DataMember(Name = "DeviceType")]
+ public string Family { get; private set; }
+ }
+
+ ///
+ /// Operating system information.
+ ///
+ [DataContract]
+ public class OperatingSystemInformation
+ {
+ ///
+ /// Gets the OS name.
+ ///
+ [DataMember(Name = "ComputerName")]
+ public string Name { get; private set; }
+
+ ///
+ /// Gets the language
+ ///
+ [DataMember(Name = "Language")]
+ public string Language { get; private set; }
+
+ ///
+ /// Gets the edition
+ ///
+ [DataMember(Name = "OsEdition")]
+ public string OsEdition { get; private set; }
+
+ ///
+ /// Gets the edition Id
+ ///
+ [DataMember(Name = "OsEditionId")]
+ public uint OsEditionId { get; private set; }
+
+ ///
+ /// Gets the OS version
+ ///
+ [DataMember(Name = "OsVersion")]
+ public string OsVersionString { get; private set; }
+
+ ///
+ /// Gets the raw platform type
+ ///
+ [DataMember(Name = "Platform")]
+ public string PlatformName { get; private set; }
+
+ ///
+ /// Gets the platform
+ ///
+ public DevicePortalPlatforms Platform
+ {
+ get
+ {
+ DevicePortalPlatforms platform = DevicePortalPlatforms.Unknown;
+
+ try
+ {
+ // MinnowBoard Max model no. can change based on firmware
+ if (this.PlatformName.Contains("Minnowboard Max"))
+ {
+ return DevicePortalPlatforms.IoTMinnowboardMax;
+ }
+
+ switch (this.PlatformName)
+ {
+ case "Xbox One":
+ platform = DevicePortalPlatforms.XboxOne;
+ break;
+
+ case "SBC":
+ platform = DevicePortalPlatforms.IoTDragonboard410c;
+ break;
+
+ case "Raspberry Pi 2":
+ platform = DevicePortalPlatforms.IoTRaspberryPi2;
+ break;
+
+ case "Raspberry Pi 3":
+ platform = DevicePortalPlatforms.IoTRaspberryPi3;
+ break;
+
+ case "Virtual Machine":
+ platform = DevicePortalPlatforms.VirtualMachine;
+ break;
+
+ default:
+ platform = (DevicePortalPlatforms)Enum.Parse(typeof(DevicePortalPlatforms), this.PlatformName);
+ break;
+ }
+ }
+ catch
+ {
+ switch (this.OsEdition)
+ {
+ case "Enterprise":
+ case "Home":
+ case "Professional":
+ platform = DevicePortalPlatforms.Windows;
+ break;
+
+ case "Mobile":
+ platform = DevicePortalPlatforms.Mobile;
+ break;
+
+ default:
+ platform = DevicePortalPlatforms.Unknown;
+ break;
+ }
+ }
+
+ return platform;
+ }
+ }
+ }
+ #endregion // Data contract
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/PerformanceData.cs b/WindowsDevicePortalWrapper/Core/PerformanceData.cs
new file mode 100644
index 0000000..35b519c
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/PerformanceData.cs
@@ -0,0 +1,549 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for Performance methods
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API for getting all running processes
+ ///
+ public static readonly string RunningProcessApi = "api/resourcemanager/processes";
+
+ ///
+ /// API for getting system performance
+ ///
+ public static readonly string SystemPerfApi = "api/resourcemanager/systemperf";
+
+ ///
+ /// Web socket to get running processes.
+ ///
+ private WebSocket deviceProcessesWebSocket;
+
+ ///
+ /// Web socket to get the system perf of the device.
+ ///
+ private WebSocket systemPerfWebSocket;
+
+ ///
+ /// The running processes message received handler.
+ ///
+ public event WebSocketMessageReceivedEventHandler RunningProcessesMessageReceived;
+
+ ///
+ /// The system perf message received handler.
+ ///
+ public event WebSocketMessageReceivedEventHandler SystemPerfMessageReceived;
+
+ ///
+ /// Gets the collection of processes running on the device.
+ ///
+ /// RunningProcesses object containing the list of running processes.
+ public async Task GetRunningProcessesAsync()
+ {
+ return await this.GetAsync(RunningProcessApi);
+ }
+
+ ///
+ /// Starts listening for the running processes on the device with them being returned via the RunningProcessesMessageReceived event handler.
+ ///
+ /// Task for connecting to the websocket but not for listening to it.
+ public async Task StartListeningForRunningProcessesAsync()
+ {
+ if (this.deviceProcessesWebSocket == null)
+ {
+#if WINDOWS_UWP
+ this.deviceProcessesWebSocket = new WebSocket(this.deviceConnection);
+#else
+ this.deviceProcessesWebSocket = new WebSocket(this.deviceConnection, this.ServerCertificateValidation);
+#endif
+
+ this.deviceProcessesWebSocket.WebSocketMessageReceived += this.RunningProcessesReceivedHandler;
+ }
+ else
+ {
+ if (this.deviceProcessesWebSocket.IsListeningForMessages)
+ {
+ return;
+ }
+ }
+
+ await this.deviceProcessesWebSocket.ConnectAsync(RunningProcessApi);
+ await this.deviceProcessesWebSocket.ReceiveMessagesAsync();
+ }
+
+ ///
+ /// Stop listening for the running processes on the device.
+ ///
+ /// Task for stop listening for processes and disconnecting from the websocket .
+ public async Task StopListeningForRunningProcessesAsync()
+ {
+ await this.deviceProcessesWebSocket.CloseAsync();
+ }
+
+ ///
+ /// Gets system performance information for the device.
+ ///
+ /// SystemPerformanceInformation object containing information such as memory usage.
+ public async Task GetSystemPerfAsync()
+ {
+ return await this.GetAsync(SystemPerfApi);
+ }
+
+ ///
+ /// Starts listening for the system performance information for the device with it being returned via the SystemPerfMessageReceived event handler.
+ ///
+ /// Task for connecting to the websocket but not for listening to it.
+ public async Task StartListeningForSystemPerfAsync()
+ {
+ if (this.systemPerfWebSocket == null)
+ {
+#if WINDOWS_UWP
+ this.systemPerfWebSocket = new WebSocket(this.deviceConnection);
+#else
+ this.systemPerfWebSocket = new WebSocket(this.deviceConnection, this.ServerCertificateValidation);
+#endif
+
+ this.systemPerfWebSocket.WebSocketMessageReceived += this.SystemPerfReceivedHandler;
+ }
+ else
+ {
+ if (this.systemPerfWebSocket.IsListeningForMessages)
+ {
+ return;
+ }
+ }
+
+ await this.systemPerfWebSocket.ConnectAsync(SystemPerfApi);
+ await this.systemPerfWebSocket.ReceiveMessagesAsync();
+ }
+
+ ///
+ /// Stop listening for the system performance information for the device.
+ ///
+ /// Task for stop listening for system perf and disconnecting from the websocket .
+ public async Task StopListeningForSystemPerfAsync()
+ {
+ await this.systemPerfWebSocket.CloseAsync();
+ }
+
+ ///
+ /// Handler for the processes received event that passes the event to the RunningProcessesMessageReceived handler.
+ ///
+ /// The object sending the event.
+ /// The event data.
+ private void RunningProcessesReceivedHandler(
+ WebSocket sender,
+ WebSocketMessageReceivedEventArgs args)
+ {
+ if (args.Message != null)
+ {
+ this.RunningProcessesMessageReceived?.Invoke(
+ this,
+ args);
+ }
+ }
+
+ ///
+ /// Handler for the system performance information received event that passes the event to the SystemPerfMessageReceived handler.
+ ///
+ /// The object sending the event.
+ /// The event data.
+ private void SystemPerfReceivedHandler(
+ WebSocket sender,
+ WebSocketMessageReceivedEventArgs args)
+ {
+ if (args.Message != null)
+ {
+ this.SystemPerfMessageReceived?.Invoke(
+ this,
+ args);
+ }
+ }
+
+#region Device contract
+
+ ///
+ /// Object representing the app version. Only present if the process is an app.
+ ///
+ [DataContract]
+ public class AppVersion
+ {
+ ///
+ /// Gets the major version number
+ ///
+ [DataMember(Name = "Major")]
+ public uint Major { get; private set; }
+
+ ///
+ /// Gets the minor version number
+ ///
+ [DataMember(Name = "Minor")]
+ public uint Minor { get; private set; }
+
+ ///
+ /// Gets the build number
+ ///
+ [DataMember(Name = "Build")]
+ public uint Build { get; private set; }
+
+ ///
+ /// Gets the revision number
+ ///
+ [DataMember(Name = "Revision")]
+ public uint Revision { get; private set; }
+ }
+
+ ///
+ /// Process Info. Contains app information if the process is an app.
+ ///
+ [DataContract]
+ public class DeviceProcessInfo
+ {
+ ///
+ /// Gets the app name. Only present if the process is an app.
+ ///
+ [DataMember(Name = "AppName")]
+ public string AppName { get; private set; }
+
+ ///
+ /// Gets CPU Usage as a percentage of available CPU resources (0-100)
+ ///
+ [DataMember(Name = "CPUUsage")]
+ public float CpuUsage { get; private set; }
+
+ ///
+ /// Gets the image name
+ ///
+ [DataMember(Name = "ImageName")]
+ public string Name { get; private set; }
+
+ ///
+ /// Gets the process id (pid)
+ ///
+ [DataMember(Name = "ProcessId")]
+ public uint ProcessId { get; private set; }
+
+ ///
+ /// Gets the user the process is running as.
+ ///
+ [DataMember(Name = "UserName")]
+ public string UserName { get; private set; }
+
+ ///
+ /// Gets the package full name. Only present if the process is an app.
+ ///
+ [DataMember(Name = "PackageFullName")]
+ public string PackageFullName { get; private set; }
+
+ ///
+ /// Gets the Page file usage info, in bytes
+ ///
+ [DataMember(Name = "PageFileUsage")]
+ public ulong PageFile { get; private set; }
+
+ ///
+ /// Gets the working set size, in bytes
+ ///
+ [DataMember(Name = "WorkingSetSize")]
+ public ulong WorkingSet { get; private set; }
+
+ ///
+ /// Gets package working set, in bytes
+ ///
+ [DataMember(Name = "PrivateWorkingSet")]
+ public ulong PrivateWorkingSet { get; private set; }
+
+ ///
+ /// Gets session id
+ ///
+ [DataMember(Name = "SessionId")]
+ public uint SessionId { get; private set; }
+
+ ///
+ /// Gets total commit, in bytes
+ ///
+ [DataMember(Name = "TotalCommit")]
+ public ulong TotalCommit { get; private set; }
+
+ ///
+ /// Gets virtual size, in bytes
+ ///
+ [DataMember(Name = "VirtualSize")]
+ public ulong VirtualSize { get; private set; }
+
+ ///
+ /// Gets a value indicating whether or not the app is running
+ /// (versus suspended). Only present if the process is an app.
+ ///
+ [DataMember(Name = "IsRunning")]
+ public bool IsRunning { get; private set; }
+
+ ///
+ /// Gets publisher. Only present if the process is an app.
+ ///
+ [DataMember(Name = "Publisher")]
+ public string Publisher { get; private set; }
+
+ ///
+ /// Gets version. Only present if the process is an app.
+ ///
+ [DataMember(Name = "Version")]
+ public AppVersion Version { get; private set; }
+
+ ///
+ /// Gets a value indicating whether or not the package is a XAP
+ /// package. Only present if the process is an app.
+ ///
+ [DataMember(Name = "IsXAP")]
+ public bool IsXAP { get; private set; }
+
+ ///
+ /// String representation of a process
+ ///
+ /// String representation
+ public override string ToString()
+ {
+ return string.Format("{0} ({1})", this.AppName, this.Name);
+ }
+ }
+
+ ///
+ /// GPU Adaptors
+ ///
+ [DataContract]
+ public class GpuAdapter
+ {
+ ///
+ /// Gets total Dedicated memory in bytes
+ ///
+ [DataMember(Name = "DedicatedMemory")]
+ public ulong DedicatedMemory { get; private set; }
+
+ ///
+ /// Gets used Dedicated memory in bytes
+ ///
+ [DataMember(Name = "DedicatedMemoryUsed")]
+ public ulong DedicatedMemoryUsed { get; private set; }
+
+ ///
+ /// Gets description
+ ///
+ [DataMember(Name = "Description")]
+ public string Description { get; private set; }
+
+ ///
+ /// Gets system memory in bytes
+ ///
+ [DataMember(Name = "SystemMemory")]
+ public ulong SystemMemory { get; private set; }
+
+ ///
+ /// Gets memory used in bytes
+ ///
+ [DataMember(Name = "SystemMemoryUsed")]
+ public ulong SystemMemoryUsed { get; private set; }
+
+ ///
+ /// Gets engines utilization as percent of maximum.
+ ///
+ [DataMember(Name = "EnginesUtilization")]
+ public List EnginesUtilization { get; private set; }
+ }
+
+ ///
+ /// GPU performance data
+ ///
+ [DataContract]
+ public class GpuPerformanceData
+ {
+ ///
+ /// Gets list of available adapters
+ ///
+ [DataMember(Name = "AvailableAdapters")]
+ public List Adapters { get; private set; }
+ }
+
+ ///
+ /// Network performance data
+ ///
+ [DataContract]
+ public class NetworkPerformanceData
+ {
+ ///
+ /// Gets current download speed in bytes per second
+ ///
+ [DataMember(Name = "NetworkInBytes")]
+ public ulong BytesIn { get; private set; }
+
+ ///
+ /// Gets current upload speed in bytes per second
+ ///
+ [DataMember(Name = "NetworkOutBytes")]
+ public ulong BytesOut { get; private set; }
+ }
+
+ ///
+ /// Running processes
+ ///
+ [DataContract]
+ public class RunningProcesses
+ {
+ ///
+ /// Gets list of running processes.
+ ///
+ [DataMember(Name = "Processes")]
+ public List Processes { get; private set; }
+
+ ///
+ /// Checks to see if this process Id is in the list of processes
+ ///
+ /// Process to look for
+ /// whether the process id was found
+ public bool Contains(int processId)
+ {
+ bool found = false;
+
+ foreach (DeviceProcessInfo pi in this.Processes)
+ {
+ if (pi.ProcessId == processId)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ return found;
+ }
+
+ ///
+ /// Checks for a given package name
+ ///
+ /// Name of the package to look for
+ /// Whether we should be case sensitive in our search
+ /// Whether the package was found
+ public bool Contains(string packageName, bool caseSensitive = true)
+ {
+ bool found = false;
+
+ foreach (DeviceProcessInfo pi in this.Processes)
+ {
+ if (string.Compare(
+ pi.PackageFullName,
+ packageName,
+ caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ return found;
+ }
+ }
+
+ ///
+ /// System performance information
+ ///
+ [DataContract]
+ public class SystemPerformanceInformation
+ {
+ ///
+ /// Gets available pages
+ ///
+ [DataMember(Name = "AvailablePages")]
+ public ulong AvailablePages { get; private set; }
+
+ ///
+ /// Gets commit limit in bytes
+ ///
+ [DataMember(Name = "CommitLimit")]
+ public uint CommitLimit { get; private set; }
+
+ ///
+ /// Gets committed pages
+ ///
+ [DataMember(Name = "CommittedPages")]
+ public uint CommittedPages { get; private set; }
+
+ ///
+ /// Gets CPU load as percent of maximum (0 - 100)
+ ///
+ [DataMember(Name = "CpuLoad")]
+ public uint CpuLoad { get; private set; }
+
+ ///
+ /// Gets IO Other Speed in bytes per second
+ ///
+ [DataMember(Name = "IOOtherSpeed")]
+ public ulong IoOtherSpeed { get; private set; }
+
+ ///
+ /// Gets IO Read speed in bytes per second.
+ ///
+ [DataMember(Name = "IOReadSpeed")]
+ public ulong IoReadSpeed { get; private set; }
+
+ ///
+ /// Gets IO write speed in bytes per second
+ ///
+ [DataMember(Name = "IOWriteSpeed")]
+ public ulong IoWriteSpeed { get; private set; }
+
+ ///
+ /// Gets Non paged pool pages
+ ///
+ [DataMember(Name = "NonPagedPoolPages")]
+ public uint NonPagedPoolPages { get; private set; }
+
+ ///
+ /// Gets page size
+ ///
+ [DataMember(Name = "PageSize")]
+ public uint PageSize { get; private set; }
+
+ ///
+ /// Gets paged pool pages
+ ///
+ [DataMember(Name = "PagedPoolPages")]
+ public uint PagedPoolPages { get; private set; }
+
+ ///
+ /// Gets total installed in KB
+ ///
+ [DataMember(Name = "TotalInstalledInKb")]
+ public ulong TotalInstalledKb { get; private set; }
+
+ ///
+ /// Gets total pages
+ ///
+ [DataMember(Name = "TotalPages")]
+ public uint TotalPages { get; private set; }
+
+ ///
+ /// Gets GPU data
+ ///
+ [DataMember(Name = "GPUData")]
+ public GpuPerformanceData GpuData { get; private set; }
+
+ ///
+ /// Gets Networking data
+ ///
+ [DataMember(Name = "NetworkingData")]
+ public NetworkPerformanceData NetworkData { get; private set; }
+ }
+
+#endregion // Device contract
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/Power.cs b/WindowsDevicePortalWrapper/Core/Power.cs
new file mode 100644
index 0000000..37e2e1a
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/Power.cs
@@ -0,0 +1,202 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for Power methods.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API for getting or setting the active power scheme.
+ ///
+ public static readonly string ActivePowerSchemeApi = "api/power/activecfg";
+
+ ///
+ /// API for getting battery state.
+ ///
+ public static readonly string BatteryStateApi = "api/power/battery";
+
+ ///
+ /// API for getting or setting a power scheme's sub-value.
+ ///
+ public static readonly string PowerSchemeSubValueApi = "api/power/cfg";
+
+ ///
+ /// API for controlling power state.
+ ///
+ public static readonly string PowerStateApi = "api/power/state";
+
+ ///
+ /// API for getting a sleep study report.
+ ///
+ public static readonly string SleepStudyReportApi = "api/power/sleepstudy/report";
+
+ ///
+ /// API for getting the list of sleep study reports.
+ ///
+ public static readonly string SleepStudyReportsApi = "api/power/sleepstudy/reports";
+
+ ///
+ /// API for getting a sleep study report.
+ ///
+ public static readonly string SleepStudyTransformApi = "api/power/sleepstudy/transform";
+
+ ///
+ /// Returns the current active power scheme.
+ ///
+ /// The power scheme identifier.
+ public async Task GetActivePowerSchemeAsync()
+ {
+ ActivePowerScheme activeScheme = await this.GetAsync(ActivePowerSchemeApi);
+ return activeScheme.Id;
+ }
+
+ ///
+ /// Returns the current state of the device's battery.
+ ///
+ /// BatteryState object containing details such as the current battery level.
+ public async Task GetBatteryStateAsync()
+ {
+ return await this.GetAsync(BatteryStateApi);
+ }
+
+ ///
+ /// Gets the device's current power state.
+ ///
+ /// PowerState object containing details such as whether or not the device is in low power mode.
+ public async Task GetPowerStateAsync()
+ {
+ return await this.GetAsync(PowerStateApi);
+ }
+
+ #region Data contract
+
+ ///
+ /// Battery state.
+ ///
+ [DataContract]
+ public class ActivePowerScheme
+ {
+ ///
+ /// Gets the active power scheme identifier.
+ ///
+ [DataMember(Name = "ActivePowerScheme")]
+ public Guid Id { get; private set; }
+ }
+
+ ///
+ /// Battery state.
+ ///
+ [DataContract]
+ public class BatteryState
+ {
+ ///
+ /// Gets a value indicating whether the device is on AC power.
+ ///
+ [DataMember(Name = "AcOnline")]
+ public bool IsOnAcPower { get; private set; }
+
+ ///
+ /// Gets a value indicating whether a battery is present.
+ ///
+ [DataMember(Name = "BatteryPresent")]
+ public bool IsBatteryPresent { get; private set; }
+
+ ///
+ /// Gets a value indicating whether the device is charging.
+ ///
+ [DataMember(Name = "Charging")]
+ public bool IsCharging { get; private set; }
+
+ ///
+ /// Gets the default alert.
+ ///
+ [DataMember(Name = "DefaultAlert1")]
+ public int DefaultAlert1 { get; private set; }
+
+ ///
+ /// Gets the default alert.
+ ///
+ [DataMember(Name = "DefaultAlert2")]
+ public int DefaultAlert2 { get; private set; }
+
+ ///
+ /// Gets estimated battery time left in seconds.
+ ///
+ [DataMember(Name = "EstimatedTime")]
+ public uint EstimatedTimeRaw { get; private set; }
+
+ ///
+ /// Gets maximum capacity.
+ ///
+ [DataMember(Name = "MaximumCapacity")]
+ public int MaximumCapacity { get; private set; }
+
+ ///
+ /// Gets remaining capacity.
+ ///
+ [DataMember(Name = "RemainingCapacity")]
+ public int RemainingCapacity { get; private set; }
+
+ ///
+ /// Gets the battery level as a percentage of the maximum capacity.
+ ///
+ public float Level
+ {
+ get
+ {
+ // Desktop PCs typically do not have a battery, return 100%
+ if (this.MaximumCapacity == 0)
+ {
+ return 100f;
+ }
+
+ return 100.0f * ((float)this.RemainingCapacity / this.MaximumCapacity);
+ }
+ }
+
+ ///
+ /// Gets the remaining battery time left, as a TimeSpan.
+ /// Will be 0 if the device has no battery.
+ /// Will be 0xFFFF,FFFF (around 138 years) if the device is charging.
+ ///
+ public TimeSpan EstimatedTime
+ {
+ get
+ {
+ return new TimeSpan(0, 0, (int)this.EstimatedTimeRaw);
+ }
+ }
+ }
+
+ ///
+ /// Power state
+ ///
+ [DataContract]
+ public class PowerState
+ {
+ ///
+ /// Gets a value indicating whether the device is in a lower power mode
+ ///
+ [DataMember(Name = "LowPowerState")]
+ public bool InLowPowerState { get; private set; }
+
+ ///
+ /// Gets a value indicating whether the device supports a lower power mode
+ ///
+ [DataMember(Name = "LowPowerStateAvailable")]
+ public bool IsLowPowerStateAvailable { get; private set; }
+ }
+
+ #endregion // Data contract
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/RemoteControl.cs b/WindowsDevicePortalWrapper/Core/RemoteControl.cs
new file mode 100644
index 0000000..9ba49c2
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/RemoteControl.cs
@@ -0,0 +1,48 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for Remote Control methods.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API for rebooting the device.
+ ///
+ public static readonly string RebootApi = "api/control/restart";
+
+ ///
+ /// API for shutting down the device.
+ ///
+ public static readonly string ShutdownApi = "api/control/shutdown";
+
+ ///
+ /// Reboots the device.
+ ///
+ ///
+ /// Task tracking reboot completion.
+ ///
+ public async Task RebootAsync()
+ {
+ await this.PostAsync(RebootApi);
+ }
+
+ ///
+ /// Shuts down the device.
+ ///
+ ///
+ /// Task tracking shutdown completion.
+ ///
+ public async Task ShutdownAsync()
+ {
+ await this.PostAsync(ShutdownApi);
+ }
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/TaskManager.cs b/WindowsDevicePortalWrapper/Core/TaskManager.cs
new file mode 100644
index 0000000..54af85a
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/TaskManager.cs
@@ -0,0 +1,69 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for Task Manager methods
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API for starting or stopping a modern application.
+ ///
+ public static readonly string TaskManagerApi = "api/taskmanager/app";
+
+ ///
+ /// Starts running the specified application.
+ ///
+ /// Application ID
+ /// The name of the application package.
+ /// Process identifier for the application instance.
+ public async Task LaunchApplicationAsync(
+ string appid,
+ string packageName)
+ {
+ string payload = string.Format(
+ "appid={0}&package={1}",
+ Utilities.Hex64Encode(appid),
+ Utilities.Hex64Encode(packageName));
+
+ await this.PostAsync(
+ TaskManagerApi,
+ payload);
+
+ RunningProcesses runningApps = await this.GetRunningProcessesAsync();
+
+ uint processId = 0;
+ foreach (DeviceProcessInfo process in runningApps.Processes)
+ {
+ if (string.Compare(process.PackageFullName, packageName) == 0)
+ {
+ processId = process.ProcessId;
+ break;
+ }
+ }
+
+ return processId;
+ }
+
+ ///
+ /// Stops the specified application from running.
+ ///
+ /// The name of the application package.
+ ///
+ /// Task for tracking termination completion
+ ///
+ public async Task TerminateApplicationAsync(string packageName)
+ {
+ await this.DeleteAsync(
+ TaskManagerApi,
+ string.Format("package={0}", Utilities.Hex64Encode(packageName)));
+ }
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/WiFiManagement.cs b/WindowsDevicePortalWrapper/Core/WiFiManagement.cs
new file mode 100644
index 0000000..ed1063c
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/WiFiManagement.cs
@@ -0,0 +1,248 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for WiFi management methods.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API for getting the WiFi interfaces.
+ ///
+ public static readonly string WifiInterfacesApi = "api/wifi/interfaces";
+
+ ///
+ /// API for the controlling the WiFi network.
+ ///
+ public static readonly string WifiNetworkApi = "api/wifi/network";
+
+ ///
+ /// API for getting available WiFi networks.
+ ///
+ public static readonly string WifiNetworksApi = "api/wifi/networks";
+
+ ///
+ /// Connect to a WiFi network using a given network adapter and SSID.
+ ///
+ /// Network adaptor GUID.
+ /// SSID of the network.
+ /// Network key.
+ /// Task tracking connection status.
+ public async Task ConnectToWifiNetworkAsync(
+ Guid networkAdapter,
+ string ssid,
+ string networkKey)
+ {
+ string payload = string.Format(
+ "interface={0}&ssid={1}&op=connect&createprofile=yes&key={2}",
+ networkAdapter.ToString(),
+ Utilities.Hex64Encode(ssid),
+ Utilities.Hex64Encode(networkKey));
+
+ await this.PostAsync(
+ WifiNetworkApi,
+ payload);
+ }
+
+ ///
+ /// Gets WiFi interfaces.
+ ///
+ /// List of WiFi interfaces.
+ public async Task GetWifiInterfacesAsync()
+ {
+ return await this.GetAsync(WifiInterfacesApi);
+ }
+
+ ///
+ /// Gets WiFi networks as seen from a WiFi interface.
+ ///
+ /// Interface to get networks from.
+ /// List of available networks.
+ public async Task GetWifiNetworksAsync(Guid interfaceGuid)
+ {
+ return await this.GetAsync(
+ WifiNetworksApi,
+ string.Format("interface={0}", interfaceGuid.ToString()));
+ }
+
+ #region Data contract
+
+ ///
+ /// WiFi interface.
+ ///
+ [DataContract]
+ public class WifiInterface
+ {
+ ///
+ /// Gets description.
+ ///
+ [DataMember(Name = "Description")]
+ public string Description { get; private set; }
+
+ ///
+ /// Gets GUID.
+ ///
+ [DataMember(Name = "GUID")]
+ public Guid Guid { get; private set; }
+
+ ///
+ /// Gets index.
+ ///
+ [DataMember(Name = "Index")]
+ public int Index { get; private set; }
+
+ ///
+ /// Gets profiles list.
+ ///
+ [DataMember(Name = "ProfilesList")]
+ public List Profiles { get; private set; }
+ }
+
+ ///
+ /// WiFi interfaces.
+ ///
+ [DataContract]
+ public class WifiInterfaces
+ {
+ ///
+ /// Gets the list of interfaces.
+ ///
+ [DataMember(Name = "Interfaces")]
+ public List Interfaces { get; private set; }
+ }
+
+ ///
+ /// WiFi networks.
+ ///
+ [DataContract]
+ public class WifiNetworks
+ {
+ ///
+ /// Gets the list of available networks.
+ ///
+ [DataMember(Name = "AvailableNetworks")]
+ public List AvailableNetworks { get; private set; }
+ }
+
+ ///
+ /// WiFi network info.
+ ///
+ [DataContract]
+ public class WifiNetworkInfo
+ {
+ ///
+ /// Gets a value indicating whether the device is already connected to this network.
+ ///
+ [DataMember(Name = "AlreadyConnected")]
+ public bool IsConnected { get; private set; }
+
+ ///
+ /// Gets the authentication algorithm.
+ ///
+ [DataMember(Name = "AuthenticationAlgorithm")]
+ public string AuthenticationAlgorithm { get; private set; }
+
+ ///
+ /// Gets the channel.
+ ///
+ [DataMember(Name = "Channel")]
+ public int Channel { get; private set; }
+
+ ///
+ /// Gets the cipher algorithm.
+ ///
+ [DataMember(Name = "CipherAlgorithm")]
+ public string CipherAlgorithm { get; private set; }
+
+ ///
+ /// Gets a value indicating whether this network is connectable
+ ///
+ [DataMember(Name = "Connectable")]
+ public bool IsConnectable { get; private set; }
+
+ ///
+ /// Gets the infrastructure type - ad hoc or standard.
+ ///
+ [DataMember(Name = "InfrastructureType")]
+ public string InfrastructureType { get; private set; }
+
+ ///
+ /// Gets a value indicating whether a profile is available.
+ ///
+ [DataMember(Name = "ProfileAvailable")]
+ public bool IsProfileAvailable { get; private set; }
+
+ ///
+ /// Gets the profile name.
+ ///
+ [DataMember(Name = "ProfileName")]
+ public string ProfileName { get; private set; }
+
+ ///
+ /// Gets the SSID.
+ ///
+ [DataMember(Name = "SSID")]
+ public string Ssid { get; private set; }
+
+ ///
+ /// Gets a value indicating whether security is enabled.
+ ///
+ [DataMember(Name = "SecurityEnabled")]
+ public bool IsSecurityEnabled { get; private set; }
+
+ ///
+ /// Gets the signal quality.
+ ///
+ [DataMember(Name = "SignalQuality")]
+ public int SignalQuality { get; private set; }
+
+ ///
+ /// Gets the BSSID.
+ ///
+ [DataMember(Name = "BSSID")]
+ public List Bssid { get; private set; }
+
+ ///
+ /// Gets physical types.
+ ///
+ [DataMember(Name = "PhysicalTypes")]
+ public List NetworkTypes { get; private set; }
+ }
+
+ ///
+ /// WiFi network profile.
+ ///
+ [DataContract]
+ public class WifiNetworkProfile
+ {
+ ///
+ /// Gets a value indicating whether this is a group policy profile.
+ ///
+ [DataMember(Name = "GroupPolicyProfile")]
+ public bool IsGroupPolicyProfile { get; private set; }
+
+ ///
+ /// Gets the name.
+ ///
+ [DataMember(Name = "Name")]
+ public string Name { get; private set; }
+
+ ///
+ /// Gets a value indicating whether this is a per user profile.
+ ///
+ [DataMember(Name = "PerUserProfile")]
+ public bool IsPerUserProfile { get; private set; }
+ }
+ #endregion // Data contract
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/WindowsErrorReporting.cs b/WindowsDevicePortalWrapper/Core/WindowsErrorReporting.cs
new file mode 100644
index 0000000..9124a52
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/WindowsErrorReporting.cs
@@ -0,0 +1,215 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+ using System.Runtime.Serialization;
+ using System.Threading.Tasks;
+
+ ///
+ /// Wrapper for collecting Windows Error Reports from the device.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API for downloading a Windows error reporting file.
+ ///
+ public static readonly string WindowsErrorReportingFileApi = "api/wer/report/file";
+
+ ///
+ /// API for getting the list of files in a Windows error report.
+ ///
+ public static readonly string WindowsErrorReportingFilesApi = "api/wer/report/files";
+
+ ///
+ /// API for getting the list of Windows error reports.
+ ///
+ public static readonly string WindowsErrorReportsApi = "api/wer/reports";
+
+ ///
+ /// Gets the list of Windows Error Reporting (WER) reports.
+ ///
+ /// The list of Windows Error Reporting (WER) reports.
+ public async Task GetWindowsErrorReportsAsync()
+ {
+ this.CheckPlatformSupport();
+
+ return await this.GetAsync(WindowsErrorReportsApi);
+ }
+
+ ///
+ /// Gets the list of files in a Windows Error Reporting (WER) report.
+ ///
+ /// The user associated with the report.
+ /// The type of report. This can be either 'queried' or 'archived'.
+ /// The name of the report.
+ /// The list of files.
+ public async Task GetWindowsErrorReportingFileListAsync(string user, string type, string name)
+ {
+ this.CheckPlatformSupport();
+
+ Dictionary payload = new Dictionary();
+ payload.Add("user", user);
+ payload.Add("type", type);
+ payload.Add("name", Utilities.Hex64Encode(name));
+
+ return await this.GetAsync(WindowsErrorReportingFilesApi, Utilities.BuildQueryString(payload));
+ }
+
+ ///
+ /// Gets the specified file from a Windows Error Reporting (WER) report.
+ ///
+ /// The user associated with the report.
+ /// The type of report. This can be either 'queried' or 'archived'.
+ /// The name of the report.
+ /// The name of the file to download from the report.
+ /// Byte array containing the file data
+ public async Task GetWindowsErrorReportingFileAsync(string user, string type, string name, string file)
+ {
+ this.CheckPlatformSupport();
+
+ Dictionary payload = new Dictionary();
+ payload.Add("user", user);
+ payload.Add("type", type);
+ payload.Add("name", Utilities.Hex64Encode(name));
+ payload.Add("file", Utilities.Hex64Encode(file));
+
+ Uri uri = Utilities.BuildEndpoint(
+ this.deviceConnection.Connection,
+ WindowsErrorReportingFileApi,
+ Utilities.BuildQueryString(payload));
+
+ byte[] werFile = null;
+ using (Stream stream = await this.GetAsync(uri))
+ {
+ werFile = new byte[stream.Length];
+ stream.Read(werFile, 0, werFile.Length);
+ }
+
+ return werFile;
+ }
+
+ ///
+ /// Checks if the Windows Error Reporting (WER) APIs are being called on a supported platform.
+ ///
+ private void CheckPlatformSupport()
+ {
+ switch (this.Platform)
+ {
+ case DevicePortalPlatforms.Mobile:
+ case DevicePortalPlatforms.XboxOne:
+ throw new NotSupportedException("This method is only supported on Windows Desktop, HoloLens and IoT platforms.");
+ }
+ }
+
+ ///
+ /// A list of all files contained within a Windows Error Reporting (WER) report.
+ ///
+ [DataContract]
+ public class WerFiles
+ {
+ ///
+ /// Gets a list of all files contained within a Windows Error Reporting (WER) report.
+ ///
+ [DataMember(Name = "Files")]
+ public List Files { get; private set; }
+ }
+
+ ///
+ /// Information about a Windows Error Reporting (WER) report file.
+ ///
+ [DataContract]
+ public class WerFileInformation
+ {
+ ///
+ /// Gets the name of the file.
+ ///
+ [DataMember(Name = "Name")]
+ public string Name { get; private set; }
+
+ ///
+ /// Gets the size of the file (in bytes).
+ ///
+ [DataMember(Name = "Size")]
+ public int Size { get; private set; }
+ }
+
+ ///
+ /// A list of all Windows Error Reporting (WER) reports on a device.
+ ///
+ [DataContract]
+ public class WerDeviceReports
+ {
+ ///
+ /// Gets a list of all Windows Error Reporting (WER) reports on a
+ /// device. The SYSTEM user account usually holds the bulk of the
+ /// error reports.
+ ///
+ [DataMember(Name = "WerReports")]
+ public List UserReports { get; private set; }
+
+ ///
+ /// Gets system error reports - Convenience accessor for the System error reports - this is
+ /// where most error reports end up.
+ ///
+ public WerUserReports SystemErrorReports
+ {
+ get
+ {
+ return this.UserReports.First(x => x.UserName == "SYSTEM");
+ }
+ }
+ }
+
+ ///
+ /// A list of Windows Error Reporting (WER) reports for a specific user.
+ ///
+ [DataContract]
+ public class WerUserReports
+ {
+ ///
+ /// Gets the user name.
+ ///
+ [DataMember(Name = "User")]
+ public string UserName { get; private set; }
+
+ ///
+ /// Gets a list of Windows Error Reporting (WER) reports
+ ///
+ [DataMember(Name = "Reports")]
+ public List Reports { get; private set; }
+ }
+
+ ///
+ /// Information about a Windows Error Reporting (WER) report.
+ ///
+ [DataContract]
+ public class WerReportInformation
+ {
+ ///
+ /// Gets the creation time.
+ ///
+ [DataMember(Name = "CreationTime")]
+ public ulong CreationTime { get; private set; }
+
+ ///
+ /// Gets the report name (not base64 encoded).
+ ///
+ [DataMember(Name = "Name")]
+ public string Name { get; private set; }
+
+ ///
+ /// Gets the report type ("Queue" or "Archive").
+ ///
+ [DataMember(Name = "Type")]
+ public string Type { get; private set; }
+ }
+ }
+}
diff --git a/WindowsDevicePortalWrapper/Core/WindowsPerformanceRecorder.cs b/WindowsDevicePortalWrapper/Core/WindowsPerformanceRecorder.cs
new file mode 100644
index 0000000..7573013
--- /dev/null
+++ b/WindowsDevicePortalWrapper/Core/WindowsPerformanceRecorder.cs
@@ -0,0 +1,34 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for DNS methods
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// API for starting and stopping a Windows performance recorder boot performance trace session.
+ ///
+ public static readonly string WindowsPerformanceBootTraceApi = "api/wpr/boottrace";
+
+ ///
+ /// API for starting a Windows performance recorder trace using a custom profile.
+ ///
+ public static readonly string WindowsPerformanceCustomTraceApi = "api/wpr/customtrace";
+
+ ///
+ /// API for starting and stopping a Windows performance recorder trace session.
+ ///
+ public static readonly string WindowsPerformanceTraceApi = "api/wpr/trace";
+
+ ///
+ /// API for getting the status of a Windows performance recorder trace session.
+ ///
+ public static readonly string WindowsPerformanceTraceStatusApi = "api/wpr/status";
+ }
+}
diff --git a/WindowsDevicePortalWrapper/DefaultDevicePortalConnection.cs b/WindowsDevicePortalWrapper/DefaultDevicePortalConnection.cs
new file mode 100644
index 0000000..720321a
--- /dev/null
+++ b/WindowsDevicePortalWrapper/DefaultDevicePortalConnection.cs
@@ -0,0 +1,204 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Net;
+using System.Security.Cryptography.X509Certificates;
+using static Microsoft.Tools.WindowsDevicePortal.DevicePortal;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Default implementation of the IDevicePortalConnection interface.
+ /// This implementation is designed to be compatibile with all device families.
+ ///
+ public class DefaultDevicePortalConnection : IDevicePortalConnection
+ {
+ ///
+ /// The device's root certificate.
+ ///
+ private X509Certificate2 deviceCertificate = null;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The fully qualified (ex: "https:/1.2.3.4:4321") address of the device.
+ /// The user name used in the connection credentials.
+ /// The password used in the connection credentials.
+ public DefaultDevicePortalConnection(
+ string address,
+ string userName,
+ string password)
+ {
+ this.Connection = new Uri(address);
+
+ if (!string.IsNullOrEmpty(userName) &&
+ !string.IsNullOrEmpty(password))
+ {
+ // append auto- to the credentials to bypass CSRF token requirement on non-Get requests.
+ this.Credentials = new NetworkCredential(string.Format("auto-{0}", userName), password);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class, using a SecureString to secure the password.
+ ///
+ /// device identifier
+ /// WDP username
+ /// WDP password
+ public DefaultDevicePortalConnection(
+ string address,
+ string userName,
+ System.Security.SecureString password)
+ {
+ this.Connection = new Uri(address);
+
+ if (!string.IsNullOrEmpty(userName) &&
+ password != null &&
+ password.Length > 0)
+ {
+ // append auto- to the credentials to bypass CSRF token requirement on non-Get requests.
+ this.Credentials = new NetworkCredential(string.Format("auto-{0}", userName), password);
+ }
+ }
+
+ ///
+ /// Gets the URI used to connect to the device.
+ ///
+ public Uri Connection
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets Web Socket Connection property
+ ///
+ public Uri WebSocketConnection
+ {
+ get
+ {
+ if (this.Connection == null)
+ {
+ return null;
+ }
+
+ // Convert the scheme from http[s] to ws[s].
+ string scheme = this.Connection.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase) ? "wss" : "ws";
+
+ return new Uri(
+ string.Format(
+ "{0}://{1}",
+ scheme,
+ this.Connection.Authority));
+ }
+ }
+
+ ///
+ /// Gets the credentials used to connect to the device.
+ ///
+ public NetworkCredential Credentials
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets or sets the device's operating system family.
+ ///
+ public string Family
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the operating system information.
+ ///
+ public OperatingSystemInformation OsInfo
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets the provided device certificate.
+ ///
+ /// Stored device certificate.
+ public X509Certificate2 GetDeviceCertificate()
+ {
+ return this.deviceCertificate;
+ }
+
+ ///
+ /// Stores a manually provided device certificate.
+ ///
+ /// The device's root certificate.
+ public void SetDeviceCertificate(X509Certificate2 certificate)
+ {
+ this.deviceCertificate = certificate;
+ }
+
+ ///
+ /// Updates the device's connection Uri.
+ ///
+ /// Indicates whether or not to always require a secure connection.
+ public void UpdateConnection(bool requiresHttps)
+ {
+ this.Connection = new Uri(
+ string.Format(
+ "{0}://{1}",
+ requiresHttps ? "https" : "http",
+ this.Connection.Authority));
+ }
+
+ ///
+ /// Updates the device's connection Uri.
+ ///
+ /// Object that describes the current network configuration.
+ /// True if an https connection is required, false otherwise.
+ /// True if the previous connection's port is to continue to be used, false otherwise.
+ public void UpdateConnection(
+ IpConfiguration ipConfig,
+ bool requiresHttps,
+ bool preservePort)
+ {
+ Uri newConnection = null;
+
+ foreach (NetworkAdapterInfo adapter in ipConfig.Adapters)
+ {
+ foreach (IpAddressInfo addressInfo in adapter.IpAddresses)
+ {
+ // We take the first, non-169.x.x.x address we find that is not 0.0.0.0.
+ if ((addressInfo.Address != "0.0.0.0") && !addressInfo.Address.StartsWith("169."))
+ {
+ string address = addressInfo.Address;
+ if (preservePort)
+ {
+ address = string.Format(
+ "{0}:{1}",
+ address,
+ this.Connection.Port);
+ }
+
+ newConnection = new Uri(
+ string.Format(
+ "{0}://{1}",
+ requiresHttps ? "https" : "http",
+ address));
+ break;
+ }
+ }
+
+ if (newConnection != null)
+ {
+ this.Connection = newConnection;
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/WindowsDevicePortalWrapper/DeviceInfo.cs b/WindowsDevicePortalWrapper/DeviceInfo.cs
new file mode 100644
index 0000000..bf82ecf
--- /dev/null
+++ b/WindowsDevicePortalWrapper/DeviceInfo.cs
@@ -0,0 +1,430 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// Wrappers for Device Information.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// IOT device information API.
+ ///
+ public static readonly string IoTOsInfoApi = "api/iot/device/information";
+
+ ///
+ /// IOT device timezone API.
+ ///
+ public static readonly string TimezoneInfoApi = "api/iot/device/timezones";
+
+ ///
+ /// IOT device datetime API.
+ ///
+ public static readonly string DateTimeInfoApi = "api/iot/device/datetime";
+
+ ///
+ /// IOT device Controller Driver API.
+ ///
+ public static readonly string ControllerDriverApi = "api/iot/device/controllersdriver";
+
+ ///
+ /// IOT display resolution API.
+ ///
+ public static readonly string DisplayResolutionApi = "api/iot/device/displayresolution";
+
+ ///
+ /// IOT display orientation API.
+ ///
+ public static readonly string DisplayOrientationApi = "api/iot/device/displayorientation";
+
+ ///
+ /// IOT device name API.
+ ///
+ public static readonly string DeviceNameApi = "api/iot/device/name";
+
+ ///
+ /// IOT Device password API.
+ ///
+ public static readonly string ResetPasswordApi = "api/iot/device/password";
+
+ ///
+ /// IOT remote debugging pin API.
+ ///
+ public static readonly string NewRemoteDebuggingPinApi = "api/iot/device/remotedebuggingpin";
+
+ ///
+ /// IOT set timezone API.
+ ///
+ public static readonly string SetTimeZoneApi = "api/iot/device/settimezone";
+
+ ///
+ /// Gets the IoT OS Information.
+ ///
+ /// String containing the OS information.
+ public async Task GetIoTOSInfoAsync()
+ {
+ return await this.GetAsync(IoTOsInfoApi);
+ }
+
+ ///
+ /// Gets the Timezone information.
+ ///
+ /// String containing the timezone information.
+ public async Task GetTimezoneInfoAsync()
+ {
+ return await this.GetAsync(TimezoneInfoApi);
+ }
+
+ ///
+ /// Gets the datetime information.
+ ///
+ /// String containing the datetime information.
+ public async Task GetDateTimeInfoAsync()
+ {
+ return await this.GetAsync(DateTimeInfoApi);
+ }
+
+ ///
+ /// Gets the controller driver information.
+ ///
+ /// String containing the controller driver information.
+ public async Task GetControllerDriverInfoAsync()
+ {
+ return await this.GetAsync(ControllerDriverApi);
+ }
+
+ ///
+ /// Gets the dispaly orientation information.
+ ///
+ /// String containing the dispaly orientation information.
+ public async Task GetDisplayOrientationInfoAsync()
+ {
+ return await this.GetAsync(DisplayOrientationApi);
+ }
+
+ ///
+ /// Gets the dispaly resolution information.
+ ///
+ /// String containing the dispaly resolution information.
+ public async Task GetDisplayResolutionInfoAsync()
+ {
+ return await this.GetAsync(DisplayResolutionApi);
+ }
+
+ ///
+ /// Sets the Device Name.
+ ///
+ /// Name to set for the device.
+ /// Task tracking completion of the REST call.
+ public async Task SetIoTDeviceNameAsync(string name)
+ {
+ await this.PostAsync(DeviceNameApi, string.Format("newdevicename={0}", Utilities.Hex64Encode(name)));
+ }
+
+ ///
+ /// Sets a new password.
+ ///
+ /// Old password.
+ /// New desired password.
+ /// Task tracking completion of the REST call.
+ public async Task SetNewPasswordAsync(string oldPassword, string newPassword)
+ {
+ return await this.PostAsync(
+ ResetPasswordApi,
+ string.Format("oldpassword={0}&newpassword={1}", Utilities.Hex64Encode(oldPassword), Utilities.Hex64Encode(newPassword)));
+ }
+
+ ///
+ /// Sets a new remote debugging pin.
+ ///
+ /// New pin.
+ /// Task tracking completion of the REST call.
+ public async Task SetNewRemoteDebuggingPinAsync(string newPin)
+ {
+ await this.PostAsync(
+ NewRemoteDebuggingPinApi,
+ string.Format("newpin={0}", Utilities.Hex64Encode(newPin)));
+ }
+
+ ///
+ /// Sets controllers drivers.
+ ///
+ /// Driver to set.
+ /// Task tracking completion of the REST call.
+ public async Task SetControllersDriversAsync(string newDriver)
+ {
+ return await this.PostAsync(
+ ControllerDriverApi,
+ string.Format("newdriver={0}", Utilities.Hex64Encode(newDriver)));
+ }
+
+ ///
+ /// Sets Timezone.
+ ///
+ /// Timezone index.
+ /// Task tracking completion of the REST call.
+ public async Task SetTimeZoneAsync(int index)
+ {
+ return await this.PostAsync(
+ SetTimeZoneApi,
+ string.Format("index={0}", index));
+ }
+
+ ///
+ /// Sets display resolution.
+ ///
+ /// New display resolution.
+ /// Task tracking completion of the REST call.
+ public async Task SetDisplayResolutionAsync(string displayResolution)
+ {
+ await this.PostAsync(
+ DisplayResolutionApi,
+ string.Format("newdisplayresolution={0}", Utilities.Hex64Encode(displayResolution)));
+ }
+
+ ///
+ /// Set display orientation.
+ ///
+ /// Desired orientation.
+ /// Task tracking completion of the REST call.
+ public async Task SetDisplayOrientationAsync(string displayOrientation)
+ {
+ await this.PostAsync(
+ DisplayOrientationApi,
+ string.Format("newdisplayorientation={0}", Utilities.Hex64Encode(displayOrientation)));
+ }
+ #region Data contract
+
+ ///
+ /// Operating system information.
+ ///
+ [DataContract]
+ public class IoTOSInfo
+ {
+ ///
+ /// Gets the device model
+ ///
+ [DataMember(Name = "DeviceModel")]
+ public string Model { get; private set; }
+
+ ///
+ /// Gets the device name.
+ ///
+ [DataMember(Name = "DeviceName")]
+ public string Name { get; private set; }
+
+ ///
+ /// Gets the OS version
+ ///
+ [DataMember(Name = "OSVersion")]
+ public string OSVersion { get; private set; }
+ }
+
+ ///
+ /// Timezone information.
+ ///
+ [DataContract]
+ public class TimezoneInfo
+ {
+ ///
+ /// Gets the current timezone
+ ///
+ [DataMember(Name = "Current")]
+ public Timezone CurrentTimeZone { get; private set; }
+
+ ///
+ /// Gets the list of all timezones
+ ///
+ [DataMember(Name = "Timezones")]
+ public List Timezones { get; private set; }
+ }
+
+ ///
+ /// Timezone specifications.
+ ///
+ [DataContract]
+ public partial class Timezone
+ {
+ ///
+ /// Gets the timezone description
+ ///
+ [DataMember(Name = "Description")]
+ public string Description { get; private set; }
+
+ ///
+ /// Gets the timezone index
+ ///
+ [DataMember(Name = "Index")]
+ public int Index { get; private set; }
+
+ ///
+ /// Gets the timezone name
+ ///
+ [DataMember(Name = "Name")]
+ public string Name { get; private set; }
+ }
+
+ ///
+ /// DateTime information.
+ ///
+ [DataContract]
+ public class DateTimeInfo
+ {
+ ///
+ /// Gets the current date time
+ ///
+ [DataMember(Name = "Current")]
+ public DateTimeDescription CurrentDateTime { get; private set; }
+ }
+
+ ///
+ /// Current Datetime information.
+ ///
+ [DataContract]
+ public class DateTimeDescription
+ {
+ ///
+ /// Gets the current day
+ ///
+ [DataMember(Name = "Day")]
+ public int Day { get; private set; }
+
+ ///
+ /// Gets the current hour
+ ///
+ [DataMember(Name = "Hour")]
+ public int Hour { get; private set; }
+
+ ///
+ /// Gets the current minute
+ ///
+ [DataMember(Name = "Minute")]
+ public int Min { get; private set; }
+
+ ///
+ /// Gets the current month
+ ///
+ [DataMember(Name = "Month")]
+ public int Month { get; private set; }
+
+ ///
+ /// Gets the current second
+ ///
+ [DataMember(Name = "Second")]
+ public int Sec { get; private set; }
+
+ ///
+ /// Gets the current year
+ ///
+ [DataMember(Name = "Year")]
+ public int Year { get; private set; }
+ }
+
+ ///
+ /// Controller Driver information.
+ ///
+ [DataContract]
+ public class ControllerDriverInfo
+ {
+ ///
+ /// Gets the current driver information
+ ///
+ [DataMember(Name = "CurrentDriver")]
+ public string CurrentDriver { get; private set; }
+
+ ///
+ /// Gets the list of all the controller drivers information
+ ///
+ [DataMember(Name = "ControllersDrivers")]
+ public List ControllersDrivers { get; private set; }
+
+ ///
+ /// Gets the request for reboot
+ ///
+ [DataMember(Name = "RequestReboot")]
+ public string RequestReboot { get; private set; }
+ }
+
+ ///
+ /// Dispaly orientation information.
+ ///
+ [DataContract]
+ public class DisplayOrientationInfo
+ {
+ ///
+ /// Gets the dispaly orientation information
+ ///
+ [DataMember(Name = "Orientation")]
+ public int Orientation { get; private set; }
+ }
+
+ ///
+ /// Dispaly resolution information.
+ ///
+ [DataContract]
+ public class DisplayResolutionInfo
+ {
+ ///
+ /// Gets the current display resolution
+ ///
+ [DataMember(Name = "Current")]
+ public Resolution CurrentResolution { get; private set; }
+
+ ///
+ /// Gets the list of resolution specifications
+ ///
+ [DataMember(Name = "Resolutions")]
+ public List Resolutions { get; private set; }
+ }
+
+ ///
+ /// Dispaly resolution specifications.
+ ///
+ [DataContract]
+ public class Resolution
+ {
+ ///
+ /// Gets the list of supported display resolutions
+ ///
+ [DataMember(Name = "Resolution")]
+ public string ResolutionDetail { get; private set; }
+
+ ///
+ /// Gets the index for the resolution information
+ ///
+ [DataMember(Name = "Index")]
+ public int Index { get; private set; }
+ }
+
+ ///
+ /// Error information if a request fails.
+ ///
+ [DataContract]
+ public class ErrorInformation
+ {
+ ///
+ /// Gets the error code
+ ///
+ [DataMember(Name = "ErrorCode")]
+ public int ErrorCode { get; private set; }
+
+ ///
+ /// Gets the status of the request
+ ///
+ [DataMember(Name = "Status")]
+ public string Status { get; private set; }
+ }
+
+ #endregion // Data contract
+ }
+}
diff --git a/WindowsDevicePortalWrapper/DevicePortal.cs b/WindowsDevicePortalWrapper/DevicePortal.cs
new file mode 100644
index 0000000..259c3a0
--- /dev/null
+++ b/WindowsDevicePortalWrapper/DevicePortal.cs
@@ -0,0 +1,496 @@
+//----------------------------------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+#if !WINDOWS_UWP
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+#endif // !WINDOWS_UWP
+#if WINDOWS_UWP
+using System.Runtime.InteropServices.WindowsRuntime;
+#endif // WINDOWS_UWP
+#if !WINDOWS_UWP
+using System.Security.Cryptography.X509Certificates;
+#endif // !WINDOWS_UWP
+using System.Threading;
+using System.Threading.Tasks;
+#if WINDOWS_UWP
+using Windows.Security.Cryptography.Certificates;
+using Windows.Web.Http;
+using Windows.Web.Http.Headers;
+#endif // WINDOWS_UWP
+
+namespace Microsoft.Tools.WindowsDevicePortal
+{
+ ///
+ /// This is the main DevicePortal object. It contains methods for making HTTP REST calls against
+ /// all of the WDP endpoints covered by the wrapper project. Different endpoints have their
+ /// implementation separated out into individual files.
+ ///
+ public partial class DevicePortal
+ {
+ ///
+ /// Issuer for the device certificate.
+ ///
+ public static readonly string DevicePortalCertificateIssuer = "Microsoft Windows Web Management";
+
+ ///
+ /// Endpoint used to access the certificate.
+ ///
+ public static readonly string RootCertificateEndpoint = "config/rootcertificate";
+
+ ///
+ /// Expected number of OS version sections once the OS version is split by period characters
+ ///
+ private static readonly uint ExpectedOSVersionSections = 5;
+
+ ///
+ /// The target OS version section index once the OS version is split by periods
+ ///
+ private static readonly uint TargetOSVersionSection = 3;
+
+ ///
+ /// Device connection object.
+ ///
+ private IDevicePortalConnection deviceConnection;
+#if !WINDOWS_UWP
+
+ ///
+ /// Initializes static members of the class.
+ ///
+ static DevicePortal()
+ {
+ System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
+ }
+
+#endif
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Implementation of a connection object.
+ public DevicePortal(IDevicePortalConnection connection)
+ {
+ this.deviceConnection = connection;
+ }
+
+ ///
+ /// Handler for reporting connection status.
+ ///
+ public event DeviceConnectionStatusEventHandler ConnectionStatus;
+
+ ///
+ /// HTTP Methods
+ ///
+ public enum HttpMethods
+ {
+ ///
+ /// The HTTP Get method.
+ ///
+ Get,
+
+ ///
+ /// The HTTP Put method.
+ ///
+ Put,
+
+ ///
+ /// The HTTP Post method.
+ ///
+ Post,
+
+ ///
+ /// The HTTP Delete method.
+ ///
+ Delete,
+
+ ///
+ /// The HTTP WebSocket method.
+ ///
+ WebSocket
+ }
+
+ ///
+ /// Gets the device address.
+ ///
+ public string Address
+ {
+ get { return this.deviceConnection.Connection.Authority; }
+ }
+
+ ///
+ /// Gets the status code for establishing our connection.
+ ///
+ public HttpStatusCode ConnectionHttpStatusCode
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets a description of why the connection failed.
+ ///
+ public string ConnectionFailedDescription
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the device operating system family.
+ ///
+ public string DeviceFamily
+ {
+ get
+ {
+ return (this.deviceConnection.Family != null) ? this.deviceConnection.Family : string.Empty;
+ }
+ }
+
+ ///
+ /// Gets the operating system version.
+ ///
+ public string OperatingSystemVersion
+ {
+ get
+ {
+ return (this.deviceConnection.OsInfo != null) ? this.deviceConnection.OsInfo.OsVersionString : string.Empty;
+ }
+ }
+
+ ///
+ /// Gets the device platform.
+ ///
+ public DevicePortalPlatforms Platform
+ {
+ get
+ {
+ return (this.deviceConnection.OsInfo != null) ? this.deviceConnection.OsInfo.Platform : DevicePortalPlatforms.Unknown;
+ }
+ }
+
+ ///
+ /// Gets the device platform name.
+ ///
+ public string PlatformName
+ {
+ get
+ {
+ return (this.deviceConnection.OsInfo != null) ? this.deviceConnection.OsInfo.PlatformName : "Unknown";
+ }
+ }
+
+ ///
+ /// Connects to the device pointed to by IDevicePortalConnection provided in the constructor.
+ ///
+ /// Optional network SSID.
+ /// Optional network key.
+ /// Indicates whether we should update this connection's IP address after connecting.
+ /// A manually provided X509 Certificate for trust validation against this device.
+ /// Connect sends ConnectionStatus events to indicate the current progress in the connection process.
+ /// Some applications may opt to not register for the ConnectionStatus event and await on Connect.
+ /// Task for tracking the connect.
+ [method: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "manualCertificate param doesn't really span multiple lines, it just has a different type for UWP and .NET implementations.")]
+ public async Task ConnectAsync(
+ string ssid = null,
+ string ssidKey = null,
+ bool updateConnection = false,
+#if WINDOWS_UWP
+ Certificate manualCertificate = null)
+#else
+ X509Certificate2 manualCertificate = null)
+#endif
+ {
+#if WINDOWS_UWP
+ this.ConnectionHttpStatusCode = HttpStatusCode.Ok;
+#else
+ this.ConnectionHttpStatusCode = HttpStatusCode.OK;
+#endif // WINDOWS_UWP
+ string connectionPhaseDescription = string.Empty;
+
+ if (manualCertificate != null)
+ {
+ this.SetManualCertificate(manualCertificate);
+ }
+
+ try
+ {
+ // Get the device family and operating system information.
+ connectionPhaseDescription = "Requesting operating system information";
+ this.SendConnectionStatus(
+ DeviceConnectionStatus.Connecting,
+ DeviceConnectionPhase.RequestingOperatingSystemInformation,
+ connectionPhaseDescription);
+ this.deviceConnection.Family = await this.GetDeviceFamilyAsync().ConfigureAwait(false);
+ this.deviceConnection.OsInfo = await this.GetOperatingSystemInformationAsync().ConfigureAwait(false);
+
+ // Default to using whatever was specified in the connection.
+ bool requiresHttps = this.IsUsingHttps();
+
+ // HoloLens is the only device that supports the GetIsHttpsRequired method.
+ if (this.deviceConnection.OsInfo.Platform == DevicePortalPlatforms.HoloLens)
+ {
+ // Check to see if HTTPS is required to communicate with this device.
+ connectionPhaseDescription = "Checking secure connection requirements";
+ this.SendConnectionStatus(
+ DeviceConnectionStatus.Connecting,
+ DeviceConnectionPhase.DeterminingConnectionRequirements,
+ connectionPhaseDescription);
+ requiresHttps = await this.GetIsHttpsRequiredAsync().ConfigureAwait(false);
+ }
+
+ // Connect the device to the specified network.
+ if (!string.IsNullOrWhiteSpace(ssid))
+ {
+ connectionPhaseDescription = string.Format("Connecting to {0} network", ssid);
+ this.SendConnectionStatus(
+ DeviceConnectionStatus.Connecting,
+ DeviceConnectionPhase.ConnectingToTargetNetwork,
+ connectionPhaseDescription);
+ WifiInterfaces wifiInterfaces = await this.GetWifiInterfacesAsync().ConfigureAwait(false);
+
+ // TODO - consider what to do if there is more than one wifi interface on a device
+ await this.ConnectToWifiNetworkAsync(wifiInterfaces.Interfaces[0].Guid, ssid, ssidKey).ConfigureAwait(false);
+ }
+
+ // Get the device's IP configuration and update the connection as appropriate.
+ if (updateConnection)
+ {
+ connectionPhaseDescription = "Updating device connection";
+ this.SendConnectionStatus(
+ DeviceConnectionStatus.Connecting,
+ DeviceConnectionPhase.UpdatingDeviceAddress,
+ connectionPhaseDescription);
+
+ bool preservePort = true;
+
+ // HoloLens and Mobile are the only devices that support USB.
+ // They require the port to be changed when the connection is updated
+ // to WiFi.
+ if ((this.Platform == DevicePortalPlatforms.HoloLens) ||
+ (this.Platform == DevicePortalPlatforms.Mobile))
+ {
+ preservePort = false;
+ }
+
+ this.deviceConnection.UpdateConnection(
+ await this.GetIpConfigAsync().ConfigureAwait(false),
+ requiresHttps,
+ preservePort);
+ }
+
+ this.SendConnectionStatus(
+ DeviceConnectionStatus.Connected,
+ DeviceConnectionPhase.Idle,
+ "Device connection established");
+ }
+ catch (Exception e)
+ {
+ DevicePortalException dpe = e as DevicePortalException;
+
+ if (dpe != null)
+ {
+ this.ConnectionHttpStatusCode = dpe.StatusCode;
+ this.ConnectionFailedDescription = dpe.Message;
+ }
+ else
+ {
+ this.ConnectionHttpStatusCode = HttpStatusCode.Conflict;
+
+ // Get to the innermost exception for our return message.
+ Exception innermostException = e;
+ while (innermostException.InnerException != null)
+ {
+ innermostException = innermostException.InnerException;
+ await Task.Yield();
+ }
+
+ this.ConnectionFailedDescription = innermostException.Message;
+ }
+
+ this.SendConnectionStatus(
+ DeviceConnectionStatus.Failed,
+ DeviceConnectionPhase.Idle,
+ string.Format("Device connection failed: {0}, {1}", connectionPhaseDescription, this.ConnectionFailedDescription));
+ }
+ }
+
+ ///
+ /// Helper method used for saving the content of a response to a file.
+ /// This allows unittests to easily generate real data to use as mock responses.
+ ///
+ /// API endpoint we are calling.
+ /// Directory to store our file.
+ /// The http method to be performed.
+ /// An optional stream to use for the request body content.
+ /// The content type of the request stream.
+ /// Task waiting for HTTP call to return and file copy to complete.
+ public async Task SaveEndpointResponseToFileAsync(
+ string endpoint,
+ string directory,
+ HttpMethods httpMethod,
+ Stream requestBody = null,
+ string requestBodyContentType = null)
+ {
+ Uri uri = new Uri(this.deviceConnection.Connection, endpoint);
+
+ // Convert the OS version, such as 14385.1002.amd64fre.rs1_xbox_rel_1608.160709-1700, into a friendly OS version, such as rs1_xbox_rel_1608
+ string friendlyOSVersion = this.OperatingSystemVersion;
+ string[] versionParts = friendlyOSVersion.Split('.');
+ if (versionParts.Length == ExpectedOSVersionSections)
+ {
+ friendlyOSVersion = versionParts[TargetOSVersionSection];
+ }
+
+ // Create the filename as DeviceFamily_OSVersion.dat, replacing '/', '.', and '-' with '_' so
+ // we can create a class with the same name as this Device/OS pair for tests.
+ string filename = endpoint + "_" + this.Platform.ToString() + "_" + friendlyOSVersion;
+
+ if (httpMethod != HttpMethods.Get)
+ {
+ filename = httpMethod.ToString() + "_" + filename;
+ }
+
+ Utilities.ModifyEndpointForFilename(ref filename);
+
+ filename += ".dat";
+ string filepath = Path.Combine(directory, filename);
+
+ if (HttpMethods.WebSocket == httpMethod)
+ {
+#if WINDOWS_UWP
+ WebSocket