Add files via upload
This commit is contained in:
Родитель
7b998cf109
Коммит
aeb452583a
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
|
||||
</startup>
|
||||
</configuration>
|
|
@ -0,0 +1,117 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{A1A457E2-2559-4400-9C37-9ABC3D8D5D35}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>DevicePortalTool</RootNamespace>
|
||||
<AssemblyName>DevicePortalTool</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>DevicePortalTool.Program</StartupObject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Net.Http.WebRequest" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Source\AppOperation.cs" />
|
||||
<Compile Include="Source\AppxManifest.cs" />
|
||||
<Compile Include="Source\PackageHelper.cs" />
|
||||
<Compile Include="Source\ParameterHelper.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\AppDeployment.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\ApplicationManager.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\CertificateHandling.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\AppCrashDumpCollection.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\AppDeployment.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\AppFileExplorer.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\DeviceManager.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\Dns-Sd.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\DumpCollection.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\Etw.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\Networking.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\OsInformation.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\PerformanceData.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\Power.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\RemoteControl.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\TaskManager.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\WiFiManagement.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\WindowsErrorReporting.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Core\WindowsPerformanceRecorder.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\DefaultDevicePortalConnection.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\DeviceInfo.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\DevicePortal.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Events\ApplicationInstallStatus.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Events\ConnectionStatus.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Events\WebSocketMessageReceivedEventArgs.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Exceptions\DevicePortalException.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HoloLens\HolographicOs.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HoloLens\HolographicPerception.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HoloLens\HolographicThermal.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HoloLens\MixedRealityCapture.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HoloLens\PerceptionSimulationPlayback.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HoloLens\PerceptionSimulationRecording.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HttpMultipartFileContent.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HttpRest\HttpHeadersHelper.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HttpRest\RequestHelpers.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HttpRest\ResponseHelpers.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HttpRest\RestDelete.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HttpRest\RestGet.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HttpRest\RestPost.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HttpRest\RestPut.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\HttpRest\WebSocket.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Interfaces\IDevicePortalConnection.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\RestDelete.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\RestGet.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\RestPost.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\RestPut.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\UnvalidatedCert.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\Utilities.cs" />
|
||||
<Compile Include="WindowsDevicePortalWrapper\WebSocket.cs" />
|
||||
<Compile Include="Source\Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -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
|
|
@ -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")]
|
|
@ -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.
|
||||
|
|
@ -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:<operation> [<operation arguments>]\n"
|
||||
+ " Executes the app operation with operation-specific parameters\n"
|
||||
+ "\n"
|
||||
+ " /op:<operation> /?\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:<path to Appx file> [/cert:<path to certificate file>] [/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:<full package name> /aumid:<app's user model ID>\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:<app package name>\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<AppPackages> 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<PackageInfo> 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<AppPackages> 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<PackageInfo> matchingPackages)
|
||||
{
|
||||
if (_verbose)
|
||||
{
|
||||
Console.Out.WriteLine("Attempting to query installed packages matching app");
|
||||
}
|
||||
|
||||
matchingPackages = null;
|
||||
bool successful = false;
|
||||
|
||||
try
|
||||
{
|
||||
Task<AppPackages> 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<AppPackages> packagesTask = _portal.GetInstalledAppPackagesAsync();
|
||||
packagesTask.Wait();
|
||||
|
||||
appPackage = packagesTask.Result.Packages.FirstOrDefault(package => (package.FullName == fullPackageName));
|
||||
}
|
||||
catch {; }
|
||||
|
||||
numAttempts--;
|
||||
|
||||
} while (appPackage == null && numAttempts > 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<string, AppxManifest> ManifestCache = new Dictionary<string, AppxManifest>();
|
||||
|
||||
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<string> 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<string>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="ParameterHelper.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
// <copyright file="ParameterHelper.cs" company="Unity Technologies">
|
||||
// Modified under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
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<string, string> parameters = new Dictionary<string, string>();
|
||||
private List<string> flags = new List<string>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <operation area> /op:<operation type> [operation parameters]] /ip:<device IP address> [/stdincred | /user:WDP username /pwd:<WDP password] [/v]\n"
|
||||
+ " <operation area> - 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 <operation area> /?\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)://<host address>:<WDP port>");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="AppDeployment.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// MOCK implementation of App Deployment methods.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for getting installation status.
|
||||
/// </summary>
|
||||
/// <returns>The status</returns>
|
||||
public async Task<ApplicationInstallStatus> GetInstallStatusAsync()
|
||||
{
|
||||
ApplicationInstallStatus status = ApplicationInstallStatus.Completed;
|
||||
|
||||
return await Task.FromResult(status);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="ApplicationManager.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for Application Management.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// IoT device application list API.
|
||||
/// </summary>
|
||||
public static readonly string AppsListApi = "api/iot/appx/default";
|
||||
|
||||
/// <summary>
|
||||
/// IoT device headless application list API.
|
||||
/// </summary>
|
||||
public static readonly string HeadlessAppsListApi = "api/iot/appx/listHeadlessApps";
|
||||
|
||||
/// <summary>
|
||||
/// IoT device headless startup application API.
|
||||
/// </summary>
|
||||
public static readonly string HeadlessStartupAppApi = "api/iot/appx/startupHeadlessApp";
|
||||
|
||||
/// <summary>
|
||||
/// IoT device package activation API.
|
||||
/// </summary>
|
||||
public static readonly string ActivatePackageApi = "api/iot/appx/app";
|
||||
|
||||
/// <summary>
|
||||
/// Gets List of apps.
|
||||
/// </summary>
|
||||
/// <returns>Object containing the list of applications.</returns>
|
||||
public async Task<AppsListInfo> GetAppsListInfoAsync()
|
||||
{
|
||||
return await this.GetAsync<AppsListInfo>(AppsListApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets list of headless apps.
|
||||
/// </summary>
|
||||
/// <returns>Object containing the list of headless applications.</returns>
|
||||
public async Task<HeadlessAppsListInfo> GetHeadlessAppsListInfoAsync()
|
||||
{
|
||||
return await this.GetAsync<HeadlessAppsListInfo>(HeadlessAppsListApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets selected app as the startup app.
|
||||
/// </summary>
|
||||
/// <param name="appId">App Id.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
public async Task UpdateStartupAppAsync(string appId)
|
||||
{
|
||||
await this.PostAsync(
|
||||
AppsListApi,
|
||||
string.Format("appid={0}", Utilities.Hex64Encode(appId)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the selected app as the headless startup app.
|
||||
/// </summary>
|
||||
/// <param name="appId">App Id.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
public async Task UpdateHeadlessStartupAppAsync(string appId)
|
||||
{
|
||||
await this.PostAsync(
|
||||
HeadlessStartupAppApi,
|
||||
string.Format("appid={0}", Utilities.Hex64Encode(appId)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the selected app from the headless startup app list.
|
||||
/// </summary>
|
||||
/// <param name="appId">App Id.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
public async Task RemoveHeadlessStartupAppAsync(string appId)
|
||||
{
|
||||
await this.DeleteAsync(
|
||||
HeadlessStartupAppApi,
|
||||
string.Format("appid={0}", Utilities.Hex64Encode(appId)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activiates the selected app package.
|
||||
/// </summary>
|
||||
/// <param name="appId">App Id.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
public async Task ActivatePackageAsync(string appId)
|
||||
{
|
||||
await this.PostAsync(
|
||||
ActivatePackageApi,
|
||||
string.Format("appid={0}", Utilities.Hex64Encode(appId)));
|
||||
}
|
||||
#region Data contract
|
||||
|
||||
/// <summary>
|
||||
/// Application list info.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class AppsListInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default application
|
||||
/// </summary>
|
||||
[DataMember(Name = "DefaultApp")]
|
||||
public string DefaultApp { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the application packages
|
||||
/// </summary>
|
||||
[DataMember(Name = "AppPackages")]
|
||||
public List<AppPackage> AppPackages { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Application package.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class AppPackage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the app is the startup app
|
||||
/// </summary>
|
||||
[DataMember(Name = "IsStartup")]
|
||||
public bool IsStartup { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the complate package name
|
||||
/// </summary>
|
||||
[DataMember(Name = "PackageFullName")]
|
||||
public string PackageFullName { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Headless app list information.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class HeadlessAppsListInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of headless application packages
|
||||
/// </summary>
|
||||
[DataMember(Name = "AppPackages")]
|
||||
public List<AppPackage> AppPackages { get; private set; }
|
||||
}
|
||||
|
||||
#endregion // Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="CertificateHandling.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
{
|
||||
/// <content>
|
||||
/// .net 4.x implementation of device certificate handling methods.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// A manually provided certificate for trust validation.
|
||||
/// </summary>
|
||||
private X509Certificate2 manualCertificate = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets handler for untrusted certificate handling
|
||||
/// </summary>
|
||||
public event UnvalidatedCertEventHandler UnvalidatedCert;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root certificate from the device.
|
||||
/// </summary>
|
||||
/// <returns>The device certificate.</returns>
|
||||
public async Task<X509Certificate2> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the manual certificate.
|
||||
/// </summary>
|
||||
/// <param name="cert">Manual certificate</param>
|
||||
private void SetManualCertificate(X509Certificate2 cert)
|
||||
{
|
||||
this.manualCertificate = cert;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate the server certificate
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender object</param>
|
||||
/// <param name="certificate">The server's certificate</param>
|
||||
/// <param name="chain">The cert chain</param>
|
||||
/// <param name="sslPolicyErrors">Policy Errors</param>
|
||||
/// <returns>whether the cert passes validation</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="AppCrashDumpCollection.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for app crash dump collection methods.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API to retrieve list of the available crash dumps (for sideloaded applications).
|
||||
/// </summary>
|
||||
public static readonly string AvailableCrashDumpsApi = "api/debug/dump/usermode/dumps";
|
||||
|
||||
/// <summary>
|
||||
/// API to download or delete a crash dump file (for a sideloaded application).
|
||||
/// </summary>
|
||||
public static readonly string CrashDumpFileApi = "api/debug/dump/usermode/crashdump";
|
||||
|
||||
/// <summary>
|
||||
/// API to control the crash dump settings for a sideloaded application.
|
||||
/// </summary>
|
||||
public static readonly string CrashDumpSettingsApi = "api/debug/dump/usermode/crashcontrol";
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of app crash dumps on the device.
|
||||
/// </summary>
|
||||
/// <returns>List of AppCrashDump objects, which represent crashdumps on the device. </returns>
|
||||
public async Task<List<AppCrashDump>> GetAppCrashDumpListAsync()
|
||||
{
|
||||
AppCrashDumpList cdl = await this.GetAsync<AppCrashDumpList>(AvailableCrashDumpsApi);
|
||||
return cdl.CrashDumps;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download a sideloaded app's crash dump.
|
||||
/// </summary>
|
||||
/// <param name="crashdump"> The AppCrashDump to download</param>
|
||||
/// <returns>Stream of the crash dump</returns>
|
||||
public async Task<Stream> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete an app crash dump stored on the device.
|
||||
/// </summary>
|
||||
/// <param name="crashdump">The crashdump to be deleted</param>
|
||||
/// <returns>Task tracking completion of the request.</returns>
|
||||
public async Task DeleteAppCrashDumpAsync(AppCrashDump crashdump)
|
||||
{
|
||||
await this.DeleteAsync(
|
||||
CrashDumpFileApi,
|
||||
string.Format("packageFullName={0}&fileName={1}", crashdump.PackageFullName, crashdump.Filename));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the crash settings for a sideloaded app.
|
||||
/// </summary>
|
||||
/// <param name="app">The app to get settings for</param>
|
||||
/// <returns>The crash settings for the app</returns>
|
||||
public async Task<AppCrashDumpSettings> GetAppCrashDumpSettingsAsync(AppPackage app)
|
||||
{
|
||||
return await this.GetAppCrashDumpSettingsAsync(app.PackageFullName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the crash settings for a sideloaded app.
|
||||
/// </summary>
|
||||
/// <param name="packageFullname">The app to get settings for</param>
|
||||
/// <returns>The crash settings for the app</returns>
|
||||
public async Task<AppCrashDumpSettings> GetAppCrashDumpSettingsAsync(string packageFullname)
|
||||
{
|
||||
return await this.GetAsync<AppCrashDumpSettings>(
|
||||
CrashDumpSettingsApi,
|
||||
string.Format("packageFullName={0}", packageFullname));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the crash settings for a sideloaded app.
|
||||
/// </summary>
|
||||
/// <param name="app">The app to set crash settings for.</param>
|
||||
/// <param name="enable">Whether to enable or disable crash collection for the app. </param>
|
||||
/// <returns>Task tracking completion of the request.</returns>
|
||||
public async Task SetAppCrashDumpSettingsAsync(AppPackage app, bool enable = true)
|
||||
{
|
||||
string pfn = app.PackageFullName;
|
||||
await this.SetAppCrashDumpSettingsAsync(pfn, enable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the crash settings for a sideloaded app.
|
||||
/// </summary>
|
||||
/// <param name="packageFullName">The app to set crash settings for.</param>
|
||||
/// <param name="enable">Whether to enable or disable crash collection for the app. </param>
|
||||
/// <returns>Task tracking completion of the request.</returns>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Per-app crash dump settings.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class AppCrashDumpSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether crash dumps are enabled for the app
|
||||
/// </summary>
|
||||
[DataMember(Name = "CrashDumpEnabled")]
|
||||
public bool CrashDumpEnabled
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a crash dump collected from a sideloaded app.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class AppCrashDump
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the timestamp of the crash as a string.
|
||||
/// </summary>
|
||||
[DataMember(Name = "FileDate")]
|
||||
public string FileDateAsString
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timestamp of the crash.
|
||||
/// </summary>
|
||||
public DateTime FileDate
|
||||
{
|
||||
get
|
||||
{
|
||||
return DateTime.Parse(this.FileDateAsString);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the filename of the crash file.
|
||||
/// </summary>
|
||||
[DataMember(Name = "FileName")]
|
||||
public string Filename
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the crash dump, in bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "FileSize")]
|
||||
public uint FileSizeInBytes
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the package full name of the app that crashed.
|
||||
/// </summary>
|
||||
[DataMember(Name = "PackageFullName")]
|
||||
public string PackageFullName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of crash dumps. Internal usage only.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
private class AppCrashDumpList
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of crash dumps on the device.
|
||||
/// </summary>
|
||||
[DataMember(Name = "CrashDumps")]
|
||||
public List<AppCrashDump> CrashDumps
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
}
|
||||
#endregion Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,411 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="AppDeployment.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for App Deployment methods.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API to retrieve list of installed packages.
|
||||
/// </summary>
|
||||
public static readonly string InstalledPackagesApi = "api/app/packagemanager/packages";
|
||||
|
||||
/// <summary>
|
||||
/// Install state API.
|
||||
/// </summary>
|
||||
public static readonly string InstallStateApi = "api/app/packagemanager/state";
|
||||
|
||||
/// <summary>
|
||||
/// API for package management.
|
||||
/// </summary>
|
||||
public static readonly string PackageManagerApi = "api/app/packagemanager/package";
|
||||
|
||||
/// <summary>
|
||||
/// App Install Status handler.
|
||||
/// </summary>
|
||||
public event ApplicationInstallStatusEventHandler AppInstallStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of applications installed on the device.
|
||||
/// </summary>
|
||||
/// <returns>AppPackages object containing the list of installed application packages.</returns>
|
||||
public async Task<AppPackages> GetInstalledAppPackagesAsync()
|
||||
{
|
||||
return await this.GetAsync<AppPackages>(InstalledPackagesApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs an application
|
||||
/// </summary>
|
||||
/// <param name="appName">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.</param>
|
||||
/// <param name="packageFileName">Full name of the application package file.</param>
|
||||
/// <param name="dependencyFileNames">List containing the full names of any required dependency files.</param>
|
||||
/// <param name="certificateFileName">Full name of the optional certificate file.</param>
|
||||
/// <param name="stateCheckIntervalMs">How frequently we should check the installation state.</param>
|
||||
/// <param name="timeoutInMinutes">Operation timeout.</param>
|
||||
/// <param name="uninstallPreviousVersion">Indicate whether or not the previous app version should be uninstalled prior to installing.</param>
|
||||
/// <remarks>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.</remarks>
|
||||
/// <returns>Task for tracking completion of install initialization.</returns>
|
||||
public async Task InstallApplicationAsync(
|
||||
string appName,
|
||||
string packageFileName,
|
||||
List<string> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uninstalls the specified application.
|
||||
/// </summary>
|
||||
/// <param name="packageName">The name of the application package to uninstall.</param>
|
||||
/// <returns>Task tracking the uninstall operation.</returns>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the application installation Uri and generates a unique boundary string for the multipart form data.
|
||||
/// </summary>
|
||||
/// <param name="packageName">The name of the application package.</param>
|
||||
/// <param name="uri">The endpoint for the install request.</param>
|
||||
/// <param name="boundaryString">Unique string used to separate the parts of the multipart form data.</param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends application install status.
|
||||
/// </summary>
|
||||
/// <param name="status">Status of the installation.</param>
|
||||
/// <param name="phase">Current installation phase (ex: Uninstalling previous version)</param>
|
||||
/// <param name="message">Optional error message describing the install status.</param>
|
||||
private void SendAppInstallStatus(
|
||||
ApplicationInstallStatus status,
|
||||
ApplicationInstallPhase phase,
|
||||
string message = "")
|
||||
{
|
||||
this.AppInstallStatus?.Invoke(
|
||||
this,
|
||||
new ApplicationInstallStatusEventArgs(status, phase, message));
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
/// <summary>
|
||||
/// Object representing a list of Application Packages
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class AppPackages
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of the packages
|
||||
/// </summary>
|
||||
[DataMember(Name = "InstalledPackages")]
|
||||
public List<PackageInfo> Packages { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Presents a user readable representation of a list of AppPackages
|
||||
/// </summary>
|
||||
/// <returns>User readable list of AppPackages.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
string output = "Packages:\n";
|
||||
foreach (PackageInfo package in this.Packages)
|
||||
{
|
||||
output += package;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representing the install state
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class InstallState
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets install state code
|
||||
/// </summary>
|
||||
[DataMember(Name = "Code")]
|
||||
public int Code { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets message text
|
||||
/// </summary>
|
||||
[DataMember(Name = "CodeText")]
|
||||
public string CodeText { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets reason for state
|
||||
/// </summary>
|
||||
[DataMember(Name = "Reason")]
|
||||
public string Reason { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this was successful
|
||||
/// </summary>
|
||||
[DataMember(Name = "Success")]
|
||||
public bool WasSuccessful { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// object representing the package information
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class PackageInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets package name
|
||||
/// </summary>
|
||||
[DataMember(Name = "Name")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets package family name
|
||||
/// </summary>
|
||||
[DataMember(Name = "PackageFamilyName")]
|
||||
public string FamilyName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets package full name
|
||||
/// </summary>
|
||||
[DataMember(Name = "PackageFullName")]
|
||||
public string FullName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets package relative Id
|
||||
/// </summary>
|
||||
[DataMember(Name = "PackageRelativeId")]
|
||||
public string AppId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets package publisher
|
||||
/// </summary>
|
||||
[DataMember(Name = "Publisher")]
|
||||
public string Publisher { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets package version
|
||||
/// </summary>
|
||||
[DataMember(Name = "Version")]
|
||||
public PackageVersion Version { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
[DataMember(Name = "PackageOrigin")]
|
||||
public int PackageOrigin { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to determine if the app was sideloaded and therefore can be used with e.g. GetFolderContentsAsync
|
||||
/// </summary>
|
||||
/// <returns> True if the package is sideloaded. </returns>
|
||||
public bool IsSideloaded()
|
||||
{
|
||||
return this.PackageOrigin == 4 || this.PackageOrigin == 5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a string representation of the package
|
||||
/// </summary>
|
||||
/// <returns>String representation</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("\t{0}\n\t\t{1}\n", this.FullName, this.AppId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representing a package version
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class PackageVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets version build
|
||||
/// </summary>
|
||||
[DataMember(Name = "Build")]
|
||||
public int Build { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets package Major number
|
||||
/// </summary>
|
||||
[DataMember(Name = "Major")]
|
||||
public int Major { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets package minor number
|
||||
/// </summary>
|
||||
[DataMember(Name = "Minor")]
|
||||
public int Minor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets package revision
|
||||
/// </summary>
|
||||
[DataMember(Name = "Revision")]
|
||||
public int Revision { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets package version
|
||||
/// </summary>
|
||||
public Version Version
|
||||
{
|
||||
get { return new Version(this.Major, this.Minor, this.Build, this.Revision); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a string representation of a version
|
||||
/// </summary>
|
||||
/// <returns>String representation</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Version.ToString();
|
||||
}
|
||||
}
|
||||
#endregion // Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,351 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="AppFileExplorer.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for App File explorer methods
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API to upload, download or delete a file in a folder.
|
||||
/// </summary>
|
||||
public static readonly string GetFileApi = "api/filesystem/apps/file";
|
||||
|
||||
/// <summary>
|
||||
/// API to rename a file in a folder.
|
||||
/// </summary>
|
||||
public static readonly string RenameFileApi = "api/filesystem/apps/rename";
|
||||
|
||||
/// <summary>
|
||||
/// API to retrieve the list of files in a folder.
|
||||
/// </summary>
|
||||
public static readonly string GetFilesApi = "api/filesystem/apps/files";
|
||||
|
||||
/// <summary>
|
||||
/// API to retrieve the list of accessible top-level folders.
|
||||
/// </summary>
|
||||
public static readonly string KnownFoldersApi = "api/filesystem/apps/knownfolders";
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of Known Folders on the device.
|
||||
/// </summary>
|
||||
/// <returns>List of known folders available on this device.</returns>
|
||||
public async Task<KnownFolders> GetKnownFoldersAsync()
|
||||
{
|
||||
return await this.GetAsync<KnownFolders>(KnownFoldersApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of files in a Known Folder (e.g. LocalAppData).
|
||||
/// </summary>
|
||||
/// <param name="knownFolderId">The known folder id for the root of the path.</param>
|
||||
/// <param name="subPath">An optional subpath to the folder.</param>
|
||||
/// <param name="packageFullName">The package full name if using LocalAppData.</param>
|
||||
/// <returns>Contents of the requested folder.</returns>
|
||||
public async Task<FolderContents> GetFolderContentsAsync(
|
||||
string knownFolderId,
|
||||
string subPath = null,
|
||||
string packageFullName = null)
|
||||
{
|
||||
Dictionary<string, string> payload = this.BuildCommonFilePayload(knownFolderId, subPath, packageFullName);
|
||||
|
||||
return await this.GetAsync<FolderContents>(GetFilesApi, Utilities.BuildQueryString(payload));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a file from LocalAppData or another Known Folder on the device.
|
||||
/// </summary>
|
||||
/// <param name="knownFolderId">The known folder id for the root of the path.</param>
|
||||
/// <param name="filename">The name of the file we are downloading.</param>
|
||||
/// <param name="subPath">An optional subpath to the folder.</param>
|
||||
/// <param name="packageFullName">The package full name if using LocalAppData.</param>
|
||||
/// <returns>Stream to the downloaded file.</returns>
|
||||
public async Task<Stream> GetFileAsync(
|
||||
string knownFolderId,
|
||||
string filename,
|
||||
string subPath = null,
|
||||
string packageFullName = null)
|
||||
{
|
||||
Dictionary<string, string> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a file to a Known Folder (e.g. LocalAppData)
|
||||
/// </summary>
|
||||
/// <param name="knownFolderId">The known folder id for the root of the path.</param>
|
||||
/// <param name="filepath">The path to the file we are uploading.</param>
|
||||
/// <param name="subPath">An optional subpath to the folder.</param>
|
||||
/// <param name="packageFullName">The package full name if using LocalAppData.</param>
|
||||
/// <returns>Task tracking completion of the upload request.</returns>
|
||||
public async Task UploadFileAsync(
|
||||
string knownFolderId,
|
||||
string filepath,
|
||||
string subPath = null,
|
||||
string packageFullName = null)
|
||||
{
|
||||
Dictionary<string, string> payload = this.BuildCommonFilePayload(knownFolderId, subPath, packageFullName);
|
||||
|
||||
List<string> files = new List<string>();
|
||||
files.Add(filepath);
|
||||
|
||||
await this.PostAsync(GetFileApi, files, Utilities.BuildQueryString(payload));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a file from a Known Folder.
|
||||
/// </summary>
|
||||
/// <param name="knownFolderId">The known folder id for the root of the path.</param>
|
||||
/// <param name="filename">The name of the file we are deleting.</param>
|
||||
/// <param name="subPath">An optional subpath to the folder.</param>
|
||||
/// <param name="packageFullName">The package full name if using LocalAppData.</param>
|
||||
/// <returns>Task tracking completion of the delete request.</returns>
|
||||
public async Task DeleteFileAsync(
|
||||
string knownFolderId,
|
||||
string filename,
|
||||
string subPath = null,
|
||||
string packageFullName = null)
|
||||
{
|
||||
Dictionary<string, string> payload = this.BuildCommonFilePayload(knownFolderId, subPath, packageFullName);
|
||||
filename = WebUtility.UrlEncode(filename);
|
||||
payload.Add("filename", filename);
|
||||
|
||||
await this.DeleteAsync(GetFileApi, Utilities.BuildQueryString(payload));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renames a file in a Known Folder.
|
||||
/// </summary>
|
||||
/// <param name="knownFolderId">The known folder id for the root of the path.</param>
|
||||
/// <param name="filename">The name of the file we are renaming.</param>
|
||||
/// <param name="newFilename">The new name for this file.</param>
|
||||
/// <param name="subPath">An optional subpath to the folder.</param>
|
||||
/// <param name="packageFullName">The package full name if using LocalAppData.</param>
|
||||
/// <returns>Task tracking completion of the rename request.</returns>
|
||||
public async Task RenameFileAsync(
|
||||
string knownFolderId,
|
||||
string filename,
|
||||
string newFilename,
|
||||
string subPath = null,
|
||||
string packageFullName = null)
|
||||
{
|
||||
Dictionary<string, string> payload = this.BuildCommonFilePayload(knownFolderId, subPath, packageFullName);
|
||||
|
||||
payload.Add("filename", filename);
|
||||
payload.Add("newfilename", newFilename);
|
||||
|
||||
await this.PostAsync(RenameFileApi, Utilities.BuildQueryString(payload));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do some common parsing and validation of file explorer parameters.
|
||||
/// </summary>
|
||||
/// <param name="knownFolderId">The known folder id.</param>
|
||||
/// <param name="subPath">The optional subpath for the folder.</param>
|
||||
/// <param name="packageFullName">The packagefullname if using LocalAppData.</param>
|
||||
/// <returns>Dictionary of param name to value.</returns>
|
||||
private Dictionary<string, string> BuildCommonFilePayload(string knownFolderId, string subPath, string packageFullName)
|
||||
{
|
||||
Dictionary<string, string> payload = new Dictionary<string, string>();
|
||||
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Known Folders object.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class KnownFolders
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of known folders.
|
||||
/// </summary>
|
||||
[DataMember(Name = "KnownFolders")]
|
||||
public List<string> Folders { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Overridden ToString method providing a user readable
|
||||
/// list of known folders.
|
||||
/// </summary>
|
||||
/// <returns>String representation of the object.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Folder contents object.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class FolderContents
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of folders and files in this folder.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Items")]
|
||||
public List<FileOrFolderInformation> Contents { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Overridden ToString method providing a user readable
|
||||
/// display of a folder's contents. Tries to match the formatting
|
||||
/// of regular DIR commands.
|
||||
/// </summary>
|
||||
/// <returns>String representation of the object.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Details about a folder or file.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class FileOrFolderInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current directory.
|
||||
/// </summary>
|
||||
[DataMember(Name = "CurrentDir")]
|
||||
public string CurrentDir { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current directory.
|
||||
/// </summary>
|
||||
[DataMember(Name = "DateCreated")]
|
||||
public long DateCreated { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Id.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Id")]
|
||||
public string Id { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Name.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Name")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SubPath (equivalent to CurrentDir for files).
|
||||
/// </summary>
|
||||
[DataMember(Name = "SubPath")]
|
||||
public string SubPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Type.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Type")]
|
||||
public int Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the file (0 for folders).
|
||||
/// </summary>
|
||||
[DataMember(Name = "FileSize")]
|
||||
public long SizeInBytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public bool IsFolder
|
||||
{
|
||||
get
|
||||
{
|
||||
return (this.Type & 0x10) == 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overridden ToString method providing a user readable
|
||||
/// display of a file or folder. Tries to match the formatting
|
||||
/// of regular DIR commands.
|
||||
/// </summary>
|
||||
/// <returns>String representation of the object.</returns>
|
||||
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, "<DIR>", 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="DeviceManager.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for device management methods.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API to retrieve list of installed devices.
|
||||
/// </summary>
|
||||
public static readonly string InstalledDevicesApi = "api/devicemanager/devices";
|
||||
|
||||
/// <summary>
|
||||
/// Get a listing of installed devices
|
||||
/// </summary>
|
||||
/// <returns>List of installed devices</returns>
|
||||
public async Task<List<Device>> GetDeviceListAsync()
|
||||
{
|
||||
DeviceList deviceList = await this.GetAsync<DeviceList>(InstalledDevicesApi);
|
||||
return deviceList.Devices;
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
/// <summary>
|
||||
/// Object representing a device entry
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class DeviceList
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Device Class
|
||||
/// </summary>
|
||||
[DataMember(Name = "DeviceList")]
|
||||
public List<Device> Devices { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representing a device entry
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class Device
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Device Class
|
||||
/// </summary>
|
||||
[DataMember(Name = "Class")]
|
||||
public string Class { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Device Description
|
||||
/// </summary>
|
||||
[DataMember(Name = "Description")]
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the friendly (human-readable) name for the device. Usually more descriptive than Description. Does not apply to all Devices.
|
||||
/// </summary>
|
||||
[DataMember(Name = "FriendlyName")]
|
||||
public string FriendlyName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Device ID
|
||||
/// </summary>
|
||||
[DataMember(Name = "ID")]
|
||||
public string ID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Device Manufacturer
|
||||
/// </summary>
|
||||
[DataMember(Name = "Manufacturer")]
|
||||
public string Manufacturer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Device ParentID, used for pairing
|
||||
/// </summary>
|
||||
[DataMember(Name = "ParentID")]
|
||||
public string ParentID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Device Problem Code
|
||||
/// </summary>
|
||||
[DataMember(Name = "ProblemCode")]
|
||||
public int ProblemCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Device Status Code
|
||||
/// </summary>
|
||||
[DataMember(Name = "StatusCode")]
|
||||
public int StatusCode { get; private set; }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="Dns-Sd.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for DNS methods
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API to add or delete a tag to the DNS-SD advertisement.
|
||||
/// </summary>
|
||||
public static readonly string TagApi = "api/dns-sd/tag";
|
||||
|
||||
/// <summary>
|
||||
/// API to retrieve or delete the currently applied tags for the device.
|
||||
/// </summary>
|
||||
public static readonly string TagsApi = "api/dns-sd/tags";
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of DNS-SD tags being broadcast by this device.
|
||||
/// </summary>
|
||||
/// <returns>Array of strings, each one an individual tag.</returns>
|
||||
public async Task<List<string>> GetServiceTagsAsync()
|
||||
{
|
||||
ServiceTags tags = await this.GetAsync<ServiceTags>(TagsApi);
|
||||
return tags.Tags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a tag to this device's DNS-SD broadcast.
|
||||
/// </summary>
|
||||
/// <param name="tagValue">The tag to assign to the device.</param>
|
||||
/// <returns>Task tracking adding the tag.</returns>
|
||||
public async Task AddServiceTagAsync(string tagValue)
|
||||
{
|
||||
await this.PostAsync(
|
||||
TagApi,
|
||||
string.Format("tagValue={0}", tagValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete all tags from the device's DNS-SD broadcast.
|
||||
/// </summary>
|
||||
/// <returns>Task tracking deletion of all tags.</returns>
|
||||
public async Task DeleteAllTagsAsync()
|
||||
{
|
||||
await this.DeleteAsync(TagsApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a specific tag from the device's DNS-SD broadcast.
|
||||
/// </summary>
|
||||
/// <param name="tagValue">The tag to delete from the device broadcast.</param>
|
||||
/// <returns>Task tracking deletion of the tag.</returns>
|
||||
public async Task DeleteTagAsync(string tagValue)
|
||||
{
|
||||
await this.DeleteAsync(
|
||||
TagApi,
|
||||
string.Format("tagValue={0}", tagValue));
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
|
||||
/// <summary>
|
||||
/// Service tags object
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class ServiceTags
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the DNS-SD service tags
|
||||
/// </summary>
|
||||
[DataMember(Name = "tags")]
|
||||
public List<string> Tags
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
#endregion Data contract
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="DumpCollection.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for crash dump collection methods.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API to retrieve list of the available bugcheck minidumps.
|
||||
/// </summary>
|
||||
public static readonly string AvailableBugChecksApi = "api/debug/dump/kernel/dumplist";
|
||||
|
||||
/// <summary>
|
||||
/// API to download a bugcheck minidump file.
|
||||
/// </summary>
|
||||
public static readonly string BugcheckFileApi = "api/debug/dump/kernel/dump";
|
||||
|
||||
/// <summary>
|
||||
/// API to control bugcheck minidump settings.
|
||||
/// </summary>
|
||||
public static readonly string BugcheckSettingsApi = "api/debug/dump/kernel/crashcontrol";
|
||||
|
||||
/// <summary>
|
||||
/// API to retrieve a live kernel dump.
|
||||
/// </summary>
|
||||
public static readonly string LiveKernelDumpApi = "api/debug/dump/livekernel";
|
||||
|
||||
/// <summary>
|
||||
/// API to retrieve a live dump from a running user mode process.
|
||||
/// </summary>
|
||||
public static readonly string LiveProcessDumpApi = "api/debug/dump/usermode/live";
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of dumpfiles on the system.
|
||||
/// </summary>
|
||||
/// <returns>List of Dumpfiles. Use GetDumpFile to download the file. </returns>
|
||||
public async Task<List<Dumpfile>> GetDumpfileListAsync()
|
||||
{
|
||||
DumpFileList dfl = await this.GetAsync<DumpFileList>(AvailableBugChecksApi);
|
||||
return dfl.DumpFiles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download a dumpfile from the system.
|
||||
/// </summary>
|
||||
/// <param name="crashdump"> Dumpfile object to be downloaded</param>
|
||||
/// <returns>Stream of the dump file </returns>
|
||||
public async Task<Stream> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes a live kernel dump of the device. This does not reboot the device.
|
||||
/// </summary>
|
||||
/// <returns>Stream of the kernel dump</returns>
|
||||
public async Task<Stream> GetLiveKernelDumpAsync()
|
||||
{
|
||||
Uri uri = Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
LiveKernelDumpApi);
|
||||
|
||||
return await this.GetAsync(uri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take a live dump of a process on the system.
|
||||
/// </summary>
|
||||
/// <param name="pid"> PID of the process to get a live dump of.</param>
|
||||
/// <returns>Stream of the process live dump</returns>
|
||||
public async Task<Stream> GetLiveProcessDumpAsync(int pid)
|
||||
{
|
||||
string queryString = LiveProcessDumpApi + string.Format("?pid={0}", pid);
|
||||
Uri uri = Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
queryString);
|
||||
|
||||
return await this.GetAsync(uri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get information on how and when dump files are saved on the device.
|
||||
/// </summary>
|
||||
/// <returns>Dumpfile settings object. This can be edited and returned to the device to alter the settings.</returns>
|
||||
public async Task<DumpFileSettings> GetDumpFileSettingsAsync()
|
||||
{
|
||||
return await this.GetAsync<DumpFileSettings>(BugcheckSettingsApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set how and when dump files are saved on the device.
|
||||
/// </summary>
|
||||
/// <param name="dfs">Altered DumpFileSettings object to set on the device</param>
|
||||
/// <returns>Task tracking completion of the request</returns>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// DumpFileSettings object. Used to get and set how and when a dump is saved on the device.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class DumpFileSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The 3 types of dumps that can be saved on the device (or not saved at all).
|
||||
/// </summary>
|
||||
public enum DumpTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't collect device crash dumps
|
||||
/// </summary>
|
||||
Disabled = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Collect all in use memory
|
||||
/// </summary>
|
||||
CompleteMemoryDump = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Don't include usermode memory in the dump
|
||||
/// </summary>
|
||||
KernelDump = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Limited kernel dump
|
||||
/// </summary>
|
||||
Minidump = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the device should restart after a crash dump is taken.
|
||||
/// </summary>
|
||||
[DataMember(Name = "autoreboot")]
|
||||
public bool AutoReboot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of dump to be saved when a bugcheck occurs.
|
||||
/// </summary>
|
||||
[DataMember(Name = "dumptype")]
|
||||
public DumpTypes DumpType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the max number of dumps to be saved on the device.
|
||||
/// </summary>
|
||||
[DataMember(Name = "maxdumpcount")]
|
||||
public int MaxDumpCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether new dumps should overwrite older dumps.
|
||||
/// </summary>
|
||||
[DataMember(Name = "overwrite")]
|
||||
public bool Overwrite { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of kernel dumps on the device.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class DumpFileList
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of kernel dumps on the device.
|
||||
/// </summary>
|
||||
[DataMember(Name = "DumpFiles")]
|
||||
public List<Dumpfile> DumpFiles { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a dumpfile stored on the device.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class Dumpfile
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the timestamp of the crash as a string.
|
||||
/// </summary>
|
||||
[DataMember(Name = "FileDate")]
|
||||
public string FileDateAsString
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timestamp of the crash.
|
||||
/// </summary>
|
||||
public DateTime FileDate
|
||||
{
|
||||
get
|
||||
{
|
||||
return DateTime.Parse(this.FileDateAsString);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the filename of the crash file.
|
||||
/// </summary>
|
||||
[DataMember(Name = "FileName")]
|
||||
public string Filename
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the crash dump, in bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "FileSize")]
|
||||
public uint FileSizeInBytes
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
}
|
||||
#endregion Data Contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="Etw.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for ETW methods
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API to create a realtime ETW session.
|
||||
/// </summary>
|
||||
public static readonly string RealtimeEtwSessionApi = "api/etw/session/realtime";
|
||||
|
||||
/// <summary>
|
||||
/// API to get the list of registered custom ETW providers.
|
||||
/// </summary>
|
||||
public static readonly string CustomEtwProvidersApi = "api/etw/customproviders";
|
||||
|
||||
/// <summary>
|
||||
/// API to get the list of registered ETW providers.
|
||||
/// </summary>
|
||||
public static readonly string EtwProvidersApi = "api/etw/providers";
|
||||
|
||||
/// <summary>
|
||||
/// Web socket to get ETW events.
|
||||
/// </summary>
|
||||
private WebSocket<EtwEvents> realtimeEventsWebSocket;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the event listener has been registered
|
||||
/// </summary>
|
||||
private bool isListeningForRealtimeEvents = false;
|
||||
|
||||
/// <summary>
|
||||
/// The ETW event message received handler
|
||||
/// </summary>
|
||||
public event WebSocketMessageReceivedEventHandler<EtwEvents> RealtimeEventsMessageReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current set of registered custom ETW providers.
|
||||
/// </summary>
|
||||
/// <returns>EtwProviders object containing a list of providers, friendly name and GUID</returns>
|
||||
public async Task<EtwProviders> GetCustomEtwProvidersAsync()
|
||||
{
|
||||
return await this.GetAsync<EtwProviders>(CustomEtwProvidersApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current set of registered ETW providers.
|
||||
/// </summary>
|
||||
/// <returns>EtwProviders object containing a list of providers, friendly name and GUID</returns>
|
||||
public async Task<EtwProviders> GetEtwProvidersAsync()
|
||||
{
|
||||
return await this.GetAsync<EtwProviders>(EtwProvidersApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the listening state of a specific provider on the realtime events WebSocket.
|
||||
/// </summary>
|
||||
/// <param name="etwProvider">The provider to update the listening state of.</param>
|
||||
/// <param name="isEnabled">Determines whether the listening state should be enabled or disabled.</param>
|
||||
/// /// <param name="level">Verbosity level - 1 for least, 5 for most verbose. </param>
|
||||
/// <returns>Task for toggling the listening state of the specified provider.</returns>
|
||||
public async Task ToggleEtwProviderAsync(EtwProviderInfo etwProvider, bool isEnabled = true, int level = 5)
|
||||
{
|
||||
await this.ToggleEtwProviderAsync(etwProvider.GUID, isEnabled, level);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the listening state of a specific provider on the realtime events WebSocket.
|
||||
/// </summary>
|
||||
/// <param name="etwProvider">The GUID of the provider to update the listening state of.</param>
|
||||
/// <param name="isEnabled">Determines whether the listening state should be enabled or disabled.</param>
|
||||
/// <param name="level">Verbosity level - 1 for least, 5 for most verbose. </param>
|
||||
/// <returns>Task for toggling the listening state of the specified provider.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening for ETW events with it being returned via the RealtimeEventsMessageReceived event handler.
|
||||
/// </summary>
|
||||
/// <returns>Task for connecting to the WebSocket but not for listening to it.</returns>
|
||||
public async Task StartListeningForEtwEventsAsync()
|
||||
{
|
||||
await this.InitializeRealtimeEventsWebSocketAsync();
|
||||
|
||||
if (!this.isListeningForRealtimeEvents)
|
||||
{
|
||||
this.isListeningForRealtimeEvents = true;
|
||||
this.realtimeEventsWebSocket.WebSocketMessageReceived += this.EtwEventsReceivedHandler;
|
||||
}
|
||||
|
||||
await this.realtimeEventsWebSocket.ReceiveMessagesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop listening for ETW events.
|
||||
/// </summary>
|
||||
/// <returns>Task for stop listening for ETW events and disconnecting from the WebSocket.</returns>
|
||||
public async Task StopListeningForEtwEventsAsync()
|
||||
{
|
||||
if (this.isListeningForRealtimeEvents)
|
||||
{
|
||||
this.isListeningForRealtimeEvents = false;
|
||||
this.realtimeEventsWebSocket.WebSocketMessageReceived -= this.EtwEventsReceivedHandler;
|
||||
}
|
||||
|
||||
await this.realtimeEventsWebSocket.CloseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="WebSocket{EtwEvents}"/> if it hasn't already been initialized.
|
||||
/// </summary>
|
||||
/// <returns>Task for connecting the ETW realtime event WebSocket.</returns>
|
||||
private async Task InitializeRealtimeEventsWebSocketAsync()
|
||||
{
|
||||
if (this.realtimeEventsWebSocket == null)
|
||||
{
|
||||
#if WINDOWS_UWP
|
||||
this.realtimeEventsWebSocket = new WebSocket<EtwEvents>(this.deviceConnection);
|
||||
#else
|
||||
this.realtimeEventsWebSocket = new WebSocket<EtwEvents>(this.deviceConnection, this.ServerCertificateValidation);
|
||||
#endif
|
||||
}
|
||||
|
||||
await this.realtimeEventsWebSocket.ConnectAsync(RealtimeEtwSessionApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for the ETW received event that passes the event to the RealtimeEventsMessageReceived handler.
|
||||
/// </summary>
|
||||
/// <param name="sender">The <see cref="WebSocket{EtwEvents}"/> object sending the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
private void EtwEventsReceivedHandler(
|
||||
WebSocket<EtwEvents> sender,
|
||||
WebSocketMessageReceivedEventArgs<EtwEvents> args)
|
||||
{
|
||||
if (args.Message != null)
|
||||
{
|
||||
this.RealtimeEventsMessageReceived?.Invoke(
|
||||
this,
|
||||
args);
|
||||
}
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
|
||||
/// <summary>
|
||||
/// ETW Events.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class EtwEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves the downconverted list of events
|
||||
/// </summary>
|
||||
private List<EtwEventInfo> stashedList;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of ETW Events that occured in the last second.
|
||||
/// </summary>
|
||||
public List<EtwEventInfo> Events
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.stashedList != null)
|
||||
{
|
||||
return this.stashedList;
|
||||
}
|
||||
|
||||
List<EtwEventInfo> events = new List<EtwEventInfo>();
|
||||
foreach (Dictionary<string, string> dic in this.RawEvents)
|
||||
{
|
||||
events.Add(new EtwEventInfo(dic));
|
||||
}
|
||||
|
||||
this.stashedList = events;
|
||||
return this.stashedList;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event frequency.
|
||||
/// This is always 10 million (10000000) in RS2 devices.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Frequency")]
|
||||
public long Frequency { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the raw list of events. Not for straight usage, as it's entirely unformatted.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Events")]
|
||||
private List<Dictionary<string, string>> RawEvents { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class EtwEventInfo : Dictionary<string, string>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EtwEventInfo" /> class. Used by the DataContract at access time.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">Base dictionary used to populate the object. </param>
|
||||
internal EtwEventInfo(IDictionary<string, string> dictionary) : base(dictionary)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event identifer.
|
||||
/// </summary>
|
||||
public ushort ID
|
||||
{
|
||||
get
|
||||
{
|
||||
return ushort.Parse(this["ID"]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event keyword.
|
||||
/// </summary>
|
||||
public ulong Keyword
|
||||
{
|
||||
get
|
||||
{
|
||||
return ulong.Parse(this["Keyword"]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event level.
|
||||
/// </summary>
|
||||
public uint Level
|
||||
{
|
||||
get
|
||||
{
|
||||
return uint.Parse(this["Level"]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event provider name.
|
||||
/// </summary>
|
||||
public string Provider
|
||||
{
|
||||
get
|
||||
{
|
||||
return this["ProviderName"];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event timestamp.
|
||||
/// </summary>
|
||||
public ulong Timestamp
|
||||
{
|
||||
get
|
||||
{
|
||||
return ulong.Parse(this["Timestamp"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ETW Providers.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class EtwProviders
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets list of ETW providers
|
||||
/// </summary>
|
||||
[DataMember(Name = "Providers")]
|
||||
public List<EtwProviderInfo> Providers { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ETW Provider Info. Contains the Name and GUID.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class EtwProviderInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets provider guid.
|
||||
/// </summary>
|
||||
[DataMember(Name = "GUID")]
|
||||
public Guid GUID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets provider name.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Name")]
|
||||
public string Name { get; private set; }
|
||||
}
|
||||
|
||||
#endregion // Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="Networking.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for Networking methods.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for getting IP config data.
|
||||
/// </summary>
|
||||
public static readonly string IpConfigApi = "api/networking/ipconfig";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IP configuration data of the device.
|
||||
/// </summary>
|
||||
/// <returns>object containing details of the device's network configuration.</returns>
|
||||
public async Task<IpConfiguration> GetIpConfigAsync()
|
||||
{
|
||||
return await this.GetAsync<IpConfiguration>(IpConfigApi);
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
|
||||
/// <summary>
|
||||
/// DHCP object.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class Dhcp
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the time at which the lease will expire, in ticks.
|
||||
/// </summary>
|
||||
[DataMember(Name = "LeaseExpires")]
|
||||
public long LeaseExpiresRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time at which the lease was obtained, in ticks.
|
||||
/// </summary>
|
||||
[DataMember(Name = "LeaseObtained")]
|
||||
public long LeaseObtainedRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Address")]
|
||||
public IpAddressInfo Address { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lease expiration time.
|
||||
/// </summary>
|
||||
public DateTimeOffset LeaseExpires
|
||||
{
|
||||
get { return new DateTimeOffset(new DateTime(this.LeaseExpiresRaw)); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lease obtained time.
|
||||
/// </summary>
|
||||
public DateTimeOffset LeaseObtained
|
||||
{
|
||||
get { return new DateTimeOffset(new DateTime(this.LeaseObtainedRaw)); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IP Address info
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class IpAddressInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address
|
||||
/// </summary>
|
||||
[DataMember(Name = "IpAddress")]
|
||||
public string Address { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subnet mask
|
||||
/// </summary>
|
||||
[DataMember(Name = "Mask")]
|
||||
public string SubnetMask { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IP Configuration object
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class IpConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of networking adapters
|
||||
/// </summary>
|
||||
[DataMember(Name = "Adapters")]
|
||||
public List<NetworkAdapterInfo> Adapters { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Networking adapter info
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class NetworkAdapterInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the description
|
||||
/// </summary>
|
||||
[DataMember(Name = "Description")]
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hardware address
|
||||
/// </summary>
|
||||
[DataMember(Name = "HardwareAddress")]
|
||||
public string MacAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index
|
||||
/// </summary>
|
||||
[DataMember(Name = "Index")]
|
||||
public int Index { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name
|
||||
/// </summary>
|
||||
[DataMember(Name = "Name")]
|
||||
public Guid Id { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type
|
||||
/// </summary>
|
||||
[DataMember(Name = "Type")]
|
||||
public string AdapterType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets DHCP info
|
||||
/// </summary>
|
||||
[DataMember(Name = "DHCP")]
|
||||
public Dhcp Dhcp { get; private set; }
|
||||
|
||||
// TODO - WINS
|
||||
|
||||
/// <summary>
|
||||
/// Gets Gateway info
|
||||
/// </summary>
|
||||
[DataMember(Name = "Gateways")]
|
||||
public List<IpAddressInfo> Gateways { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of IP addresses
|
||||
/// </summary>
|
||||
[DataMember(Name = "IpAddresses")]
|
||||
public List<IpAddressInfo> IpAddresses { get; private set; }
|
||||
}
|
||||
#endregion // Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="OsInformation.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for OS Information.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for getting the device family.
|
||||
/// </summary>
|
||||
public static readonly string DeviceFamilyApi = "api/os/devicefamily";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting the machine name.
|
||||
/// </summary>
|
||||
public static readonly string MachineNameApi = "api/os/machinename";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting the OS information.
|
||||
/// </summary>
|
||||
public static readonly string OsInfoApi = "api/os/info";
|
||||
|
||||
/// <summary>
|
||||
/// Device portal platforms
|
||||
/// </summary>
|
||||
public enum DevicePortalPlatforms
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown platform
|
||||
/// </summary>
|
||||
Unknown = -1,
|
||||
|
||||
/// <summary>
|
||||
/// Windows platform
|
||||
/// </summary>
|
||||
Windows = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Mobile platform
|
||||
/// </summary>
|
||||
Mobile,
|
||||
|
||||
/// <summary>
|
||||
/// HoloLens platform
|
||||
/// </summary>
|
||||
HoloLens,
|
||||
|
||||
/// <summary>
|
||||
/// Xbox One platform
|
||||
/// </summary>
|
||||
XboxOne,
|
||||
|
||||
/// <summary>
|
||||
/// Windows IoT on Dragonboard 410c
|
||||
/// </summary>
|
||||
IoTDragonboard410c,
|
||||
|
||||
/// <summary>
|
||||
/// Windows IoT on Minnowboard Max
|
||||
/// </summary>
|
||||
IoTMinnowboardMax,
|
||||
|
||||
/// <summary>
|
||||
/// Windows IoT on Raspberry Pi 2
|
||||
/// </summary>
|
||||
IoTRaspberryPi2,
|
||||
|
||||
/// <summary>
|
||||
/// Windows IoT on Raspberry Pi 3
|
||||
/// </summary>
|
||||
IoTRaspberryPi3,
|
||||
|
||||
/// <summary>
|
||||
/// A virtual machine. This may or may not be an emulator.
|
||||
/// </summary>
|
||||
VirtualMachine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the family name (ex: Windows.Holographic) of the device.
|
||||
/// </summary>
|
||||
/// <returns>String containing the device's family.</returns>
|
||||
public async Task<string> GetDeviceFamilyAsync()
|
||||
{
|
||||
DeviceOsFamily deviceFamily = await this.GetAsync<DeviceOsFamily>(DeviceFamilyApi).ConfigureAwait(false);
|
||||
return deviceFamily.Family;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the device.
|
||||
/// </summary>
|
||||
/// <returns>String containing the device's name.</returns>
|
||||
public async Task<string> GetDeviceNameAsync()
|
||||
{
|
||||
DeviceName deviceName = await this.GetAsync<DeviceName>(MachineNameApi);
|
||||
return deviceName.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the device's operating system.
|
||||
/// </summary>
|
||||
/// <returns>OperatingSystemInformation object containing details of the installed operating system.</returns>
|
||||
public Task<OperatingSystemInformation> GetOperatingSystemInformationAsync()
|
||||
{
|
||||
return this.GetAsync<OperatingSystemInformation>(OsInfoApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the device's name
|
||||
/// </summary>
|
||||
/// <param name="name">The name to assign to the device.</param>
|
||||
/// <remarks>The new name does not take effect until the device has been restarted.</remarks>
|
||||
/// <returns>Task tracking setting the device name completion.</returns>
|
||||
public Task SetDeviceNameAsync(string name)
|
||||
{
|
||||
return this.PostAsync(
|
||||
MachineNameApi,
|
||||
string.Format("name={0}", Utilities.Hex64Encode(name)));
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
|
||||
/// <summary>
|
||||
/// Device name object.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class DeviceName
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
[DataMember(Name = "ComputerName")]
|
||||
public string Name { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Device family object.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class DeviceOsFamily
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the device family name.
|
||||
/// </summary>
|
||||
[DataMember(Name = "DeviceType")]
|
||||
public string Family { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Operating system information.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class OperatingSystemInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the OS name.
|
||||
/// </summary>
|
||||
[DataMember(Name = "ComputerName")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the language
|
||||
/// </summary>
|
||||
[DataMember(Name = "Language")]
|
||||
public string Language { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the edition
|
||||
/// </summary>
|
||||
[DataMember(Name = "OsEdition")]
|
||||
public string OsEdition { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the edition Id
|
||||
/// </summary>
|
||||
[DataMember(Name = "OsEditionId")]
|
||||
public uint OsEditionId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OS version
|
||||
/// </summary>
|
||||
[DataMember(Name = "OsVersion")]
|
||||
public string OsVersionString { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw platform type
|
||||
/// </summary>
|
||||
[DataMember(Name = "Platform")]
|
||||
public string PlatformName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the platform
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,549 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="PerformanceData.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for Performance methods
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for getting all running processes
|
||||
/// </summary>
|
||||
public static readonly string RunningProcessApi = "api/resourcemanager/processes";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting system performance
|
||||
/// </summary>
|
||||
public static readonly string SystemPerfApi = "api/resourcemanager/systemperf";
|
||||
|
||||
/// <summary>
|
||||
/// Web socket to get running processes.
|
||||
/// </summary>
|
||||
private WebSocket<RunningProcesses> deviceProcessesWebSocket;
|
||||
|
||||
/// <summary>
|
||||
/// Web socket to get the system perf of the device.
|
||||
/// </summary>
|
||||
private WebSocket<SystemPerformanceInformation> systemPerfWebSocket;
|
||||
|
||||
/// <summary>
|
||||
/// The running processes message received handler.
|
||||
/// </summary>
|
||||
public event WebSocketMessageReceivedEventHandler<RunningProcesses> RunningProcessesMessageReceived;
|
||||
|
||||
/// <summary>
|
||||
/// The system perf message received handler.
|
||||
/// </summary>
|
||||
public event WebSocketMessageReceivedEventHandler<SystemPerformanceInformation> SystemPerfMessageReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of processes running on the device.
|
||||
/// </summary>
|
||||
/// <returns>RunningProcesses object containing the list of running processes.</returns>
|
||||
public async Task<RunningProcesses> GetRunningProcessesAsync()
|
||||
{
|
||||
return await this.GetAsync<RunningProcesses>(RunningProcessApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening for the running processes on the device with them being returned via the RunningProcessesMessageReceived event handler.
|
||||
/// </summary>
|
||||
/// <returns>Task for connecting to the websocket but not for listening to it.</returns>
|
||||
public async Task StartListeningForRunningProcessesAsync()
|
||||
{
|
||||
if (this.deviceProcessesWebSocket == null)
|
||||
{
|
||||
#if WINDOWS_UWP
|
||||
this.deviceProcessesWebSocket = new WebSocket<RunningProcesses>(this.deviceConnection);
|
||||
#else
|
||||
this.deviceProcessesWebSocket = new WebSocket<RunningProcesses>(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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop listening for the running processes on the device.
|
||||
/// </summary>
|
||||
/// <returns>Task for stop listening for processes and disconnecting from the websocket .</returns>
|
||||
public async Task StopListeningForRunningProcessesAsync()
|
||||
{
|
||||
await this.deviceProcessesWebSocket.CloseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets system performance information for the device.
|
||||
/// </summary>
|
||||
/// <returns>SystemPerformanceInformation object containing information such as memory usage.</returns>
|
||||
public async Task<SystemPerformanceInformation> GetSystemPerfAsync()
|
||||
{
|
||||
return await this.GetAsync<SystemPerformanceInformation>(SystemPerfApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening for the system performance information for the device with it being returned via the SystemPerfMessageReceived event handler.
|
||||
/// </summary>
|
||||
/// <returns>Task for connecting to the websocket but not for listening to it.</returns>
|
||||
public async Task StartListeningForSystemPerfAsync()
|
||||
{
|
||||
if (this.systemPerfWebSocket == null)
|
||||
{
|
||||
#if WINDOWS_UWP
|
||||
this.systemPerfWebSocket = new WebSocket<SystemPerformanceInformation>(this.deviceConnection);
|
||||
#else
|
||||
this.systemPerfWebSocket = new WebSocket<SystemPerformanceInformation>(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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop listening for the system performance information for the device.
|
||||
/// </summary>
|
||||
/// <returns>Task for stop listening for system perf and disconnecting from the websocket .</returns>
|
||||
public async Task StopListeningForSystemPerfAsync()
|
||||
{
|
||||
await this.systemPerfWebSocket.CloseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for the processes received event that passes the event to the RunningProcessesMessageReceived handler.
|
||||
/// </summary>
|
||||
/// <param name="sender">The <see cref="WebSocket{RunningProcesses}"/> object sending the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
private void RunningProcessesReceivedHandler(
|
||||
WebSocket<RunningProcesses> sender,
|
||||
WebSocketMessageReceivedEventArgs<RunningProcesses> args)
|
||||
{
|
||||
if (args.Message != null)
|
||||
{
|
||||
this.RunningProcessesMessageReceived?.Invoke(
|
||||
this,
|
||||
args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for the system performance information received event that passes the event to the SystemPerfMessageReceived handler.
|
||||
/// </summary>
|
||||
/// <param name="sender">The <see cref="WebSocket{SystemPerformanceInformation}"/> object sending the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
private void SystemPerfReceivedHandler(
|
||||
WebSocket<SystemPerformanceInformation> sender,
|
||||
WebSocketMessageReceivedEventArgs<SystemPerformanceInformation> args)
|
||||
{
|
||||
if (args.Message != null)
|
||||
{
|
||||
this.SystemPerfMessageReceived?.Invoke(
|
||||
this,
|
||||
args);
|
||||
}
|
||||
}
|
||||
|
||||
#region Device contract
|
||||
|
||||
/// <summary>
|
||||
/// Object representing the app version. Only present if the process is an app.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class AppVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the major version number
|
||||
/// </summary>
|
||||
[DataMember(Name = "Major")]
|
||||
public uint Major { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minor version number
|
||||
/// </summary>
|
||||
[DataMember(Name = "Minor")]
|
||||
public uint Minor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the build number
|
||||
/// </summary>
|
||||
[DataMember(Name = "Build")]
|
||||
public uint Build { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the revision number
|
||||
/// </summary>
|
||||
[DataMember(Name = "Revision")]
|
||||
public uint Revision { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process Info. Contains app information if the process is an app.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class DeviceProcessInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the app name. Only present if the process is an app.
|
||||
/// </summary>
|
||||
[DataMember(Name = "AppName")]
|
||||
public string AppName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets CPU Usage as a percentage of available CPU resources (0-100)
|
||||
/// </summary>
|
||||
[DataMember(Name = "CPUUsage")]
|
||||
public float CpuUsage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image name
|
||||
/// </summary>
|
||||
[DataMember(Name = "ImageName")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process id (pid)
|
||||
/// </summary>
|
||||
[DataMember(Name = "ProcessId")]
|
||||
public uint ProcessId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user the process is running as.
|
||||
/// </summary>
|
||||
[DataMember(Name = "UserName")]
|
||||
public string UserName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the package full name. Only present if the process is an app.
|
||||
/// </summary>
|
||||
[DataMember(Name = "PackageFullName")]
|
||||
public string PackageFullName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Page file usage info, in bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "PageFileUsage")]
|
||||
public ulong PageFile { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the working set size, in bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "WorkingSetSize")]
|
||||
public ulong WorkingSet { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets package working set, in bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "PrivateWorkingSet")]
|
||||
public ulong PrivateWorkingSet { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets session id
|
||||
/// </summary>
|
||||
[DataMember(Name = "SessionId")]
|
||||
public uint SessionId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets total commit, in bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "TotalCommit")]
|
||||
public ulong TotalCommit { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets virtual size, in bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "VirtualSize")]
|
||||
public ulong VirtualSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the app is running
|
||||
/// (versus suspended). Only present if the process is an app.
|
||||
/// </summary>
|
||||
[DataMember(Name = "IsRunning")]
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets publisher. Only present if the process is an app.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Publisher")]
|
||||
public string Publisher { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets version. Only present if the process is an app.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Version")]
|
||||
public AppVersion Version { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the package is a XAP
|
||||
/// package. Only present if the process is an app.
|
||||
/// </summary>
|
||||
[DataMember(Name = "IsXAP")]
|
||||
public bool IsXAP { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// String representation of a process
|
||||
/// </summary>
|
||||
/// <returns>String representation</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} ({1})", this.AppName, this.Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GPU Adaptors
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class GpuAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets total Dedicated memory in bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "DedicatedMemory")]
|
||||
public ulong DedicatedMemory { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets used Dedicated memory in bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "DedicatedMemoryUsed")]
|
||||
public ulong DedicatedMemoryUsed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets description
|
||||
/// </summary>
|
||||
[DataMember(Name = "Description")]
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets system memory in bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "SystemMemory")]
|
||||
public ulong SystemMemory { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets memory used in bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "SystemMemoryUsed")]
|
||||
public ulong SystemMemoryUsed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets engines utilization as percent of maximum.
|
||||
/// </summary>
|
||||
[DataMember(Name = "EnginesUtilization")]
|
||||
public List<double> EnginesUtilization { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GPU performance data
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class GpuPerformanceData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets list of available adapters
|
||||
/// </summary>
|
||||
[DataMember(Name = "AvailableAdapters")]
|
||||
public List<GpuAdapter> Adapters { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Network performance data
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class NetworkPerformanceData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets current download speed in bytes per second
|
||||
/// </summary>
|
||||
[DataMember(Name = "NetworkInBytes")]
|
||||
public ulong BytesIn { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets current upload speed in bytes per second
|
||||
/// </summary>
|
||||
[DataMember(Name = "NetworkOutBytes")]
|
||||
public ulong BytesOut { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Running processes
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class RunningProcesses
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets list of running processes.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Processes")]
|
||||
public List<DeviceProcessInfo> Processes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if this process Id is in the list of processes
|
||||
/// </summary>
|
||||
/// <param name="processId">Process to look for</param>
|
||||
/// <returns>whether the process id was found</returns>
|
||||
public bool Contains(int processId)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
foreach (DeviceProcessInfo pi in this.Processes)
|
||||
{
|
||||
if (pi.ProcessId == processId)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks for a given package name
|
||||
/// </summary>
|
||||
/// <param name="packageName">Name of the package to look for</param>
|
||||
/// <param name="caseSensitive">Whether we should be case sensitive in our search</param>
|
||||
/// <returns>Whether the package was found</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// System performance information
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class SystemPerformanceInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets available pages
|
||||
/// </summary>
|
||||
[DataMember(Name = "AvailablePages")]
|
||||
public ulong AvailablePages { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets commit limit in bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "CommitLimit")]
|
||||
public uint CommitLimit { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets committed pages
|
||||
/// </summary>
|
||||
[DataMember(Name = "CommittedPages")]
|
||||
public uint CommittedPages { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets CPU load as percent of maximum (0 - 100)
|
||||
/// </summary>
|
||||
[DataMember(Name = "CpuLoad")]
|
||||
public uint CpuLoad { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets IO Other Speed in bytes per second
|
||||
/// </summary>
|
||||
[DataMember(Name = "IOOtherSpeed")]
|
||||
public ulong IoOtherSpeed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets IO Read speed in bytes per second.
|
||||
/// </summary>
|
||||
[DataMember(Name = "IOReadSpeed")]
|
||||
public ulong IoReadSpeed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets IO write speed in bytes per second
|
||||
/// </summary>
|
||||
[DataMember(Name = "IOWriteSpeed")]
|
||||
public ulong IoWriteSpeed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets Non paged pool pages
|
||||
/// </summary>
|
||||
[DataMember(Name = "NonPagedPoolPages")]
|
||||
public uint NonPagedPoolPages { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets page size
|
||||
/// </summary>
|
||||
[DataMember(Name = "PageSize")]
|
||||
public uint PageSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets paged pool pages
|
||||
/// </summary>
|
||||
[DataMember(Name = "PagedPoolPages")]
|
||||
public uint PagedPoolPages { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets total installed in KB
|
||||
/// </summary>
|
||||
[DataMember(Name = "TotalInstalledInKb")]
|
||||
public ulong TotalInstalledKb { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets total pages
|
||||
/// </summary>
|
||||
[DataMember(Name = "TotalPages")]
|
||||
public uint TotalPages { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets GPU data
|
||||
/// </summary>
|
||||
[DataMember(Name = "GPUData")]
|
||||
public GpuPerformanceData GpuData { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets Networking data
|
||||
/// </summary>
|
||||
[DataMember(Name = "NetworkingData")]
|
||||
public NetworkPerformanceData NetworkData { get; private set; }
|
||||
}
|
||||
|
||||
#endregion // Device contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="Power.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for Power methods.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for getting or setting the active power scheme.
|
||||
/// </summary>
|
||||
public static readonly string ActivePowerSchemeApi = "api/power/activecfg";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting battery state.
|
||||
/// </summary>
|
||||
public static readonly string BatteryStateApi = "api/power/battery";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting or setting a power scheme's sub-value.
|
||||
/// </summary>
|
||||
public static readonly string PowerSchemeSubValueApi = "api/power/cfg";
|
||||
|
||||
/// <summary>
|
||||
/// API for controlling power state.
|
||||
/// </summary>
|
||||
public static readonly string PowerStateApi = "api/power/state";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting a sleep study report.
|
||||
/// </summary>
|
||||
public static readonly string SleepStudyReportApi = "api/power/sleepstudy/report";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting the list of sleep study reports.
|
||||
/// </summary>
|
||||
public static readonly string SleepStudyReportsApi = "api/power/sleepstudy/reports";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting a sleep study report.
|
||||
/// </summary>
|
||||
public static readonly string SleepStudyTransformApi = "api/power/sleepstudy/transform";
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current active power scheme.
|
||||
/// </summary>
|
||||
/// <returns>The power scheme identifier.</returns>
|
||||
public async Task<Guid> GetActivePowerSchemeAsync()
|
||||
{
|
||||
ActivePowerScheme activeScheme = await this.GetAsync<ActivePowerScheme>(ActivePowerSchemeApi);
|
||||
return activeScheme.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current state of the device's battery.
|
||||
/// </summary>
|
||||
/// <returns>BatteryState object containing details such as the current battery level.</returns>
|
||||
public async Task<BatteryState> GetBatteryStateAsync()
|
||||
{
|
||||
return await this.GetAsync<BatteryState>(BatteryStateApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device's current power state.
|
||||
/// </summary>
|
||||
/// <returns>PowerState object containing details such as whether or not the device is in low power mode.</returns>
|
||||
public async Task<PowerState> GetPowerStateAsync()
|
||||
{
|
||||
return await this.GetAsync<PowerState>(PowerStateApi);
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
|
||||
/// <summary>
|
||||
/// Battery state.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class ActivePowerScheme
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the active power scheme identifier.
|
||||
/// </summary>
|
||||
[DataMember(Name = "ActivePowerScheme")]
|
||||
public Guid Id { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Battery state.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class BatteryState
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the device is on AC power.
|
||||
/// </summary>
|
||||
[DataMember(Name = "AcOnline")]
|
||||
public bool IsOnAcPower { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a battery is present.
|
||||
/// </summary>
|
||||
[DataMember(Name = "BatteryPresent")]
|
||||
public bool IsBatteryPresent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the device is charging.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Charging")]
|
||||
public bool IsCharging { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default alert.
|
||||
/// </summary>
|
||||
[DataMember(Name = "DefaultAlert1")]
|
||||
public int DefaultAlert1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default alert.
|
||||
/// </summary>
|
||||
[DataMember(Name = "DefaultAlert2")]
|
||||
public int DefaultAlert2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets estimated battery time left in seconds.
|
||||
/// </summary>
|
||||
[DataMember(Name = "EstimatedTime")]
|
||||
public uint EstimatedTimeRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets maximum capacity.
|
||||
/// </summary>
|
||||
[DataMember(Name = "MaximumCapacity")]
|
||||
public int MaximumCapacity { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets remaining capacity.
|
||||
/// </summary>
|
||||
[DataMember(Name = "RemainingCapacity")]
|
||||
public int RemainingCapacity { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the battery level as a percentage of the maximum capacity.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public TimeSpan EstimatedTime
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TimeSpan(0, 0, (int)this.EstimatedTimeRaw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Power state
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class PowerState
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the device is in a lower power mode
|
||||
/// </summary>
|
||||
[DataMember(Name = "LowPowerState")]
|
||||
public bool InLowPowerState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the device supports a lower power mode
|
||||
/// </summary>
|
||||
[DataMember(Name = "LowPowerStateAvailable")]
|
||||
public bool IsLowPowerStateAvailable { get; private set; }
|
||||
}
|
||||
|
||||
#endregion // Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="RemoteControl.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for Remote Control methods.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for rebooting the device.
|
||||
/// </summary>
|
||||
public static readonly string RebootApi = "api/control/restart";
|
||||
|
||||
/// <summary>
|
||||
/// API for shutting down the device.
|
||||
/// </summary>
|
||||
public static readonly string ShutdownApi = "api/control/shutdown";
|
||||
|
||||
/// <summary>
|
||||
/// Reboots the device.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Task tracking reboot completion.
|
||||
/// </returns>
|
||||
public async Task RebootAsync()
|
||||
{
|
||||
await this.PostAsync(RebootApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuts down the device.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Task tracking shutdown completion.
|
||||
/// </returns>
|
||||
public async Task ShutdownAsync()
|
||||
{
|
||||
await this.PostAsync(ShutdownApi);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="TaskManager.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for Task Manager methods
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for starting or stopping a modern application.
|
||||
/// </summary>
|
||||
public static readonly string TaskManagerApi = "api/taskmanager/app";
|
||||
|
||||
/// <summary>
|
||||
/// Starts running the specified application.
|
||||
/// </summary>
|
||||
/// <param name="appid">Application ID</param>
|
||||
/// <param name="packageName">The name of the application package.</param>
|
||||
/// <returns>Process identifier for the application instance.</returns>
|
||||
public async Task<uint> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the specified application from running.
|
||||
/// </summary>
|
||||
/// <param name="packageName">The name of the application package.</param>
|
||||
/// <returns>
|
||||
/// Task for tracking termination completion
|
||||
/// </returns>
|
||||
public async Task TerminateApplicationAsync(string packageName)
|
||||
{
|
||||
await this.DeleteAsync(
|
||||
TaskManagerApi,
|
||||
string.Format("package={0}", Utilities.Hex64Encode(packageName)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="WiFiManagement.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for WiFi management methods.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for getting the WiFi interfaces.
|
||||
/// </summary>
|
||||
public static readonly string WifiInterfacesApi = "api/wifi/interfaces";
|
||||
|
||||
/// <summary>
|
||||
/// API for the controlling the WiFi network.
|
||||
/// </summary>
|
||||
public static readonly string WifiNetworkApi = "api/wifi/network";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting available WiFi networks.
|
||||
/// </summary>
|
||||
public static readonly string WifiNetworksApi = "api/wifi/networks";
|
||||
|
||||
/// <summary>
|
||||
/// Connect to a WiFi network using a given network adapter and SSID.
|
||||
/// </summary>
|
||||
/// <param name="networkAdapter">Network adaptor GUID.</param>
|
||||
/// <param name="ssid">SSID of the network.</param>
|
||||
/// <param name="networkKey">Network key.</param>
|
||||
/// <returns>Task tracking connection status.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets WiFi interfaces.
|
||||
/// </summary>
|
||||
/// <returns>List of WiFi interfaces.</returns>
|
||||
public async Task<WifiInterfaces> GetWifiInterfacesAsync()
|
||||
{
|
||||
return await this.GetAsync<WifiInterfaces>(WifiInterfacesApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets WiFi networks as seen from a WiFi interface.
|
||||
/// </summary>
|
||||
/// <param name="interfaceGuid">Interface to get networks from.</param>
|
||||
/// <returns>List of available networks.</returns>
|
||||
public async Task<WifiNetworks> GetWifiNetworksAsync(Guid interfaceGuid)
|
||||
{
|
||||
return await this.GetAsync<WifiNetworks>(
|
||||
WifiNetworksApi,
|
||||
string.Format("interface={0}", interfaceGuid.ToString()));
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
|
||||
/// <summary>
|
||||
/// WiFi interface.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class WifiInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets description.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Description")]
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets GUID.
|
||||
/// </summary>
|
||||
[DataMember(Name = "GUID")]
|
||||
public Guid Guid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets index.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Index")]
|
||||
public int Index { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets profiles list.
|
||||
/// </summary>
|
||||
[DataMember(Name = "ProfilesList")]
|
||||
public List<WifiNetworkProfile> Profiles { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WiFi interfaces.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class WifiInterfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of interfaces.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Interfaces")]
|
||||
public List<WifiInterface> Interfaces { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WiFi networks.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class WifiNetworks
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of available networks.
|
||||
/// </summary>
|
||||
[DataMember(Name = "AvailableNetworks")]
|
||||
public List<WifiNetworkInfo> AvailableNetworks { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WiFi network info.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class WifiNetworkInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the device is already connected to this network.
|
||||
/// </summary>
|
||||
[DataMember(Name = "AlreadyConnected")]
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication algorithm.
|
||||
/// </summary>
|
||||
[DataMember(Name = "AuthenticationAlgorithm")]
|
||||
public string AuthenticationAlgorithm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Channel")]
|
||||
public int Channel { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cipher algorithm.
|
||||
/// </summary>
|
||||
[DataMember(Name = "CipherAlgorithm")]
|
||||
public string CipherAlgorithm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this network is connectable
|
||||
/// </summary>
|
||||
[DataMember(Name = "Connectable")]
|
||||
public bool IsConnectable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the infrastructure type - ad hoc or standard.
|
||||
/// </summary>
|
||||
[DataMember(Name = "InfrastructureType")]
|
||||
public string InfrastructureType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a profile is available.
|
||||
/// </summary>
|
||||
[DataMember(Name = "ProfileAvailable")]
|
||||
public bool IsProfileAvailable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile name.
|
||||
/// </summary>
|
||||
[DataMember(Name = "ProfileName")]
|
||||
public string ProfileName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SSID.
|
||||
/// </summary>
|
||||
[DataMember(Name = "SSID")]
|
||||
public string Ssid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether security is enabled.
|
||||
/// </summary>
|
||||
[DataMember(Name = "SecurityEnabled")]
|
||||
public bool IsSecurityEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the signal quality.
|
||||
/// </summary>
|
||||
[DataMember(Name = "SignalQuality")]
|
||||
public int SignalQuality { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the BSSID.
|
||||
/// </summary>
|
||||
[DataMember(Name = "BSSID")]
|
||||
public List<int> Bssid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets physical types.
|
||||
/// </summary>
|
||||
[DataMember(Name = "PhysicalTypes")]
|
||||
public List<string> NetworkTypes { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WiFi network profile.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class WifiNetworkProfile
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this is a group policy profile.
|
||||
/// </summary>
|
||||
[DataMember(Name = "GroupPolicyProfile")]
|
||||
public bool IsGroupPolicyProfile { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Name")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this is a per user profile.
|
||||
/// </summary>
|
||||
[DataMember(Name = "PerUserProfile")]
|
||||
public bool IsPerUserProfile { get; private set; }
|
||||
}
|
||||
#endregion // Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="WindowsErrorReporting.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <content>
|
||||
/// Wrapper for collecting Windows Error Reports from the device.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for downloading a Windows error reporting file.
|
||||
/// </summary>
|
||||
public static readonly string WindowsErrorReportingFileApi = "api/wer/report/file";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting the list of files in a Windows error report.
|
||||
/// </summary>
|
||||
public static readonly string WindowsErrorReportingFilesApi = "api/wer/report/files";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting the list of Windows error reports.
|
||||
/// </summary>
|
||||
public static readonly string WindowsErrorReportsApi = "api/wer/reports";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of Windows Error Reporting (WER) reports.
|
||||
/// </summary>
|
||||
/// <returns>The list of Windows Error Reporting (WER) reports.</returns>
|
||||
public async Task<WerDeviceReports> GetWindowsErrorReportsAsync()
|
||||
{
|
||||
this.CheckPlatformSupport();
|
||||
|
||||
return await this.GetAsync<WerDeviceReports>(WindowsErrorReportsApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of files in a Windows Error Reporting (WER) report.
|
||||
/// </summary>
|
||||
/// <param name="user">The user associated with the report.</param>
|
||||
/// <param name="type">The type of report. This can be either 'queried' or 'archived'.</param>
|
||||
/// <param name="name">The name of the report.</param>
|
||||
/// <returns>The list of files.</returns>
|
||||
public async Task<WerFiles> GetWindowsErrorReportingFileListAsync(string user, string type, string name)
|
||||
{
|
||||
this.CheckPlatformSupport();
|
||||
|
||||
Dictionary<string, string> payload = new Dictionary<string, string>();
|
||||
payload.Add("user", user);
|
||||
payload.Add("type", type);
|
||||
payload.Add("name", Utilities.Hex64Encode(name));
|
||||
|
||||
return await this.GetAsync<WerFiles>(WindowsErrorReportingFilesApi, Utilities.BuildQueryString(payload));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified file from a Windows Error Reporting (WER) report.
|
||||
/// </summary>
|
||||
/// <param name="user">The user associated with the report.</param>
|
||||
/// <param name="type">The type of report. This can be either 'queried' or 'archived'.</param>
|
||||
/// <param name="name">The name of the report.</param>
|
||||
/// <param name="file">The name of the file to download from the report.</param>
|
||||
/// <returns>Byte array containing the file data</returns>
|
||||
public async Task<byte[]> GetWindowsErrorReportingFileAsync(string user, string type, string name, string file)
|
||||
{
|
||||
this.CheckPlatformSupport();
|
||||
|
||||
Dictionary<string, string> payload = new Dictionary<string, string>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the Windows Error Reporting (WER) APIs are being called on a supported platform.
|
||||
/// </summary>
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of all files contained within a Windows Error Reporting (WER) report.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class WerFiles
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of all files contained within a Windows Error Reporting (WER) report.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Files")]
|
||||
public List<WerFileInformation> Files { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about a Windows Error Reporting (WER) report file.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class WerFileInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the file.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Name")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the file (in bytes).
|
||||
/// </summary>
|
||||
[DataMember(Name = "Size")]
|
||||
public int Size { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of all Windows Error Reporting (WER) reports on a device.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class WerDeviceReports
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataMember(Name = "WerReports")]
|
||||
public List<WerUserReports> UserReports { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets system error reports - Convenience accessor for the System error reports - this is
|
||||
/// where most error reports end up.
|
||||
/// </summary>
|
||||
public WerUserReports SystemErrorReports
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.UserReports.First(x => x.UserName == "SYSTEM");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of Windows Error Reporting (WER) reports for a specific user.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class WerUserReports
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the user name.
|
||||
/// </summary>
|
||||
[DataMember(Name = "User")]
|
||||
public string UserName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of Windows Error Reporting (WER) reports
|
||||
/// </summary>
|
||||
[DataMember(Name = "Reports")]
|
||||
public List<WerReportInformation> Reports { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about a Windows Error Reporting (WER) report.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class WerReportInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the creation time.
|
||||
/// </summary>
|
||||
[DataMember(Name = "CreationTime")]
|
||||
public ulong CreationTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the report name (not base64 encoded).
|
||||
/// </summary>
|
||||
[DataMember(Name = "Name")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the report type ("Queue" or "Archive").
|
||||
/// </summary>
|
||||
[DataMember(Name = "Type")]
|
||||
public string Type { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="WindowsPerformanceRecorder.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for DNS methods
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for starting and stopping a Windows performance recorder boot performance trace session.
|
||||
/// </summary>
|
||||
public static readonly string WindowsPerformanceBootTraceApi = "api/wpr/boottrace";
|
||||
|
||||
/// <summary>
|
||||
/// API for starting a Windows performance recorder trace using a custom profile.
|
||||
/// </summary>
|
||||
public static readonly string WindowsPerformanceCustomTraceApi = "api/wpr/customtrace";
|
||||
|
||||
/// <summary>
|
||||
/// API for starting and stopping a Windows performance recorder trace session.
|
||||
/// </summary>
|
||||
public static readonly string WindowsPerformanceTraceApi = "api/wpr/trace";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting the status of a Windows performance recorder trace session.
|
||||
/// </summary>
|
||||
public static readonly string WindowsPerformanceTraceStatusApi = "api/wpr/status";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="DefaultDevicePortalConnection.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using static Microsoft.Tools.WindowsDevicePortal.DevicePortal;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the IDevicePortalConnection interface.
|
||||
/// This implementation is designed to be compatibile with all device families.
|
||||
/// </summary>
|
||||
public class DefaultDevicePortalConnection : IDevicePortalConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// The device's root certificate.
|
||||
/// </summary>
|
||||
private X509Certificate2 deviceCertificate = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultDevicePortalConnection" /> class.
|
||||
/// </summary>
|
||||
/// <param name="address">The fully qualified (ex: "https:/1.2.3.4:4321") address of the device.</param>
|
||||
/// <param name="userName">The user name used in the connection credentials.</param>
|
||||
/// <param name="password">The password used in the connection credentials.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultDevicePortalConnection"/> class, using a SecureString to secure the password.
|
||||
/// </summary>
|
||||
/// <param name="address">device identifier</param>
|
||||
/// <param name="userName">WDP username</param>
|
||||
/// <param name="password">WDP password</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URI used to connect to the device.
|
||||
/// </summary>
|
||||
public Uri Connection
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets Web Socket Connection property
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the credentials used to connect to the device.
|
||||
/// </summary>
|
||||
public NetworkCredential Credentials
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the device's operating system family.
|
||||
/// </summary>
|
||||
public string Family
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the operating system information.
|
||||
/// </summary>
|
||||
public OperatingSystemInformation OsInfo
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the provided device certificate.
|
||||
/// </summary>
|
||||
/// <returns>Stored device certificate.</returns>
|
||||
public X509Certificate2 GetDeviceCertificate()
|
||||
{
|
||||
return this.deviceCertificate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores a manually provided device certificate.
|
||||
/// </summary>
|
||||
/// <param name="certificate">The device's root certificate.</param>
|
||||
public void SetDeviceCertificate(X509Certificate2 certificate)
|
||||
{
|
||||
this.deviceCertificate = certificate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the device's connection Uri.
|
||||
/// </summary>
|
||||
/// <param name="requiresHttps">Indicates whether or not to always require a secure connection.</param>
|
||||
public void UpdateConnection(bool requiresHttps)
|
||||
{
|
||||
this.Connection = new Uri(
|
||||
string.Format(
|
||||
"{0}://{1}",
|
||||
requiresHttps ? "https" : "http",
|
||||
this.Connection.Authority));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the device's connection Uri.
|
||||
/// </summary>
|
||||
/// <param name="ipConfig">Object that describes the current network configuration.</param>
|
||||
/// <param name="requiresHttps">True if an https connection is required, false otherwise.</param>
|
||||
/// <param name="preservePort">True if the previous connection's port is to continue to be used, false otherwise.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,430 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="DeviceInfo.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for Device Information.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// IOT device information API.
|
||||
/// </summary>
|
||||
public static readonly string IoTOsInfoApi = "api/iot/device/information";
|
||||
|
||||
/// <summary>
|
||||
/// IOT device timezone API.
|
||||
/// </summary>
|
||||
public static readonly string TimezoneInfoApi = "api/iot/device/timezones";
|
||||
|
||||
/// <summary>
|
||||
/// IOT device datetime API.
|
||||
/// </summary>
|
||||
public static readonly string DateTimeInfoApi = "api/iot/device/datetime";
|
||||
|
||||
/// <summary>
|
||||
/// IOT device Controller Driver API.
|
||||
/// </summary>
|
||||
public static readonly string ControllerDriverApi = "api/iot/device/controllersdriver";
|
||||
|
||||
/// <summary>
|
||||
/// IOT display resolution API.
|
||||
/// </summary>
|
||||
public static readonly string DisplayResolutionApi = "api/iot/device/displayresolution";
|
||||
|
||||
/// <summary>
|
||||
/// IOT display orientation API.
|
||||
/// </summary>
|
||||
public static readonly string DisplayOrientationApi = "api/iot/device/displayorientation";
|
||||
|
||||
/// <summary>
|
||||
/// IOT device name API.
|
||||
/// </summary>
|
||||
public static readonly string DeviceNameApi = "api/iot/device/name";
|
||||
|
||||
/// <summary>
|
||||
/// IOT Device password API.
|
||||
/// </summary>
|
||||
public static readonly string ResetPasswordApi = "api/iot/device/password";
|
||||
|
||||
/// <summary>
|
||||
/// IOT remote debugging pin API.
|
||||
/// </summary>
|
||||
public static readonly string NewRemoteDebuggingPinApi = "api/iot/device/remotedebuggingpin";
|
||||
|
||||
/// <summary>
|
||||
/// IOT set timezone API.
|
||||
/// </summary>
|
||||
public static readonly string SetTimeZoneApi = "api/iot/device/settimezone";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IoT OS Information.
|
||||
/// </summary>
|
||||
/// <returns>String containing the OS information.</returns>
|
||||
public async Task<IoTOSInfo> GetIoTOSInfoAsync()
|
||||
{
|
||||
return await this.GetAsync<IoTOSInfo>(IoTOsInfoApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Timezone information.
|
||||
/// </summary>
|
||||
/// <returns>String containing the timezone information.</returns>
|
||||
public async Task<TimezoneInfo> GetTimezoneInfoAsync()
|
||||
{
|
||||
return await this.GetAsync<TimezoneInfo>(TimezoneInfoApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the datetime information.
|
||||
/// </summary>
|
||||
/// <returns>String containing the datetime information.</returns>
|
||||
public async Task<DateTimeInfo> GetDateTimeInfoAsync()
|
||||
{
|
||||
return await this.GetAsync<DateTimeInfo>(DateTimeInfoApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the controller driver information.
|
||||
/// </summary>
|
||||
/// <returns>String containing the controller driver information.</returns>
|
||||
public async Task<ControllerDriverInfo> GetControllerDriverInfoAsync()
|
||||
{
|
||||
return await this.GetAsync<ControllerDriverInfo>(ControllerDriverApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dispaly orientation information.
|
||||
/// </summary>
|
||||
/// <returns>String containing the dispaly orientation information.</returns>
|
||||
public async Task<DisplayOrientationInfo> GetDisplayOrientationInfoAsync()
|
||||
{
|
||||
return await this.GetAsync<DisplayOrientationInfo>(DisplayOrientationApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dispaly resolution information.
|
||||
/// </summary>
|
||||
/// <returns>String containing the dispaly resolution information.</returns>
|
||||
public async Task<DisplayResolutionInfo> GetDisplayResolutionInfoAsync()
|
||||
{
|
||||
return await this.GetAsync<DisplayResolutionInfo>(DisplayResolutionApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Device Name.
|
||||
/// </summary>
|
||||
/// <param name="name">Name to set for the device.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
public async Task SetIoTDeviceNameAsync(string name)
|
||||
{
|
||||
await this.PostAsync(DeviceNameApi, string.Format("newdevicename={0}", Utilities.Hex64Encode(name)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a new password.
|
||||
/// </summary>
|
||||
/// <param name="oldPassword">Old password.</param>
|
||||
/// <param name="newPassword">New desired password.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
public async Task<ErrorInformation> SetNewPasswordAsync(string oldPassword, string newPassword)
|
||||
{
|
||||
return await this.PostAsync<ErrorInformation>(
|
||||
ResetPasswordApi,
|
||||
string.Format("oldpassword={0}&newpassword={1}", Utilities.Hex64Encode(oldPassword), Utilities.Hex64Encode(newPassword)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a new remote debugging pin.
|
||||
/// </summary>
|
||||
/// <param name="newPin">New pin.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
public async Task SetNewRemoteDebuggingPinAsync(string newPin)
|
||||
{
|
||||
await this.PostAsync(
|
||||
NewRemoteDebuggingPinApi,
|
||||
string.Format("newpin={0}", Utilities.Hex64Encode(newPin)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets controllers drivers.
|
||||
/// </summary>
|
||||
/// <param name="newDriver">Driver to set.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
public async Task<ControllerDriverInfo> SetControllersDriversAsync(string newDriver)
|
||||
{
|
||||
return await this.PostAsync<ControllerDriverInfo>(
|
||||
ControllerDriverApi,
|
||||
string.Format("newdriver={0}", Utilities.Hex64Encode(newDriver)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets Timezone.
|
||||
/// </summary>
|
||||
/// <param name="index">Timezone index.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
public async Task<ErrorInformation> SetTimeZoneAsync(int index)
|
||||
{
|
||||
return await this.PostAsync<ErrorInformation>(
|
||||
SetTimeZoneApi,
|
||||
string.Format("index={0}", index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets display resolution.
|
||||
/// </summary>
|
||||
/// <param name="displayResolution">New display resolution.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
public async Task SetDisplayResolutionAsync(string displayResolution)
|
||||
{
|
||||
await this.PostAsync(
|
||||
DisplayResolutionApi,
|
||||
string.Format("newdisplayresolution={0}", Utilities.Hex64Encode(displayResolution)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set display orientation.
|
||||
/// </summary>
|
||||
/// <param name="displayOrientation">Desired orientation.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
public async Task SetDisplayOrientationAsync(string displayOrientation)
|
||||
{
|
||||
await this.PostAsync(
|
||||
DisplayOrientationApi,
|
||||
string.Format("newdisplayorientation={0}", Utilities.Hex64Encode(displayOrientation)));
|
||||
}
|
||||
#region Data contract
|
||||
|
||||
/// <summary>
|
||||
/// Operating system information.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class IoTOSInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the device model
|
||||
/// </summary>
|
||||
[DataMember(Name = "DeviceModel")]
|
||||
public string Model { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device name.
|
||||
/// </summary>
|
||||
[DataMember(Name = "DeviceName")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OS version
|
||||
/// </summary>
|
||||
[DataMember(Name = "OSVersion")]
|
||||
public string OSVersion { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timezone information.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class TimezoneInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current timezone
|
||||
/// </summary>
|
||||
[DataMember(Name = "Current")]
|
||||
public Timezone CurrentTimeZone { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all timezones
|
||||
/// </summary>
|
||||
[DataMember(Name = "Timezones")]
|
||||
public List<Timezone> Timezones { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timezone specifications.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public partial class Timezone
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the timezone description
|
||||
/// </summary>
|
||||
[DataMember(Name = "Description")]
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timezone index
|
||||
/// </summary>
|
||||
[DataMember(Name = "Index")]
|
||||
public int Index { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timezone name
|
||||
/// </summary>
|
||||
[DataMember(Name = "Name")]
|
||||
public string Name { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DateTime information.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class DateTimeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current date time
|
||||
/// </summary>
|
||||
[DataMember(Name = "Current")]
|
||||
public DateTimeDescription CurrentDateTime { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current Datetime information.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class DateTimeDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current day
|
||||
/// </summary>
|
||||
[DataMember(Name = "Day")]
|
||||
public int Day { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current hour
|
||||
/// </summary>
|
||||
[DataMember(Name = "Hour")]
|
||||
public int Hour { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current minute
|
||||
/// </summary>
|
||||
[DataMember(Name = "Minute")]
|
||||
public int Min { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current month
|
||||
/// </summary>
|
||||
[DataMember(Name = "Month")]
|
||||
public int Month { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current second
|
||||
/// </summary>
|
||||
[DataMember(Name = "Second")]
|
||||
public int Sec { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current year
|
||||
/// </summary>
|
||||
[DataMember(Name = "Year")]
|
||||
public int Year { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controller Driver information.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class ControllerDriverInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current driver information
|
||||
/// </summary>
|
||||
[DataMember(Name = "CurrentDriver")]
|
||||
public string CurrentDriver { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all the controller drivers information
|
||||
/// </summary>
|
||||
[DataMember(Name = "ControllersDrivers")]
|
||||
public List<string> ControllersDrivers { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request for reboot
|
||||
/// </summary>
|
||||
[DataMember(Name = "RequestReboot")]
|
||||
public string RequestReboot { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispaly orientation information.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class DisplayOrientationInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the dispaly orientation information
|
||||
/// </summary>
|
||||
[DataMember(Name = "Orientation")]
|
||||
public int Orientation { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispaly resolution information.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class DisplayResolutionInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current display resolution
|
||||
/// </summary>
|
||||
[DataMember(Name = "Current")]
|
||||
public Resolution CurrentResolution { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of resolution specifications
|
||||
/// </summary>
|
||||
[DataMember(Name = "Resolutions")]
|
||||
public List<Resolution> Resolutions { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispaly resolution specifications.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class Resolution
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of supported display resolutions
|
||||
/// </summary>
|
||||
[DataMember(Name = "Resolution")]
|
||||
public string ResolutionDetail { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index for the resolution information
|
||||
/// </summary>
|
||||
[DataMember(Name = "Index")]
|
||||
public int Index { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error information if a request fails.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class ErrorInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the error code
|
||||
/// </summary>
|
||||
[DataMember(Name = "ErrorCode")]
|
||||
public int ErrorCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of the request
|
||||
/// </summary>
|
||||
[DataMember(Name = "Status")]
|
||||
public string Status { get; private set; }
|
||||
}
|
||||
|
||||
#endregion // Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,496 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="DevicePortal.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Issuer for the device certificate.
|
||||
/// </summary>
|
||||
public static readonly string DevicePortalCertificateIssuer = "Microsoft Windows Web Management";
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint used to access the certificate.
|
||||
/// </summary>
|
||||
public static readonly string RootCertificateEndpoint = "config/rootcertificate";
|
||||
|
||||
/// <summary>
|
||||
/// Expected number of OS version sections once the OS version is split by period characters
|
||||
/// </summary>
|
||||
private static readonly uint ExpectedOSVersionSections = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The target OS version section index once the OS version is split by periods
|
||||
/// </summary>
|
||||
private static readonly uint TargetOSVersionSection = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Device connection object.
|
||||
/// </summary>
|
||||
private IDevicePortalConnection deviceConnection;
|
||||
#if !WINDOWS_UWP
|
||||
|
||||
/// <summary>
|
||||
/// Initializes static members of the <see cref="DevicePortal" /> class.
|
||||
/// </summary>
|
||||
static DevicePortal()
|
||||
{
|
||||
System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
|
||||
}
|
||||
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DevicePortal" /> class.
|
||||
/// </summary>
|
||||
/// <param name="connection">Implementation of a connection object.</param>
|
||||
public DevicePortal(IDevicePortalConnection connection)
|
||||
{
|
||||
this.deviceConnection = connection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for reporting connection status.
|
||||
/// </summary>
|
||||
public event DeviceConnectionStatusEventHandler ConnectionStatus;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP Methods
|
||||
/// </summary>
|
||||
public enum HttpMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// The HTTP Get method.
|
||||
/// </summary>
|
||||
Get,
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP Put method.
|
||||
/// </summary>
|
||||
Put,
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP Post method.
|
||||
/// </summary>
|
||||
Post,
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP Delete method.
|
||||
/// </summary>
|
||||
Delete,
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP WebSocket method.
|
||||
/// </summary>
|
||||
WebSocket
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device address.
|
||||
/// </summary>
|
||||
public string Address
|
||||
{
|
||||
get { return this.deviceConnection.Connection.Authority; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status code for establishing our connection.
|
||||
/// </summary>
|
||||
public HttpStatusCode ConnectionHttpStatusCode
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a description of why the connection failed.
|
||||
/// </summary>
|
||||
public string ConnectionFailedDescription
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device operating system family.
|
||||
/// </summary>
|
||||
public string DeviceFamily
|
||||
{
|
||||
get
|
||||
{
|
||||
return (this.deviceConnection.Family != null) ? this.deviceConnection.Family : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the operating system version.
|
||||
/// </summary>
|
||||
public string OperatingSystemVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return (this.deviceConnection.OsInfo != null) ? this.deviceConnection.OsInfo.OsVersionString : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device platform.
|
||||
/// </summary>
|
||||
public DevicePortalPlatforms Platform
|
||||
{
|
||||
get
|
||||
{
|
||||
return (this.deviceConnection.OsInfo != null) ? this.deviceConnection.OsInfo.Platform : DevicePortalPlatforms.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device platform name.
|
||||
/// </summary>
|
||||
public string PlatformName
|
||||
{
|
||||
get
|
||||
{
|
||||
return (this.deviceConnection.OsInfo != null) ? this.deviceConnection.OsInfo.PlatformName : "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the device pointed to by IDevicePortalConnection provided in the constructor.
|
||||
/// </summary>
|
||||
/// <param name="ssid">Optional network SSID.</param>
|
||||
/// <param name="ssidKey">Optional network key.</param>
|
||||
/// <param name="updateConnection">Indicates whether we should update this connection's IP address after connecting.</param>
|
||||
/// <param name="manualCertificate">A manually provided X509 Certificate for trust validation against this device.</param>
|
||||
/// <remarks>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.</remarks>
|
||||
/// <returns>Task for tracking the connect.</returns>
|
||||
[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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">API endpoint we are calling.</param>
|
||||
/// <param name="directory">Directory to store our file.</param>
|
||||
/// <param name="httpMethod">The http method to be performed.</param>
|
||||
/// <param name="requestBody">An optional stream to use for the request body content.</param>
|
||||
/// <param name="requestBodyContentType">The content type of the request stream.</param>
|
||||
/// <returns>Task waiting for HTTP call to return and file copy to complete.</returns>
|
||||
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<object> websocket = new WebSocket<object>(this.deviceConnection, true);
|
||||
#else
|
||||
WebSocket<object> websocket = new WebSocket<object>(this.deviceConnection, this.ServerCertificateValidation, true);
|
||||
#endif // WINDOWS_UWP
|
||||
|
||||
ManualResetEvent streamReceived = new ManualResetEvent(false);
|
||||
Stream stream = null;
|
||||
|
||||
WindowsDevicePortal.WebSocketStreamReceivedEventInternalHandler<object> streamReceivedHandler =
|
||||
delegate(WebSocket<object> sender, WebSocketMessageReceivedEventArgs<Stream> args)
|
||||
{
|
||||
if (args.Message != null)
|
||||
{
|
||||
stream = args.Message;
|
||||
streamReceived.Set();
|
||||
}
|
||||
};
|
||||
|
||||
websocket.WebSocketStreamReceived += streamReceivedHandler;
|
||||
|
||||
await websocket.ConnectAsync(endpoint);
|
||||
|
||||
await websocket.ReceiveMessagesAsync();
|
||||
|
||||
streamReceived.WaitOne();
|
||||
|
||||
await websocket.CloseAsync();
|
||||
|
||||
websocket.WebSocketStreamReceived -= streamReceivedHandler;
|
||||
|
||||
using (stream)
|
||||
{
|
||||
using (var fileStream = File.Create(filepath))
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (HttpMethods.Put == httpMethod)
|
||||
{
|
||||
#if WINDOWS_UWP
|
||||
HttpStreamContent streamContent = null;
|
||||
#else
|
||||
StreamContent streamContent = null;
|
||||
#endif // WINDOWS_UWP
|
||||
|
||||
if (requestBody != null)
|
||||
{
|
||||
#if WINDOWS_UWP
|
||||
streamContent = new HttpStreamContent(requestBody.AsInputStream());
|
||||
streamContent.Headers.ContentType = new HttpMediaTypeHeaderValue(requestBodyContentType);
|
||||
#else
|
||||
streamContent = new StreamContent(requestBody);
|
||||
streamContent.Headers.ContentType = new MediaTypeHeaderValue(requestBodyContentType);
|
||||
#endif // WINDOWS_UWP
|
||||
}
|
||||
|
||||
using (Stream dataStream = await this.PutAsync(uri, streamContent))
|
||||
{
|
||||
using (var fileStream = File.Create(filepath))
|
||||
{
|
||||
dataStream.Seek(0, SeekOrigin.Begin);
|
||||
dataStream.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (HttpMethods.Post == httpMethod)
|
||||
{
|
||||
using (Stream dataStream = await this.PostAsync(uri, requestBody, requestBodyContentType))
|
||||
{
|
||||
using (var fileStream = File.Create(filepath))
|
||||
{
|
||||
dataStream.Seek(0, SeekOrigin.Begin);
|
||||
dataStream.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (HttpMethods.Delete == httpMethod)
|
||||
{
|
||||
using (Stream dataStream = await this.DeleteAsync(uri))
|
||||
{
|
||||
using (var fileStream = File.Create(filepath))
|
||||
{
|
||||
dataStream.Seek(0, SeekOrigin.Begin);
|
||||
dataStream.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (HttpMethods.Get == httpMethod)
|
||||
{
|
||||
using (Stream dataStream = await this.GetAsync(uri))
|
||||
{
|
||||
using (var fileStream = File.Create(filepath))
|
||||
{
|
||||
dataStream.Seek(0, SeekOrigin.Begin);
|
||||
dataStream.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException(string.Format("Unsupported HttpMethod {0}", httpMethod.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the connection status back to the caller
|
||||
/// </summary>
|
||||
/// <param name="status">Status of the connect attempt.</param>
|
||||
/// <param name="phase">Current phase of the connection attempt.</param>
|
||||
/// <param name="message">Optional message describing the connection status.</param>
|
||||
private void SendConnectionStatus(
|
||||
DeviceConnectionStatus status,
|
||||
DeviceConnectionPhase phase,
|
||||
string message = "")
|
||||
{
|
||||
this.ConnectionStatus?.Invoke(this, new DeviceConnectionStatusEventArgs(status, phase, message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to determine if our connection is using HTTPS
|
||||
/// </summary>
|
||||
/// <returns>Whether we are using HTTPS</returns>
|
||||
private bool IsUsingHttps()
|
||||
{
|
||||
return this.deviceConnection.Connection.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="ApplicationInstallStatus.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:FileHeaderFileNameDocumentationMustMatchTypeName", Justification = "Filename matches the enum better than the ApplicationInstallStatusEventArgs class.")]
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Application install status event handler
|
||||
/// </summary>
|
||||
/// <param name="sender">sender object</param>
|
||||
/// <param name="args">install args</param>
|
||||
public delegate void ApplicationInstallStatusEventHandler(DevicePortal sender, ApplicationInstallStatusEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Application install status
|
||||
/// </summary>
|
||||
public enum ApplicationInstallStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// No install status
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Installation is in progress
|
||||
/// </summary>
|
||||
InProgress,
|
||||
|
||||
/// <summary>
|
||||
/// Installation is completed
|
||||
/// </summary>
|
||||
Completed,
|
||||
|
||||
/// <summary>
|
||||
/// Installation failed
|
||||
/// </summary>
|
||||
Failed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Install phase
|
||||
/// </summary>
|
||||
public enum ApplicationInstallPhase
|
||||
{
|
||||
/// <summary>
|
||||
/// Idle phase
|
||||
/// </summary>
|
||||
Idle,
|
||||
|
||||
/// <summary>
|
||||
/// Uninstalling the previous version
|
||||
/// </summary>
|
||||
UninstallingPreviousVersion,
|
||||
|
||||
/// <summary>
|
||||
/// Copying the package file
|
||||
/// </summary>
|
||||
CopyingFile,
|
||||
|
||||
/// <summary>
|
||||
/// Installing the package
|
||||
/// </summary>
|
||||
Installing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Application install status event args
|
||||
/// </summary>
|
||||
public class ApplicationInstallStatusEventArgs : System.EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ApplicationInstallStatusEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="status">Install status</param>
|
||||
/// <param name="phase">Install phase</param>
|
||||
/// <param name="message">Install message</param>
|
||||
internal ApplicationInstallStatusEventArgs(
|
||||
ApplicationInstallStatus status,
|
||||
ApplicationInstallPhase phase,
|
||||
string message = "")
|
||||
{
|
||||
this.Status = status;
|
||||
this.Phase = phase;
|
||||
this.Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the install status
|
||||
/// </summary>
|
||||
public ApplicationInstallStatus Status { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the install phase
|
||||
/// </summary>
|
||||
public ApplicationInstallPhase Phase { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the install message
|
||||
/// </summary>
|
||||
public string Message { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="ConnectionStatus.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:FileHeaderFileNameDocumentationMustMatchTypeName", Justification = "Filename matches the enum better than the EventArgs class.")]
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Handler for reporting on device connection status
|
||||
/// </summary>
|
||||
/// <param name="sender">sender object</param>
|
||||
/// <param name="args">connection status details</param>
|
||||
public delegate void DeviceConnectionStatusEventHandler(DevicePortal sender, DeviceConnectionStatusEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Connection status enumeration
|
||||
/// </summary>
|
||||
public enum DeviceConnectionStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// No status
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Currently Connecting
|
||||
/// </summary>
|
||||
Connecting,
|
||||
|
||||
/// <summary>
|
||||
/// Connection complete
|
||||
/// </summary>
|
||||
Connected,
|
||||
|
||||
/// <summary>
|
||||
/// Connection failed
|
||||
/// </summary>
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connection phase enumeration
|
||||
/// </summary>
|
||||
public enum DeviceConnectionPhase
|
||||
{
|
||||
/// <summary>
|
||||
/// Idle phase
|
||||
/// </summary>
|
||||
Idle,
|
||||
|
||||
/// <summary>
|
||||
/// Acquiring the device certificate
|
||||
/// </summary>
|
||||
AcquiringCertificate,
|
||||
|
||||
/// <summary>
|
||||
/// Determining the device connection requirements
|
||||
/// </summary>
|
||||
DeterminingConnectionRequirements,
|
||||
|
||||
/// <summary>
|
||||
/// Getting some basic information about the device OS
|
||||
/// </summary>
|
||||
RequestingOperatingSystemInformation,
|
||||
|
||||
/// <summary>
|
||||
/// Connecting the device to a provided network
|
||||
/// </summary>
|
||||
ConnectingToTargetNetwork,
|
||||
|
||||
/// <summary>
|
||||
/// Updating the device address to reflect the new network
|
||||
/// </summary>
|
||||
UpdatingDeviceAddress
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the details about the connection status
|
||||
/// </summary>
|
||||
public class DeviceConnectionStatusEventArgs : System.EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeviceConnectionStatusEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="status">Status of the connection</param>
|
||||
/// <param name="phase">Phase of the connection</param>
|
||||
/// <param name="message">Optional message describing our connection/phase</param>
|
||||
internal DeviceConnectionStatusEventArgs(
|
||||
DeviceConnectionStatus status,
|
||||
DeviceConnectionPhase phase,
|
||||
string message = "")
|
||||
{
|
||||
this.Status = status;
|
||||
this.Phase = phase;
|
||||
this.Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of the connection attempt
|
||||
/// </summary>
|
||||
public DeviceConnectionStatus Status { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the phase of the connection
|
||||
/// </summary>
|
||||
public DeviceConnectionPhase Phase { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a message describing the connection status/phase
|
||||
/// </summary>
|
||||
public string Message { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="WebSocketMessageReceivedEventArgs.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Web socket message received event handler
|
||||
/// </summary>
|
||||
/// <param name="sender">Sender <see cref="DevicePortal"/> object</param>
|
||||
/// <param name="args">Web socket message received args</param>
|
||||
/// <typeparam name="T">Return type for the websocket messages.</typeparam>
|
||||
public delegate void WebSocketMessageReceivedEventHandler<T>(DevicePortal sender, WebSocketMessageReceivedEventArgs<T> args);
|
||||
|
||||
/// <summary>
|
||||
/// Web socket message received event args
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type for the websocket messages.</typeparam>
|
||||
public class WebSocketMessageReceivedEventArgs<T> : System.EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WebSocketMessageReceivedEventArgs{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The message from the web socket.</param>
|
||||
internal WebSocketMessageReceivedEventArgs(
|
||||
T message)
|
||||
{
|
||||
this.Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the web socket message
|
||||
/// </summary>
|
||||
public T Message { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="DevicePortalException.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
#if !WINDOWS_UWP
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
#endif // !WINDOWS_UWP
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Security;
|
||||
using System.Threading.Tasks;
|
||||
#if WINDOWS_UWP
|
||||
using Windows.Foundation;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.Web.Http;
|
||||
#endif // WINDOWS_UWP
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Base exception class for a Device Portal exception
|
||||
/// </summary>
|
||||
#if !WINDOWS_UWP
|
||||
[Serializable]
|
||||
#endif // !WINDOWS_UWP
|
||||
public class DevicePortalException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DevicePortalException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The Http status code.</param>
|
||||
/// <param name="errorResponse">Http parsed error response message.</param>
|
||||
/// <param name="requestUri">Request URI which threw the exception.</param>
|
||||
/// <param name="message">Optional exception message.</param>
|
||||
/// <param name="innerException">Optional inner exception.</param>
|
||||
public DevicePortalException(
|
||||
HttpStatusCode statusCode,
|
||||
HttpErrorResponse errorResponse,
|
||||
Uri requestUri = null,
|
||||
string message = "",
|
||||
Exception innerException = null) : this(
|
||||
statusCode,
|
||||
errorResponse.Reason,
|
||||
requestUri,
|
||||
message,
|
||||
innerException)
|
||||
{
|
||||
this.HResult = errorResponse.ErrorCode;
|
||||
this.Reason = errorResponse.ErrorMessage;
|
||||
|
||||
// If we didn't get the Hresult and reason from these properties, try the other ones.
|
||||
if (this.HResult == 0)
|
||||
{
|
||||
this.HResult = errorResponse.Code;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(this.Reason))
|
||||
{
|
||||
this.Reason = errorResponse.Reason;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DevicePortalException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">Http status code.</param>
|
||||
/// <param name="reason">Reason for exception.</param>
|
||||
/// <param name="requestUri">Request URI which threw the exception.</param>
|
||||
/// <param name="message">Optional message.</param>
|
||||
/// <param name="innerException">Optional inner exception.</param>
|
||||
public DevicePortalException(
|
||||
HttpStatusCode statusCode,
|
||||
string reason,
|
||||
Uri requestUri = null,
|
||||
string message = "",
|
||||
Exception innerException = null) : base(
|
||||
message,
|
||||
innerException)
|
||||
{
|
||||
this.StatusCode = statusCode;
|
||||
this.Reason = reason;
|
||||
this.RequestUri = requestUri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP Status code.
|
||||
/// </summary>
|
||||
public HttpStatusCode StatusCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reason for the exception.
|
||||
/// </summary>
|
||||
public string Reason { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request URI that threw the exception.
|
||||
/// </summary>
|
||||
public Uri RequestUri { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DevicePortalException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="responseMessage">Http response message.</param>
|
||||
/// <param name="message">Optional exception message.</param>
|
||||
/// <param name="innerException">Optional inner exception.</param>
|
||||
/// <returns>async task</returns>
|
||||
public static async Task<DevicePortalException> CreateAsync(
|
||||
HttpResponseMessage responseMessage,
|
||||
string message = "",
|
||||
Exception innerException = null)
|
||||
{
|
||||
DevicePortalException error = new DevicePortalException(
|
||||
responseMessage.StatusCode,
|
||||
responseMessage.ReasonPhrase,
|
||||
responseMessage.RequestMessage != null ? responseMessage.RequestMessage.RequestUri : null,
|
||||
message,
|
||||
innerException);
|
||||
try
|
||||
{
|
||||
if (responseMessage.Content != null)
|
||||
{
|
||||
Stream dataStream = null;
|
||||
#if !WINDOWS_UWP
|
||||
using (HttpContent content = responseMessage.Content)
|
||||
{
|
||||
dataStream = new MemoryStream();
|
||||
|
||||
await content.CopyToAsync(dataStream).ConfigureAwait(false);
|
||||
|
||||
// Ensure we point the stream at the origin.
|
||||
dataStream.Position = 0;
|
||||
}
|
||||
#else // WINDOWS_UWP
|
||||
IBuffer dataBuffer = null;
|
||||
using (IHttpContent messageContent = responseMessage.Content)
|
||||
{
|
||||
dataBuffer = await messageContent.ReadAsBufferAsync();
|
||||
|
||||
if (dataBuffer != null)
|
||||
{
|
||||
dataStream = dataBuffer.AsStream();
|
||||
}
|
||||
}
|
||||
#endif // WINDOWS_UWP
|
||||
|
||||
if (dataStream != null)
|
||||
{
|
||||
HttpErrorResponse errorResponse = DevicePortal.ReadJsonStream<HttpErrorResponse>(dataStream);
|
||||
|
||||
if (errorResponse != null)
|
||||
{
|
||||
error.HResult = errorResponse.ErrorCode;
|
||||
error.Reason = errorResponse.ErrorMessage;
|
||||
|
||||
// If we didn't get the Hresult and reason from these properties, try the other ones.
|
||||
if (error.HResult == 0)
|
||||
{
|
||||
error.HResult = errorResponse.Code;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(error.Reason))
|
||||
{
|
||||
error.Reason = errorResponse.Reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Do nothing if we fail to get additional error details from the response body.
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
/// <summary>
|
||||
/// Get object data override
|
||||
/// </summary>
|
||||
/// <param name="info">Serialization info</param>
|
||||
/// <param name="context">Streaming context</param>
|
||||
[SecurityCritical]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
}
|
||||
#endif // !WINDOWS_UWP
|
||||
|
||||
#region data contract
|
||||
|
||||
/// <summary>
|
||||
/// Object containing additional error information from
|
||||
/// an HTTP response.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class HttpErrorResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the ErrorCode
|
||||
/// </summary>
|
||||
[DataMember(Name = "ErrorCode")]
|
||||
public int ErrorCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Code (used by some endpoints instead of ErrorCode).
|
||||
/// </summary>
|
||||
[DataMember(Name = "Code")]
|
||||
public int Code { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ErrorMessage
|
||||
/// </summary>
|
||||
[DataMember(Name = "ErrorMessage")]
|
||||
public string ErrorMessage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Reason (used by some endpoints instead of ErrorMessage).
|
||||
/// </summary>
|
||||
[DataMember(Name = "Reason")]
|
||||
public string Reason { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the operation succeeded. For an error this should generally be false if present.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Success")]
|
||||
public bool Success { get; private set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
// <copyright file="HolographicOs.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for Holographic OS methods
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for getting or setting Interpupilary distance
|
||||
/// </summary>
|
||||
public static readonly string HolographicIpdApi = "api/holographic/os/settings/ipd";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting a list of running HoloLens specific services.
|
||||
/// </summary>
|
||||
public static readonly string HolographicServicesApi = "api/holographic/os/services";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting or setting HTTPS setting
|
||||
/// </summary>
|
||||
public static readonly string HolographicWebManagementHttpSettingsApi = "api/holographic/os/webmanagement/settings/https";
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration describing the status of a process
|
||||
/// </summary>
|
||||
public enum ProcessStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// The process is running
|
||||
/// </summary>
|
||||
Running = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The process is stopped
|
||||
/// </summary>
|
||||
Stopped
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of the Holographic Services on this HoloLens.
|
||||
/// </summary>
|
||||
/// <returns>HolographicServices object describing the state of the Holographic services.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<HolographicServices> GetHolographicServiceState()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
return await this.GetAsync<HolographicServices>(HolographicServicesApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the interpupilary distance registered on the device.
|
||||
/// </summary>
|
||||
/// <returns>Interpupilary distance, in millimeters.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<float> GetInterPupilaryDistanceAsync()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
InterPupilaryDistance ipd = await this.GetAsync<InterPupilaryDistance>(HolographicIpdApi);
|
||||
return ipd.Ipd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the WiFi http security requirements for communication with the device.
|
||||
/// </summary>
|
||||
/// <param name="httpsRequired">Desired value for HTTPS communication</param>
|
||||
/// <returns>True if WiFi based communication requires a secure connection, false otherwise.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task SetIsHttpsRequiredAsync(bool httpsRequired)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
await this.PostAsync(
|
||||
HolographicWebManagementHttpSettingsApi,
|
||||
string.Format("required={0}", httpsRequired));
|
||||
|
||||
this.deviceConnection.UpdateConnection(httpsRequired);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the interpupilary distance registered on the device.
|
||||
/// </summary>
|
||||
/// <param name="ipd">Interpupilary distance, in millimeters.</param>
|
||||
/// <returns>Task for tracking the POST call</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task SetInterPupilaryDistanceAsync(float ipd)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
string payload = string.Format("ipd={0}", (int)(ipd * 1000.0f));
|
||||
|
||||
await this.PostAsync(
|
||||
HolographicIpdApi,
|
||||
payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the WiFi http security requirements for communication with the device.
|
||||
/// </summary>
|
||||
/// <returns>True if WiFi based communication requires a secure connection, false otherwise.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<bool> GetIsHttpsRequiredAsync()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
WebManagementHttpSettings httpSettings = await this.GetAsync<WebManagementHttpSettings>(HolographicWebManagementHttpSettingsApi);
|
||||
return httpSettings.IsHttpsRequired;
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
/// <summary>
|
||||
/// Object reporesentation of the status of the Holographic services
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class HolographicServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the status for the collection of holographic services
|
||||
/// </summary>
|
||||
[DataMember(Name = "SoftwareStatus")]
|
||||
public HolographicSoftwareStatus Status { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representation of the collection of holographic services.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class HolographicSoftwareStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the status of dwm.exe
|
||||
/// </summary>
|
||||
[DataMember(Name = "dwm.exe")]
|
||||
public ServiceStatus Dwm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of holoshellapp.exe
|
||||
/// </summary>
|
||||
[DataMember(Name = "holoshellapp.exe")]
|
||||
public ServiceStatus HoloShellApp { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of holosi.exe
|
||||
/// </summary>
|
||||
[DataMember(Name = "holosi.exe")]
|
||||
public ServiceStatus HoloSi { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of mixedrealitycapture.exe
|
||||
/// </summary>
|
||||
[DataMember(Name = "mixedrealitycapture.exe")]
|
||||
public ServiceStatus MixedRealitytCapture { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of sihost.exe
|
||||
/// </summary>
|
||||
[DataMember(Name = "sihost.exe")]
|
||||
public ServiceStatus SiHost { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of spectrum.exe
|
||||
/// </summary>
|
||||
[DataMember(Name = "spectrum.exe")]
|
||||
public ServiceStatus Spectrum { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representation for Interpupilary distance
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class InterPupilaryDistance
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the raw interpupilary distance
|
||||
/// </summary>
|
||||
[DataMember(Name = "ipd")]
|
||||
public int IpdRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the interpupilary distance
|
||||
/// </summary>
|
||||
public float Ipd
|
||||
{
|
||||
get { return this.IpdRaw / 1000.0f; }
|
||||
set { this.IpdRaw = (int)(value * 1000); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representation of the status of a service
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class ServiceStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the raw value returned for the expected service status
|
||||
/// </summary>
|
||||
[DataMember(Name = "Expected")]
|
||||
public string ExpectedRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw value returned for the observed service status
|
||||
/// </summary>
|
||||
[DataMember(Name = "Observed")]
|
||||
public string ObservedRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the the expected service status
|
||||
/// </summary>
|
||||
public ProcessStatus Expected
|
||||
{
|
||||
get
|
||||
{
|
||||
return (this.ExpectedRaw == "Running") ? ProcessStatus.Running : ProcessStatus.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the the observed service status
|
||||
/// </summary>
|
||||
public ProcessStatus Observed
|
||||
{
|
||||
get
|
||||
{
|
||||
return (this.ObservedRaw == "Running") ? ProcessStatus.Running : ProcessStatus.Stopped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representation for HTTP settings
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class WebManagementHttpSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether HTTPS is required
|
||||
/// </summary>
|
||||
[DataMember(Name = "httpsRequired")]
|
||||
public bool IsHttpsRequired { get; private set; }
|
||||
}
|
||||
|
||||
#endregion // Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="HolographicPerception.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for Holographic Perception methods
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for running a Perception client.
|
||||
/// </summary>
|
||||
public static readonly string HolographicPerceptionClient = "api/holographic/perception/client";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting or setting the Holographic Perception Simulation control mode.
|
||||
/// </summary>
|
||||
public static readonly string HolographicSimulationModeApi = "api/holographic/simulation/control/mode";
|
||||
|
||||
/// <summary>
|
||||
/// API for controlling the Holographic Perception Simulation control stream.
|
||||
/// </summary>
|
||||
public static readonly string HolographicSimulationStreamApi = "api/holographic/simulation/control/stream";
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration defining the control modes used by the Holographic Perception Simulation.
|
||||
/// </summary>
|
||||
public enum SimulationControlMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Default mode.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Simulation mode.
|
||||
/// </summary>
|
||||
Simulation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration defining the priority levels for the Holographic Perception Simulation control stream.
|
||||
/// </summary>
|
||||
public enum SimulationControlStreamPriority
|
||||
{
|
||||
/// <summary>
|
||||
/// Low priority.
|
||||
/// </summary>
|
||||
Low = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Normal priority.
|
||||
/// </summary>
|
||||
Normal
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a simulation control stream.
|
||||
/// </summary>
|
||||
/// <param name="priority">The control stream priority.</param>
|
||||
/// <returns>The identifier of the created stream.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<string> CreatePerceptionSimulationControlStreamAsync(SimulationControlStreamPriority priority)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
if (!(await this.VerifySimulationControlModeAsync(SimulationControlMode.Simulation)))
|
||||
{
|
||||
throw new InvalidOperationException("The simulation control mode on the target HoloLens must be 'Simulation'.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"priority={0}",
|
||||
(int)priority);
|
||||
|
||||
PerceptionSimulationControlStreamId controlStreamId = await this.GetAsync<PerceptionSimulationControlStreamId>(
|
||||
HolographicSimulationStreamApi,
|
||||
payload);
|
||||
|
||||
return controlStreamId.StreamId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a simulation control stream.
|
||||
/// </summary>
|
||||
/// <param name="streamId">The identifier of the stream to be deleted.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task DeletePerceptionSimulationControlStreamAsync(string streamId)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
if (!(await this.VerifySimulationControlModeAsync(SimulationControlMode.Simulation)))
|
||||
{
|
||||
throw new InvalidOperationException("The simulation control mode on the target HoloLens must be 'Simulation'.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"streamId={0}",
|
||||
streamId);
|
||||
|
||||
await this.DeleteAsync(
|
||||
HolographicSimulationStreamApi,
|
||||
payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the perception simulation control mode.
|
||||
/// </summary>
|
||||
/// <returns>The simulation control mode.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<SimulationControlMode> GetPerceptionSimulationControlModeAsync()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
PerceptionSimulationControlMode controlMode = await this.GetAsync<PerceptionSimulationControlMode>(HolographicSimulationModeApi);
|
||||
return controlMode.Mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the perception simulation control mode.
|
||||
/// </summary>
|
||||
/// <param name="mode">The simulation control mode.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task SetPerceptionSimulationControlModeAsync(SimulationControlMode mode)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"mode={0}",
|
||||
(int)mode);
|
||||
await this.PostAsync(HolographicSimulationModeApi, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the current simulation control mode with the expected mode.
|
||||
/// </summary>
|
||||
/// <param name="expectedMode">The simulation control mode that we expect the device to be in.</param>
|
||||
/// <returns>The simulation control mode.</returns>
|
||||
private async Task<bool> VerifySimulationControlModeAsync(SimulationControlMode expectedMode)
|
||||
{
|
||||
SimulationControlMode simMode = await this.GetPerceptionSimulationControlModeAsync();
|
||||
return simMode == expectedMode;
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
/// <summary>
|
||||
/// Object representation of Perception Simulation control mode.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class PerceptionSimulationControlMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the control mode.
|
||||
/// </summary>
|
||||
[DataMember(Name = "mode")]
|
||||
public SimulationControlMode Mode { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representation of the response recevied when creating a Perception Simulation control stream.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class PerceptionSimulationControlStreamId
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the stream identifier.
|
||||
/// </summary>
|
||||
[DataMember(Name = "streamId")]
|
||||
public string StreamId { get; private set; }
|
||||
}
|
||||
#endregion // Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="HolographicThermal.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Thermal State enumeration
|
||||
/// </summary>
|
||||
public enum ThermalStages
|
||||
{
|
||||
/// <summary>
|
||||
/// No thermal stage
|
||||
/// </summary>
|
||||
Normal,
|
||||
|
||||
/// <summary>
|
||||
/// Warm stage
|
||||
/// </summary>
|
||||
Warm,
|
||||
|
||||
/// <summary>
|
||||
/// Critical stage
|
||||
/// </summary>
|
||||
Critical,
|
||||
|
||||
/// <summary>
|
||||
/// Unknown stage
|
||||
/// </summary>
|
||||
Unknown = 9999
|
||||
}
|
||||
|
||||
/// <content>
|
||||
/// Wrappers for Holographic Thermal methods
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for getting the thermal stage
|
||||
/// </summary>
|
||||
public static readonly string ThermalStageApi = "api/holographic/thermal/stage";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current thermal stage reading from the device.
|
||||
/// </summary>
|
||||
/// <returns>ThermalStages enum value.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<ThermalStages> GetThermalStageAsync()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
ThermalStage thermalStage = await this.GetAsync<ThermalStage>(ThermalStageApi);
|
||||
return thermalStage.Stage;
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
|
||||
/// <summary>
|
||||
/// Object representation of thermal stage
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class ThermalStage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the raw stage value
|
||||
/// </summary>
|
||||
[DataMember(Name = "CurrentStage")]
|
||||
public int StageRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enumeration value of the thermal stage
|
||||
/// </summary>
|
||||
public ThermalStages Stage
|
||||
{
|
||||
get
|
||||
{
|
||||
ThermalStages stage = ThermalStages.Unknown;
|
||||
|
||||
try
|
||||
{
|
||||
stage = (ThermalStages)Enum.ToObject(typeof(ThermalStages), this.StageRaw);
|
||||
|
||||
if (!Enum.IsDefined(typeof(ThermalStages), stage))
|
||||
{
|
||||
stage = ThermalStages.Unknown;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
stage = ThermalStages.Unknown;
|
||||
}
|
||||
|
||||
return stage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,723 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="MixedRealityCapture.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for Mixed reality capture methods
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for getting or deleting a Mixed Reality Capture file.
|
||||
/// </summary>
|
||||
public static readonly string MrcFileApi = "api/holographic/mrc/file";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting the list of Holographic Mixed Reality Capture files.
|
||||
/// </summary>
|
||||
public static readonly string MrcFileListApi = "api/holographic/mrc/files";
|
||||
|
||||
/// <summary>
|
||||
/// API for taking a Mixed Reality Capture photo.
|
||||
/// </summary>
|
||||
public static readonly string MrcPhotoApi = "api/holographic/mrc/photo";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting or setting the default Mixed Reality Capture settings.
|
||||
/// </summary>
|
||||
public static readonly string MrcSettingsApi = "api/holographic/mrc/settings";
|
||||
|
||||
/// <summary>
|
||||
/// API for starting a Holographic Mixed Reality Capture recording.
|
||||
/// </summary>
|
||||
public static readonly string MrcStartRecordingApi = "api/holographic/mrc/video/control/start";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting the Holographic Mixed Reality Capture status.
|
||||
/// </summary>
|
||||
public static readonly string MrcStatusApi = "api/holographic/mrc/status";
|
||||
|
||||
/// <summary>
|
||||
/// API for stopping a Holographic Mixed Reality Capture recording.
|
||||
/// </summary>
|
||||
public static readonly string MrcStopRecordingApi = "api/holographic/mrc/video/control/stop";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting a live Holographic Mixed Reality Capture stream.
|
||||
/// </summary>
|
||||
public static readonly string MrcLiveStreamApi = "api/holographic/stream/live.mp4";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting a high resolution live Holographic Mixed Reality Capture stream.
|
||||
/// </summary>
|
||||
public static readonly string MrcLiveStreamHighResApi = "api/holographic/stream/live_high.mp4";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting a low resolution live Holographic Mixed Reality Capture stream.
|
||||
/// </summary>
|
||||
public static readonly string MrcLiveStreamLowResApi = "api/holographic/stream/live_low.mp4";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting a medium resolution live Holographic Mixed Reality Capture stream.
|
||||
/// </summary>
|
||||
public static readonly string MrcLiveStreamMediumResApi = "api/holographic/stream/live_med.mp4";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting a mixed reality capture thumbnail
|
||||
/// </summary>
|
||||
public static readonly string MrcThumbnailApi = "api/holographic/mrc/thumbnail";
|
||||
|
||||
/// <summary>
|
||||
/// Removes a Mixed Reality Capture file from the device's local storage.
|
||||
/// </summary>
|
||||
/// <param name="fileName">The name of the file to be deleted.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task DeleteMrcFileAsync(string fileName)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
await this.DeleteAsync(
|
||||
MrcFileApi,
|
||||
string.Format("filename={0}", Utilities.Hex64Encode(fileName)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the Uri for the high resolution Mixed Reality Capture live stream.
|
||||
/// </summary>
|
||||
/// <param name="includeHolograms">Specifies whether or not to include holograms</param>
|
||||
/// <param name="includeColorCamera">Specifies whether or not to include the color camera</param>
|
||||
/// <param name="includeMicrophone">Specifies whether or not to include microphone data</param>
|
||||
/// <param name="includeAudio">Specifies whether or not to include audio data</param>
|
||||
/// <returns>Uri used to retreive the Mixed Reality Capture stream.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public Uri GetHighResolutionMrcLiveStreamUri(
|
||||
bool includeHolograms = true,
|
||||
bool includeColorCamera = true,
|
||||
bool includeMicrophone = true,
|
||||
bool includeAudio = true)
|
||||
{
|
||||
string payload = string.Format(
|
||||
"holo={0}&pv={1}&mic={2}&loopback={3}",
|
||||
includeHolograms,
|
||||
includeColorCamera,
|
||||
includeMicrophone,
|
||||
includeAudio).ToLower();
|
||||
|
||||
return Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
MrcLiveStreamHighResApi,
|
||||
payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the Uri for the low resolution Mixed Reality Capture live stream.
|
||||
/// </summary>
|
||||
/// <param name="includeHolograms">Specifies whether or not to include holograms</param>
|
||||
/// <param name="includeColorCamera">Specifies whether or not to include the color camera</param>
|
||||
/// <param name="includeMicrophone">Specifies whether or not to include microphone data</param>
|
||||
/// <param name="includeAudio">Specifies whether or not to include audio data</param>
|
||||
/// <returns>Uri used to retreive the Mixed Reality Capture stream.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public Uri GetLowResolutionMrcLiveStreamUri(
|
||||
bool includeHolograms = true,
|
||||
bool includeColorCamera = true,
|
||||
bool includeMicrophone = true,
|
||||
bool includeAudio = true)
|
||||
{
|
||||
string payload = string.Format(
|
||||
"holo={0}&pv={1}&mic={2}&loopback={3}",
|
||||
includeHolograms,
|
||||
includeColorCamera,
|
||||
includeMicrophone,
|
||||
includeAudio).ToLower();
|
||||
|
||||
return Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
MrcLiveStreamLowResApi,
|
||||
payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the Uri for the medium resolution Mixed Reality Capture live stream.
|
||||
/// </summary>
|
||||
/// <param name="includeHolograms">Specifies whether or not to include holograms</param>
|
||||
/// <param name="includeColorCamera">Specifies whether or not to include the color camera</param>
|
||||
/// <param name="includeMicrophone">Specifies whether or not to include microphone data</param>
|
||||
/// <param name="includeAudio">Specifies whether or not to include audio data</param>
|
||||
/// <returns>Uri used to retreive the Mixed Reality Capture stream.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public Uri GetMediumResolutionMrcLiveStreamUri(
|
||||
bool includeHolograms = true,
|
||||
bool includeColorCamera = true,
|
||||
bool includeMicrophone = true,
|
||||
bool includeAudio = true)
|
||||
{
|
||||
string payload = string.Format(
|
||||
"holo={0}&pv={1}&mic={2}&loopback={3}",
|
||||
includeHolograms,
|
||||
includeColorCamera,
|
||||
includeMicrophone,
|
||||
includeAudio).ToLower();
|
||||
|
||||
return Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
MrcLiveStreamMediumResApi,
|
||||
payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the capture file data
|
||||
/// </summary>
|
||||
/// <param name="fileName">Name of the file to retrieve.</param>
|
||||
/// <param name="isThumbnailRequest">Specifies whether or not we are requesting a thumbnail image.</param>
|
||||
/// <returns>Byte array containing the file data.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<byte[]> GetMrcFileDataAsync(
|
||||
string fileName,
|
||||
bool isThumbnailRequest = false)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
byte[] dataBytes = null;
|
||||
|
||||
string apiPath = isThumbnailRequest ? MrcThumbnailApi : MrcFileApi;
|
||||
|
||||
string payload = string.Format("filename={0}", Utilities.Hex64Encode(fileName));
|
||||
Uri uri = Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
apiPath,
|
||||
payload);
|
||||
|
||||
using (Stream dataStream = await this.GetAsync(uri))
|
||||
{
|
||||
dataBytes = new byte[dataStream.Length];
|
||||
dataStream.Read(dataBytes, 0, dataBytes.Length);
|
||||
}
|
||||
|
||||
return dataBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of capture files
|
||||
/// </summary>
|
||||
/// <returns>List of the capture files</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<MrcFileList> GetMrcFileListAsync()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
MrcFileList mrcFileList = await this.GetAsync<MrcFileList>(MrcFileListApi);
|
||||
|
||||
foreach (MrcFileInformation mfi in mrcFileList.Files)
|
||||
{
|
||||
try
|
||||
{
|
||||
mfi.Thumbnail = await this.GetMrcThumbnailDataAsync(mfi.FileName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return mrcFileList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the Uri for the Mixed Reality Capture live stream using the default resolution.
|
||||
/// </summary>
|
||||
/// <param name="includeHolograms">Specifies whether or not to include holograms</param>
|
||||
/// <param name="includeColorCamera">Specifies whether or not to include the color camera</param>
|
||||
/// <param name="includeMicrophone">Specifies whether or not to include microphone data</param>
|
||||
/// <param name="includeAudio">Specifies whether or not to include audio data</param>
|
||||
/// <returns>Uri used to retreive the Mixed Reality Capture stream.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public Uri GetMrcLiveStreamUri(
|
||||
bool includeHolograms = true,
|
||||
bool includeColorCamera = true,
|
||||
bool includeMicrophone = true,
|
||||
bool includeAudio = true)
|
||||
{
|
||||
string payload = string.Format(
|
||||
"holo={0}&pv={1}&mic={2}&loopback={3}",
|
||||
includeHolograms,
|
||||
includeColorCamera,
|
||||
includeMicrophone,
|
||||
includeAudio).ToLower();
|
||||
|
||||
return Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
MrcLiveStreamApi,
|
||||
payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Mixed Reality Capture settings
|
||||
/// </summary>
|
||||
/// <returns>MrcSettings object containing the current settings</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<MrcSettings> GetMrcSettingsAsync()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
return await this.GetAsync<MrcSettings>(MrcSettingsApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of the reality capture
|
||||
/// </summary>
|
||||
/// <returns>Status of the capture</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<MrcStatus> GetMrcStatusAsync()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
return await this.GetAsync<MrcStatus>(MrcStatusApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets thumbnail data for the capture
|
||||
/// </summary>
|
||||
/// <param name="fileName">Name of the capture file</param>
|
||||
/// <returns>Byte array containing the thumbnail image data</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<byte[]> GetMrcThumbnailDataAsync(string fileName)
|
||||
{
|
||||
// GetMrcFileData checks for the appropriate platform. We do not need to duplicate the check here.
|
||||
return await this.GetMrcFileDataAsync(fileName, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default Mixed Reality Capture settings
|
||||
/// </summary>
|
||||
/// <param name="settings">Mixed Reality Capture settings to be used as the default.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task SetMrcSettingsAsync(MrcSettings settings)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"holo={0}&pv={1}&mic={2}&appAudio={3}&vstabbuffer={4}",
|
||||
settings.IncludeHolograms.ToString().ToLower(),
|
||||
settings.IncludeColorCamera.ToString().ToLower(),
|
||||
settings.IncludeMicrophone.ToString().ToLower(),
|
||||
settings.IncludeAudio.ToString().ToLower(),
|
||||
settings.VideoStabilizationBuffer);
|
||||
|
||||
await this.PostAsync(
|
||||
MrcSettingsApi,
|
||||
payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a Mixed Reality Capture recording.
|
||||
/// </summary>
|
||||
/// <param name="includeHolograms">Specifies whether or not to include holograms</param>
|
||||
/// <param name="includeColorCamera">Specifies whether or not to include the color camera</param>
|
||||
/// <param name="includeMicrophone">Specifies whether or not to include microphone data</param>
|
||||
/// <param name="includeAudio">Specifies whether or not to include audio data</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task StartMrcRecordingAsync(
|
||||
bool includeHolograms = true,
|
||||
bool includeColorCamera = true,
|
||||
bool includeMicrophone = true,
|
||||
bool includeAudio = true)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"holo={0}&pv={1}&mic={2}&loopback={3}",
|
||||
includeHolograms,
|
||||
includeColorCamera,
|
||||
includeMicrophone,
|
||||
includeAudio).ToLower();
|
||||
|
||||
await this.PostAsync(
|
||||
MrcStartRecordingApi,
|
||||
payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the Mixed Reality Capture recording
|
||||
/// </summary>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task StopMrcRecordingAsync()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
await this.PostAsync(MrcStopRecordingApi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take a Mixed Reality Capture photo
|
||||
/// </summary>
|
||||
/// <param name="includeHolograms">Specifies whether or not to include holograms</param>
|
||||
/// <param name="includeColorCamera">Specifies whether or not to include the color camera</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task TakeMrcPhotoAsync(
|
||||
bool includeHolograms = true,
|
||||
bool includeColorCamera = true)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
await this.PostAsync(
|
||||
MrcPhotoApi,
|
||||
string.Format("holo={0}&pv={1}", includeHolograms, includeColorCamera).ToLower());
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
/// <summary>
|
||||
/// Object representation of the capture file list
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class MrcFileList
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of files
|
||||
/// </summary>
|
||||
[DataMember(Name = "MrcRecordings")]
|
||||
public List<MrcFileInformation> Files { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representation of an individual capture file
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class MrcFileInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the raw creation time
|
||||
/// </summary>
|
||||
[DataMember(Name = "CreationTime")]
|
||||
public long CreationTimeRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the filename
|
||||
/// </summary>
|
||||
[DataMember(Name = "FileName")]
|
||||
public string FileName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file size
|
||||
/// </summary>
|
||||
[DataMember(Name = "FileSize")]
|
||||
public uint FileSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the thumbnail
|
||||
/// </summary>
|
||||
public byte[] Thumbnail { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the creation time
|
||||
/// </summary>
|
||||
public DateTime Created
|
||||
{
|
||||
get { return new DateTime(this.CreationTimeRaw); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representation of the Capture status
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class MrcStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the device is recording
|
||||
/// </summary>
|
||||
[DataMember(Name = "IsRecording")]
|
||||
public bool IsRecording { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recording status
|
||||
/// </summary>
|
||||
[DataMember(Name = "ProcessStatus")]
|
||||
public MrcProcessStatus Status { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representation of the Mixed Reality Capture process status
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class MrcProcessStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the raw data for the Mixed Reality Capture process status
|
||||
/// </summary>
|
||||
[DataMember(Name = "MrcProcess")]
|
||||
public string MrcProcessRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of the Mixed Reality Capture process
|
||||
/// </summary>
|
||||
public ProcessStatus MrcProcess
|
||||
{
|
||||
get
|
||||
{
|
||||
return (this.MrcProcessRaw == "Running") ? ProcessStatus.Running : ProcessStatus.Stopped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representation of a Mixed Reality Capture setting.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class MrcSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the setting
|
||||
/// </summary>
|
||||
[DataMember(Name = "Setting")]
|
||||
public string Setting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the setting
|
||||
/// </summary>
|
||||
[DataMember(Name = "Value")]
|
||||
public object Value { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representing the collection of Mixed Reality Capture settings
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class MrcSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the collection of settings
|
||||
/// </summary>
|
||||
[DataMember(Name = "MrcSettings")]
|
||||
public List<MrcSetting> Settings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not holograms are included.
|
||||
/// </summary>
|
||||
public bool IncludeHolograms
|
||||
{
|
||||
get
|
||||
{
|
||||
object setting = this.GetSetting("EnableHolograms");
|
||||
|
||||
if (setting == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool)setting;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.SetSetting(
|
||||
"EnableHolograms",
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not color camera data is included.
|
||||
/// </summary>
|
||||
public bool IncludeColorCamera
|
||||
{
|
||||
get
|
||||
{
|
||||
object setting = this.GetSetting("EnableCamera");
|
||||
|
||||
if (setting == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool)setting;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.SetSetting(
|
||||
"EnableCamera",
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not microphone audio is included.
|
||||
/// </summary>
|
||||
public bool IncludeMicrophone
|
||||
{
|
||||
get
|
||||
{
|
||||
object setting = this.GetSetting("EnableMicrophone");
|
||||
|
||||
if (setting == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool)setting;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.SetSetting(
|
||||
"EnableMicrophone",
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not audio is included.
|
||||
/// </summary>
|
||||
public bool IncludeAudio
|
||||
{
|
||||
get
|
||||
{
|
||||
object setting = this.GetSetting("EnableSystemAudio");
|
||||
|
||||
if (setting == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool)setting;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.SetSetting(
|
||||
"EnableSystemAudio",
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size, in frames, of the video stabilization buffer.
|
||||
/// </summary>
|
||||
public int VideoStabilizationBuffer
|
||||
{
|
||||
get
|
||||
{
|
||||
object setting = this.GetSetting("VideoStabilizationBuffer");
|
||||
|
||||
if (setting == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int)setting;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
throw new ArgumentException("The video stabilization buffer value must be >= 0");
|
||||
}
|
||||
|
||||
this.SetSetting(
|
||||
"VideoStabilizationBuffer",
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of a setting
|
||||
/// </summary>
|
||||
/// <param name="settingName">The name of the setting</param>
|
||||
/// <returns>The value of the setting, or if not found, null.</returns>
|
||||
private object GetSetting(string settingName)
|
||||
{
|
||||
object value = null;
|
||||
|
||||
foreach (MrcSetting setting in this.Settings)
|
||||
{
|
||||
if (setting.Setting == settingName)
|
||||
{
|
||||
value = setting.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of a Mixed Reality Capture setting.
|
||||
/// </summary>
|
||||
/// <param name="settingName">The name of the setting</param>
|
||||
/// <param name="value">The value of the setting</param>
|
||||
private void SetSetting(
|
||||
string settingName,
|
||||
object value)
|
||||
{
|
||||
// If the setting exists, update the value, otherwise create a new one.
|
||||
MrcSetting mrcSetting = null;
|
||||
|
||||
foreach (MrcSetting setting in this.Settings)
|
||||
{
|
||||
if (setting.Setting == settingName)
|
||||
{
|
||||
mrcSetting = setting;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mrcSetting != null)
|
||||
{
|
||||
mrcSetting.Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
mrcSetting = new MrcSetting();
|
||||
mrcSetting.Setting = settingName;
|
||||
mrcSetting.Value = value;
|
||||
|
||||
this.Settings.Add(mrcSetting);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,426 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="PerceptionSimulationPlayback.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for Perception Simulation Playback methods
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for loading or unloading a Holographic Perception Simulation recording.
|
||||
/// </summary>
|
||||
public static readonly string HolographicSimulationPlaybackSessionFileApi = "api/holographic/simulation/playback/session/file";
|
||||
|
||||
/// <summary>
|
||||
/// API for pausing a Holographic Perception Simulation recording.
|
||||
/// </summary>
|
||||
public static readonly string HolographicSimulationPlaybackPauseApi = "api/holographic/simulation/playback/session/pause";
|
||||
|
||||
/// <summary>
|
||||
/// API for uploading or deleting a Holographic Perception Simulation recording file.
|
||||
/// </summary>
|
||||
public static readonly string HolographicSimulationPlaybackFileApi = "api/holographic/simulation/playback/file";
|
||||
|
||||
/// <summary>
|
||||
/// API for retrieving a list of a Holographic Perception Simulation recording files.
|
||||
/// </summary>
|
||||
public static readonly string HolographicSimulationPlaybackFilesApi = "api/holographic/simulation/playback/files";
|
||||
|
||||
/// <summary>
|
||||
/// API for starting playback of a Holographic Perception Simulation recording.
|
||||
/// </summary>
|
||||
public static readonly string HolographicSimulationPlaybackPlayApi = "api/holographic/simulation/playback/session/play";
|
||||
|
||||
/// <summary>
|
||||
/// API for getting the list of loaded Holographic Perception Simulation files.
|
||||
/// </summary>
|
||||
public static readonly string HolographicSimulationPlaybackSessionFilesApi = "api/holographic/simulation/playback/session/files";
|
||||
|
||||
/// <summary>
|
||||
/// API for retrieving the playback state of a Holographic Perception Simulation recording.
|
||||
/// </summary>
|
||||
public static readonly string HolographicSimulationPlaybackStateApi = "api/holographic/simulation/playback/session";
|
||||
|
||||
/// <summary>
|
||||
/// API for starting playback of a Holographic Perception Simulation recording.
|
||||
/// </summary>
|
||||
public static readonly string HolographicSimulationPlaybackStopApi = "api/holographic/simulation/playback/session/stop";
|
||||
|
||||
/// <summary>
|
||||
/// API for retrieving the types of data in a Holographic Perception Simulation recording.
|
||||
/// </summary>
|
||||
public static readonly string HolographicSimulationPlaybackDataTypesApi = "api/holographic/simulation/playback/session/types";
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration describing the available Holgraphic Simulation playback states.
|
||||
/// </summary>
|
||||
public enum HolographicSimulationPlaybackStates
|
||||
{
|
||||
/// <summary>
|
||||
/// The simulation has been stopped.
|
||||
/// </summary>
|
||||
Stopped = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The simulation is playing.
|
||||
/// </summary>
|
||||
Playing,
|
||||
|
||||
/// <summary>
|
||||
/// The simulation has been paused.
|
||||
/// </summary>
|
||||
Paused,
|
||||
|
||||
/// <summary>
|
||||
/// Playback has completed.
|
||||
/// </summary>
|
||||
Complete,
|
||||
|
||||
/// <summary>
|
||||
/// Playback is in an unexpected / unknown state.
|
||||
/// </summary>
|
||||
Unexpected = 9999
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified Holographic Simulation recording.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the recording to delete (ex: testsession.xef).</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task DeleteHolographicSimulationRecordingAsync(string name)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"recording={0}",
|
||||
name);
|
||||
|
||||
await this.DeleteAsync(HolographicSimulationPlaybackFileApi, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of Holographic Perception Simulation files on this HoloLens.
|
||||
/// </summary>
|
||||
/// <returns>HolographicSimulationPlaybackFiles object representing the files on the HoloLens</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<HolographicSimulationPlaybackFiles> GetHolographicSimulationPlaybackFilesAsync()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
return await this.GetHolographicSimulationPlaybackFilesPrivateAsync(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of loaded Holographic Perception Simulation files on this HoloLens.
|
||||
/// </summary>
|
||||
/// <returns>HolographicSimulationPlaybackFiles object representing the files loaded on the HoloLens</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<HolographicSimulationPlaybackFiles> GetHolographicSimulationPlaybackSessionFilesAsync()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
return await this.GetHolographicSimulationPlaybackFilesPrivateAsync(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the types of data that are in a loaded Holographic Perception Simulation file.
|
||||
/// </summary>
|
||||
/// <param name="recordingName">Name of the recording file, with extension.</param>
|
||||
/// <returns>HolographicSimulationDataTypes object representing they types of data in the file</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<HolographicSimulationDataTypes> GetHolographicSimulationPlaybackSessionDataTypesAsync(string recordingName)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"recording={0}",
|
||||
recordingName);
|
||||
|
||||
return await this.GetAsync<HolographicSimulationDataTypes>(
|
||||
HolographicSimulationPlaybackDataTypesApi,
|
||||
payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the playback state of a Holographic Simulation recording.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the recording (ex: testsession.xef).</param>
|
||||
/// <returns>HolographicSimulationPlaybackStates enum value describing the state of the recording.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<HolographicSimulationPlaybackStates> GetHolographicSimulationPlaybackStateAsync(string name)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
HolographicSimulationPlaybackStates playbackState = HolographicSimulationPlaybackStates.Unexpected;
|
||||
|
||||
string payload = string.Format(
|
||||
"recording={0}",
|
||||
name);
|
||||
|
||||
Uri uri = Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
HolographicSimulationPlaybackStateApi,
|
||||
payload);
|
||||
|
||||
using (Stream dataStream = await this.GetAsync(uri))
|
||||
{
|
||||
if ((dataStream != null) &&
|
||||
(dataStream.Length != 0))
|
||||
{
|
||||
// Try to get the session state.
|
||||
try
|
||||
{
|
||||
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(HolographicSimulationPlaybackSessionState));
|
||||
HolographicSimulationPlaybackSessionState sessionState = (HolographicSimulationPlaybackSessionState)serializer.ReadObject(dataStream);
|
||||
playbackState = sessionState.State;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We did not receive the session state, check to see if we received a simulation error.
|
||||
dataStream.Position = 0;
|
||||
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(HolographicSimulationError));
|
||||
HolographicSimulationError error = (HolographicSimulationError)serializer.ReadObject(dataStream);
|
||||
throw new InvalidOperationException(error.Reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return playbackState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the specified Holographic Simulation recording.
|
||||
/// </summary>
|
||||
/// <param name="recordingName">The name of the recording to load (ex: testsession.xef).</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task LoadHolographicSimulationRecordingAsync(string recordingName)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"recording={0}",
|
||||
recordingName);
|
||||
|
||||
await this.PostAsync(HolographicSimulationPlaybackSessionFileApi, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses playback of a Holographic Perception Simulation recording
|
||||
/// </summary>
|
||||
/// <param name="recordingName">The name of the recording to pause</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task PauseHolographicSimulationRecordingAsync(string recordingName)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"recording={0}",
|
||||
recordingName);
|
||||
|
||||
await this.PostAsync(HolographicSimulationPlaybackPauseApi, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts playback of a Holographic Perception Simulation recording
|
||||
/// </summary>
|
||||
/// <param name="recordingName">The name of the recording to play</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task PlayHolographicSimulationRecordingAsync(string recordingName)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"recording={0}",
|
||||
recordingName);
|
||||
|
||||
await this.PostAsync(HolographicSimulationPlaybackPlayApi, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops playback of a Holographic Perception Simulation recording
|
||||
/// </summary>
|
||||
/// <param name="recordingName">The name of the recording to stop</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task StopHolographicSimulationRecordingAsync(string recordingName)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"recording={0}",
|
||||
recordingName);
|
||||
|
||||
await this.PostAsync(HolographicSimulationPlaybackStopApi, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the specified Holographic Simulation recording.
|
||||
/// </summary>
|
||||
/// <param name="recordingName">The name of the recording to unload (ex: testsession.xef).</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task UnloadHolographicSimulationRecordingAsync(string recordingName)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"recording={0}",
|
||||
recordingName);
|
||||
|
||||
await this.DeleteAsync(HolographicSimulationPlaybackSessionFileApi, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of Holographic Perception Simulation files
|
||||
/// </summary>
|
||||
/// <param name="session">Value indicating whether or not to return loaded files.</param>
|
||||
/// <returns>Collection of Holographic Perception simulation file names</returns>
|
||||
private async Task<HolographicSimulationPlaybackFiles> GetHolographicSimulationPlaybackFilesPrivateAsync(bool session)
|
||||
{
|
||||
string apiPath = session ? HolographicSimulationPlaybackSessionFilesApi : HolographicSimulationPlaybackFilesApi;
|
||||
|
||||
return await this.GetAsync<HolographicSimulationPlaybackFiles>(apiPath);
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
/// <summary>
|
||||
/// Object representing the data types in a Holographic Perception Simulation file
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class HolographicSimulationDataTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the file contains hand data.
|
||||
/// </summary>
|
||||
[DataMember(Name = "hands")]
|
||||
public bool IncludesHands { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the file contains head data.
|
||||
/// </summary>
|
||||
[DataMember(Name = "head")]
|
||||
public bool IncludesHead { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the file contains environmentatl data.
|
||||
/// </summary>
|
||||
[DataMember(Name = "environment")]
|
||||
public bool IncludesEnvironment { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the file contains spatial mapping data.
|
||||
/// </summary>
|
||||
[DataMember(Name = "spatialMapping")]
|
||||
public bool IncludesSpatialMapping { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representation of the Holographic Perception Simulation files collection
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class HolographicSimulationPlaybackFiles
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of recording file names.
|
||||
/// </summary>
|
||||
[DataMember(Name = "recordings")]
|
||||
public List<string> Files { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representation of the Holographic Perception Simulation playback state
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class HolographicSimulationPlaybackSessionState
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the state value as a string
|
||||
/// </summary>
|
||||
[DataMember(Name = "state")]
|
||||
public string StateRaw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the playback session state
|
||||
/// </summary>
|
||||
public HolographicSimulationPlaybackStates State
|
||||
{
|
||||
get
|
||||
{
|
||||
HolographicSimulationPlaybackStates state = HolographicSimulationPlaybackStates.Unexpected;
|
||||
|
||||
switch (this.StateRaw)
|
||||
{
|
||||
case "stopped":
|
||||
state = HolographicSimulationPlaybackStates.Stopped;
|
||||
break;
|
||||
|
||||
case "playing":
|
||||
state = HolographicSimulationPlaybackStates.Playing;
|
||||
break;
|
||||
|
||||
case "paused":
|
||||
state = HolographicSimulationPlaybackStates.Paused;
|
||||
break;
|
||||
|
||||
case "end":
|
||||
state = HolographicSimulationPlaybackStates.Complete;
|
||||
break;
|
||||
|
||||
default:
|
||||
state = HolographicSimulationPlaybackStates.Unexpected;
|
||||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion // Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="PerceptionSimulationRecording.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Wrappers for Perception Simulation Recording methods
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// API for getting a Holographic Perception Simulation recording status.
|
||||
/// </summary>
|
||||
public static readonly string HolographicSimulationRecordingStatusApi = "api/holographic/simulation/recording/status";
|
||||
|
||||
/// <summary>
|
||||
/// API for starting a Holographic Perception Simulation recording.
|
||||
/// </summary>
|
||||
public static readonly string StartHolographicSimulationRecordingApi = "api/holographic/simulation/recording/start";
|
||||
|
||||
/// <summary>
|
||||
/// API for stopping a Holographic Perception Simulation recording.
|
||||
/// </summary>
|
||||
public static readonly string StopHolographicSimulationRecordingApi = "api/holographic/simulation/recording/stop";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the holographic simulation recording status.
|
||||
/// </summary>
|
||||
/// <returns>True if recording, false otherwise.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<bool> GetHolographicSimulationRecordingStatusAsync()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
HolographicSimulationRecordingStatus status = await this.GetAsync<HolographicSimulationRecordingStatus>(HolographicSimulationRecordingStatusApi);
|
||||
return status.IsRecording;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a Holographic Simulation recording session.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the recording.</param>
|
||||
/// <param name="recordHead">Should head data be recorded? The default value is true.</param>
|
||||
/// <param name="recordHands">Should hand data be recorded? The default value is true.</param>
|
||||
/// <param name="recordSpatialMapping">Should Spatial Mapping data be recorded? The default value is true.</param>
|
||||
/// <param name="recordEnvironment">Should environment data be recorded? The default value is true.</param>
|
||||
/// <returns>Task tracking completion of the REST call.</returns>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task StartHolographicSimulationRecordingAsync(
|
||||
string name,
|
||||
bool recordHead = true,
|
||||
bool recordHands = true,
|
||||
bool recordSpatialMapping = true,
|
||||
bool recordEnvironment = true)
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
string payload = string.Format(
|
||||
"head={0}&hands={1}&spatialMapping={2}&environment={3}&name={4}",
|
||||
recordHead ? 1 : 0,
|
||||
recordHands ? 1 : 0,
|
||||
recordSpatialMapping ? 1 : 0,
|
||||
recordEnvironment ? 1 : 0,
|
||||
name);
|
||||
await this.PostAsync(StartHolographicSimulationRecordingApi, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops a Holographic Simulation recording session.
|
||||
/// </summary>
|
||||
/// <returns>Byte array containing the recorded data.</returns>
|
||||
/// <exception cref="InvalidOperationException">No recording was in progress.</exception>
|
||||
/// <remarks>This method is only supported on HoloLens.</remarks>
|
||||
public async Task<byte[]> StopHolographicSimulationRecordingAsync()
|
||||
{
|
||||
if (!Utilities.IsHoloLens(this.Platform, this.DeviceFamily))
|
||||
{
|
||||
throw new NotSupportedException("This method is only supported on HoloLens.");
|
||||
}
|
||||
|
||||
Uri uri = Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
StopHolographicSimulationRecordingApi);
|
||||
|
||||
byte[] dataBytes = null;
|
||||
|
||||
using (Stream dataStream = await this.GetAsync(uri))
|
||||
{
|
||||
if ((dataStream != null) &&
|
||||
(dataStream.Length != 0))
|
||||
{
|
||||
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(HolographicSimulationError));
|
||||
HolographicSimulationError error = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Try to get / interpret an error response.
|
||||
error = (HolographicSimulationError)serializer.ReadObject(dataStream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (error != null)
|
||||
{
|
||||
// We received an error response.
|
||||
throw new InvalidOperationException(error.Reason);
|
||||
}
|
||||
|
||||
// Getting here indicates that we have file data to return.
|
||||
dataBytes = new byte[dataStream.Length];
|
||||
dataStream.Read(dataBytes, 0, dataBytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
return dataBytes;
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
/// <summary>
|
||||
/// Object representation of a Holographic Simulation (playback or recording) error.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class HolographicSimulationError
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Reason string.
|
||||
/// </summary>
|
||||
[DataMember(Name = "Reason")]
|
||||
public string Reason { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object representation of Holographic Simulation recording status.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class HolographicSimulationRecordingStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the simulation is recording.
|
||||
/// </summary>
|
||||
[DataMember(Name = "recording")]
|
||||
public bool IsRecording { get; private set; }
|
||||
}
|
||||
#endregion // Data contract
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="HttpMultipartFileContent.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// This class mimicks <see cref="HttpMultipartContent"/>, with two main differences
|
||||
/// 1. Simplifies posting files by taking file names instead of managing streams.
|
||||
/// 2. Does not quote the boundaries, due to a bug in the device portal
|
||||
/// </summary>
|
||||
internal sealed class HttpMultipartFileContent : HttpContent
|
||||
{
|
||||
/// <summary>
|
||||
/// List of items to transfer
|
||||
/// </summary>
|
||||
private List<string> items = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Boundary string
|
||||
/// </summary>
|
||||
private string boundaryString;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpMultipartFileContent" /> class.
|
||||
/// </summary>
|
||||
public HttpMultipartFileContent() : this(Guid.NewGuid().ToString())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpMultipartFileContent" /> class.
|
||||
/// </summary>
|
||||
/// <param name="boundary">The boundary string for file content.</param>
|
||||
public HttpMultipartFileContent(string boundary)
|
||||
{
|
||||
this.boundaryString = boundary;
|
||||
Headers.TryAddWithoutValidation("Content-Type", string.Format("multipart/form-data; boundary={0}", this.boundaryString));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a file to the list of items to transfer
|
||||
/// </summary>
|
||||
/// <param name="filename">The name of the file to add</param>
|
||||
public void Add(string filename)
|
||||
{
|
||||
if (filename != null)
|
||||
{
|
||||
this.items.Add(filename);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a range of files to the list of items to transfer
|
||||
/// </summary>
|
||||
/// <param name="filenames">List of files to add</param>
|
||||
public void AddRange(IEnumerable<string> filenames)
|
||||
{
|
||||
if (filenames != null)
|
||||
{
|
||||
this.items.AddRange(filenames);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the stream.
|
||||
/// </summary>
|
||||
/// <param name="outStream">Serialized Stream</param>
|
||||
/// <param name="context">The Transport Context</param>
|
||||
/// <returns>Task tracking progress</returns>
|
||||
protected override async Task SerializeToStreamAsync(Stream outStream, TransportContext context)
|
||||
{
|
||||
var boundary = Encoding.ASCII.GetBytes($"--{boundaryString}\r\n");
|
||||
var newline = Encoding.ASCII.GetBytes("\r\n");
|
||||
foreach (var item in this.items)
|
||||
{
|
||||
outStream.Write(boundary, 0, boundary.Length);
|
||||
var headerdata = GetFileHeader(new FileInfo(item));
|
||||
outStream.Write(headerdata, 0, headerdata.Length);
|
||||
|
||||
using (var file = File.OpenRead(item))
|
||||
{
|
||||
await file.CopyToAsync(outStream);
|
||||
}
|
||||
|
||||
outStream.Write(newline, 0, newline.Length);
|
||||
await outStream.FlushAsync();
|
||||
}
|
||||
|
||||
// Close the installation request data.
|
||||
boundary = Encoding.ASCII.GetBytes($"--{boundaryString}--\r\n");
|
||||
outStream.Write(boundary, 0, boundary.Length);
|
||||
await outStream.FlushAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes required length for the transfer.
|
||||
/// </summary>
|
||||
/// <param name="length">The computed length value</param>
|
||||
/// <returns>Whether or not the length was successfully computed</returns>
|
||||
protected override bool TryComputeLength(out long length)
|
||||
{
|
||||
length = 0;
|
||||
var boundaryLength = Encoding.ASCII.GetBytes(string.Format("--{0}\r\n", this.boundaryString)).Length;
|
||||
foreach (var item in this.items)
|
||||
{
|
||||
var headerdata = GetFileHeader(new FileInfo(item));
|
||||
length += boundaryLength + headerdata.Length + new FileInfo(item).Length + 2;
|
||||
}
|
||||
|
||||
length += boundaryLength + 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file header for the transfer
|
||||
/// </summary>
|
||||
/// <param name="info">Information about the file</param>
|
||||
/// <returns>A byte array with the file header information</returns>
|
||||
private static byte[] GetFileHeader(FileInfo info)
|
||||
{
|
||||
string contentType = "application/octet-stream";
|
||||
if (info.Extension.ToLower() == ".cer")
|
||||
{
|
||||
contentType = "application/x-x509-ca-cert";
|
||||
}
|
||||
|
||||
return Encoding.ASCII.GetBytes(string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{0}\"\r\nContent-Type: {1}\r\n\r\n", info.Name, contentType));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="HttpHeadersHelper.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
#else
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Web.Http;
|
||||
using Windows.Web.Http.Headers;
|
||||
#endif // !WINDOWS_UWP
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Methods for working with Http headers.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Header name for Content Type of a request body.
|
||||
/// </summary>
|
||||
private static readonly string ContentTypeHeaderName = "Content-Type";
|
||||
|
||||
/// <summary>
|
||||
/// Header name for a CSRF-Token.
|
||||
/// </summary>
|
||||
private static readonly string CsrfTokenName = "CSRF-Token";
|
||||
|
||||
/// <summary>
|
||||
/// Header name for a User-Agent.
|
||||
/// </summary>
|
||||
private static readonly string UserAgentName = "User-Agent";
|
||||
|
||||
/// <summary>
|
||||
/// Header value for User-Agent for the WDPW Open Source project.
|
||||
/// </summary>
|
||||
private static readonly string UserAgentValue = "WindowsDevicePortalWrapper";
|
||||
|
||||
/// <summary>
|
||||
/// CSRF token retrieved by GET calls and used on subsequent POST/DELETE/PUT calls.
|
||||
/// This token is intended to prevent a security vulnerability from cross site forgery.
|
||||
/// </summary>
|
||||
private string csrfToken = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Applies the CSRF token to the HTTP client.
|
||||
/// </summary>
|
||||
/// <param name="client">The HTTP client on which to have the header set.</param>
|
||||
/// <param name="method">The HTTP method (ex: POST) that will be called on the client.</param>
|
||||
private void ApplyCSRFHeader(
|
||||
HttpClient client,
|
||||
HttpMethods method)
|
||||
{
|
||||
string headerName = "X-" + CsrfTokenName;
|
||||
string headerValue = this.csrfToken;
|
||||
|
||||
if (string.Compare(method.ToString(), "get", true) == 0)
|
||||
{
|
||||
headerName = CsrfTokenName;
|
||||
headerValue = string.IsNullOrEmpty(this.csrfToken) ? "Fetch" : headerValue;
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP
|
||||
HttpRequestHeaderCollection headers = client.DefaultRequestHeaders;
|
||||
#else
|
||||
HttpRequestHeaders headers = client.DefaultRequestHeaders;
|
||||
#endif // WINDOWS_UWP
|
||||
|
||||
headers.Add(headerName, headerValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies any needed headers to the HTTP client.
|
||||
/// </summary>
|
||||
/// <param name="client">The HTTP client on which to have the headers set.</param>
|
||||
/// <param name="method">The HTTP method (ex: POST) that will be called on the client.</param>
|
||||
private void ApplyHttpHeaders(
|
||||
HttpClient client,
|
||||
HttpMethods method)
|
||||
{
|
||||
this.ApplyUserAgentHeader(client);
|
||||
this.ApplyCSRFHeader(client, method);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the User-Agent string to a request to identify it
|
||||
/// as coming from the WDPW Open Source project.
|
||||
/// </summary>
|
||||
/// <param name="client">The HTTP client on which to have the header set.</param>
|
||||
private void ApplyUserAgentHeader(HttpClient client)
|
||||
{
|
||||
string userAgentValue = UserAgentValue;
|
||||
|
||||
#if WINDOWS_UWP
|
||||
Assembly asm = this.GetType().GetTypeInfo().Assembly;
|
||||
userAgentValue += "-v" + asm.GetName().Version.ToString();
|
||||
userAgentValue += "-UWP";
|
||||
HttpRequestHeaderCollection headers = client.DefaultRequestHeaders;
|
||||
#else
|
||||
Assembly asm = Assembly.GetExecutingAssembly();
|
||||
userAgentValue += "-v" + asm.GetName().Version.ToString();
|
||||
userAgentValue += "-dotnet";
|
||||
HttpRequestHeaders headers = client.DefaultRequestHeaders;
|
||||
#endif // WINDOWS_UWP
|
||||
|
||||
headers.Add(UserAgentName, userAgentValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the CSRF token from the HTTP response and stores it.
|
||||
/// </summary>
|
||||
/// <param name="response">The HTTP response from which to retrieve the header.</param>
|
||||
private void RetrieveCsrfToken(HttpResponseMessage response)
|
||||
{
|
||||
// If the response sets a CSRF token, store that for future requests.
|
||||
#if WINDOWS_UWP
|
||||
string cookie;
|
||||
if (response.Headers.TryGetValue("Set-Cookie", out cookie))
|
||||
{
|
||||
string csrfTokenNameWithEquals = CsrfTokenName + "=";
|
||||
if (cookie.StartsWith(csrfTokenNameWithEquals))
|
||||
{
|
||||
this.csrfToken = cookie.Substring(csrfTokenNameWithEquals.Length);
|
||||
}
|
||||
}
|
||||
#else
|
||||
IEnumerable<string> cookies;
|
||||
if (response.Headers.TryGetValues("Set-Cookie", out cookies))
|
||||
{
|
||||
foreach (string cookie in cookies)
|
||||
{
|
||||
string csrfTokenNameWithEquals = CsrfTokenName + "=";
|
||||
if (cookie.StartsWith(csrfTokenNameWithEquals))
|
||||
{
|
||||
this.csrfToken = cookie.Substring(csrfTokenNameWithEquals.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="RequestHelpers.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Methods for working with Http requests.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Copies a file to the specified stream and prepends the necessary content information
|
||||
/// required to be part of a multipart form data request.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to be copied.</param>
|
||||
/// <param name="stream">The stream to which the file will be copied.</param>
|
||||
private static void CopyFileToRequestStream(
|
||||
FileInfo file,
|
||||
Stream stream)
|
||||
{
|
||||
byte[] data;
|
||||
string contentDisposition = string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\n", file.Name, file.Name);
|
||||
string contentType = "Content-Type: application/octet-stream\r\n\r\n";
|
||||
|
||||
data = Encoding.ASCII.GetBytes(contentDisposition);
|
||||
stream.Write(data, 0, data.Length);
|
||||
|
||||
data = Encoding.ASCII.GetBytes(contentType);
|
||||
stream.Write(data, 0, data.Length);
|
||||
|
||||
using (FileStream fs = File.OpenRead(file.FullName))
|
||||
{
|
||||
fs.CopyTo(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="ResponseHelpers.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// Methods for working with Http responses.
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// The prefix for the <see cref="SystemPerformanceInformation" /> JSON formatting error.
|
||||
/// </summary>
|
||||
private static readonly string SysPerfInfoErrorPrefix = "{\"Reason\" : \"";
|
||||
|
||||
/// <summary>
|
||||
/// The postfix for the <see cref="SystemPerformanceInformation" /> JSON formatting error.
|
||||
/// </summary>
|
||||
private static readonly string SysPerfInfoErrorPostfix = "\"}";
|
||||
|
||||
/// <summary>
|
||||
/// Reads dataStream as T.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type for the JSON message</typeparam>
|
||||
/// <param name="dataStream">The stream that contains the JSON message to be checked.</param>
|
||||
/// <param name="settings">Optional settings for JSON serialization.</param>
|
||||
/// <returns>Read data</returns>
|
||||
public static T ReadJsonStream<T>(Stream dataStream, DataContractJsonSerializerSettings settings = null)
|
||||
{
|
||||
T data = default(T);
|
||||
object response = null;
|
||||
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T), settings);
|
||||
|
||||
using (dataStream)
|
||||
{
|
||||
if ((dataStream != null) &&
|
||||
(dataStream.Length != 0))
|
||||
{
|
||||
JsonFormatCheck<T>(dataStream);
|
||||
|
||||
try
|
||||
{
|
||||
response = serializer.ReadObject(dataStream);
|
||||
}
|
||||
catch (SerializationException)
|
||||
{
|
||||
// Assert on serialization failure.
|
||||
Debug.Assert(false, "Serialization failure encountered. Check DataContract types for a possible mismatch between expectations and reality");
|
||||
throw;
|
||||
}
|
||||
|
||||
data = (T)response;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the JSON for any known formatting errors and fixes them.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type for the JSON message</typeparam>
|
||||
/// <param name="jsonStream">The stream that contains the JSON message to be checked.</param>
|
||||
private static void JsonFormatCheck<T>(Stream jsonStream)
|
||||
{
|
||||
if (typeof(T) == typeof(SystemPerformanceInformation))
|
||||
{
|
||||
StreamReader read = new StreamReader(jsonStream);
|
||||
string rawJsonString = read.ReadToEnd();
|
||||
|
||||
// Recover from an error in which SystemPerformanceInformation is returned with an incorrect prefix, postfix and the message converted into JSON a second time.
|
||||
if (rawJsonString.StartsWith(SysPerfInfoErrorPrefix, StringComparison.OrdinalIgnoreCase) && rawJsonString.EndsWith(SysPerfInfoErrorPostfix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Remove the incorrect prefix and postfix from the JSON message.
|
||||
rawJsonString = rawJsonString.Substring(SysPerfInfoErrorPrefix.Length, rawJsonString.Length - SysPerfInfoErrorPrefix.Length - SysPerfInfoErrorPostfix.Length);
|
||||
|
||||
// Undo the second JSON conversion.
|
||||
rawJsonString = Regex.Replace(rawJsonString, "\\\\\"", "\"", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
rawJsonString = Regex.Replace(rawJsonString, "\\\\\\\\", "\\", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
// Overwrite the stream with the fixed JSON.
|
||||
jsonStream.SetLength(0);
|
||||
var sw = new StreamWriter(jsonStream);
|
||||
sw.Write(rawJsonString);
|
||||
sw.Flush();
|
||||
}
|
||||
|
||||
jsonStream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
#region Data contract
|
||||
|
||||
/// <summary>
|
||||
/// A null response class when we don't care about the response
|
||||
/// body.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
private class NullResponse
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="RestDelete.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// HTTP DELETE Wrapper
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Calls the specified API with the provided payload. This signature leaves
|
||||
/// off the optional response so callers who don't need a response body
|
||||
/// don't need to specify a type for it.
|
||||
/// </summary>
|
||||
/// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param>
|
||||
/// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param>
|
||||
/// <returns>Task tracking the HTTP completion.</returns>
|
||||
public async Task DeleteAsync(
|
||||
string apiPath,
|
||||
string payload = null)
|
||||
{
|
||||
await this.DeleteAsync<NullResponse>(apiPath, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the specified API with the provided payload.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the data for the HTTP response body (if present).</typeparam>
|
||||
/// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param>
|
||||
/// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param>
|
||||
/// <returns>Task tracking the HTTP completion.</returns>
|
||||
public async Task<T> DeleteAsync<T>(
|
||||
string apiPath,
|
||||
string payload = null) where T : new()
|
||||
{
|
||||
Uri uri = Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
apiPath,
|
||||
payload);
|
||||
|
||||
return ReadJsonStream<T>(await this.DeleteAsync(uri));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="RestGet.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// HTTP GET Wrapper
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Calls the specified API with the provided payload.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type for the GET call</typeparam>
|
||||
/// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param>
|
||||
/// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param>
|
||||
/// <returns>An object of the specified type containing the data returned by the request.</returns>
|
||||
public async Task<T> GetAsync<T>(
|
||||
string apiPath,
|
||||
string payload = null) where T : new()
|
||||
{
|
||||
Uri uri = Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
apiPath,
|
||||
payload);
|
||||
|
||||
return ReadJsonStream<T>(await this.GetAsync(uri).ConfigureAwait(false));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="RestPost.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// HTTP POST Wrapper
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Calls the specified API with the provided body. This signature leaves
|
||||
/// off the optional response so callers who don't need a response body
|
||||
/// don't need to specify a type for it.
|
||||
/// </summary>
|
||||
/// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param>
|
||||
/// <param name="files">List of files that we want to include in the post request.</param>
|
||||
/// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param>
|
||||
/// <returns>Task tracking the POST completion.</returns>
|
||||
public async Task PostAsync(
|
||||
string apiPath,
|
||||
List<string> files,
|
||||
string payload = null)
|
||||
{
|
||||
Uri uri = Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
apiPath,
|
||||
payload);
|
||||
|
||||
var content = new HttpMultipartFileContent();
|
||||
content.AddRange(files);
|
||||
await this.PostAsync(uri, content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the specified API with the provided body. This signature leaves
|
||||
/// off the optional response so callers who don't need a response body
|
||||
/// don't need to specify a type for it.
|
||||
/// </summary>
|
||||
/// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param>
|
||||
/// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param>
|
||||
/// <returns>Task tracking the POST completion.</returns>
|
||||
public async Task PostAsync(
|
||||
string apiPath,
|
||||
string payload = null)
|
||||
{
|
||||
await this.PostAsync<NullResponse>(apiPath, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the specified API with the provided payload.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the data for the HTTP response body (if present).</typeparam>
|
||||
/// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param>
|
||||
/// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param>
|
||||
/// <param name="requestStream">Optional stream containing data for the request body.</param>
|
||||
/// <param name="requestStreamContentType">The type of that request body data.</param>
|
||||
/// <returns>Task tracking the POST completion.</returns>
|
||||
public async Task<T> PostAsync<T>(
|
||||
string apiPath,
|
||||
string payload = null,
|
||||
Stream requestStream = null,
|
||||
string requestStreamContentType = null) where T : new()
|
||||
{
|
||||
Uri uri = Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
apiPath,
|
||||
payload);
|
||||
|
||||
return ReadJsonStream<T>(await this.PostAsync(uri, requestStream, requestStreamContentType));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="RestPut.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
#if !WINDOWS_UWP
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
#endif // !WINDOWS_UWP
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Threading.Tasks;
|
||||
#if WINDOWS_UWP
|
||||
using Windows.Foundation;
|
||||
using Windows.Networking;
|
||||
using Windows.Security.Credentials;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.Web.Http;
|
||||
using Windows.Web.Http.Filters;
|
||||
using Windows.Web.Http.Headers;
|
||||
#endif // WINDOWS_UWP
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// HTTP PUT Wrapper
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Calls the specified API with the provided body. This signature leaves
|
||||
/// off the optional response so callers who don't need a response body
|
||||
/// don't need to specify a type for it, which also would force them
|
||||
/// to explicitly declare their bodyData type instead of letting it
|
||||
/// be implied implicitly.
|
||||
/// </summary>
|
||||
/// <typeparam name="K">The type of the data for the HTTP request body.</typeparam>
|
||||
/// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param>
|
||||
/// <param name="bodyData">The data to be used for the HTTP request body.</param>
|
||||
/// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param>
|
||||
/// <returns>Task tracking the PUT completion.</returns>
|
||||
public async Task PutAsync<K>(
|
||||
string apiPath,
|
||||
K bodyData,
|
||||
string payload = null) where K : class
|
||||
{
|
||||
await this.PutAsync<NullResponse, K>(apiPath, bodyData, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the specified API with the provided body.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the data for the HTTP response body (if present).</typeparam>
|
||||
/// <typeparam name="K">The type of the data for the HTTP request body.</typeparam>
|
||||
/// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param>
|
||||
/// <param name="bodyData">The data to be used for the HTTP request body.</param>
|
||||
/// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param>
|
||||
/// <returns>Task tracking the PUT completion, optional response body.</returns>
|
||||
public async Task<T> PutAsync<T, K>(
|
||||
string apiPath,
|
||||
K bodyData = null,
|
||||
string payload = null) where T : new()
|
||||
where K : class
|
||||
{
|
||||
Uri uri = Utilities.BuildEndpoint(
|
||||
this.deviceConnection.Connection,
|
||||
apiPath,
|
||||
payload);
|
||||
|
||||
#if WINDOWS_UWP
|
||||
HttpStreamContent streamContent = null;
|
||||
#else
|
||||
StreamContent streamContent = null;
|
||||
#endif // WINDOWS_UWP
|
||||
|
||||
if (bodyData != null)
|
||||
{
|
||||
// Serialize the body to a JSON stream
|
||||
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(K));
|
||||
Stream stream = new MemoryStream();
|
||||
serializer.WriteObject(stream, bodyData);
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
#if WINDOWS_UWP
|
||||
streamContent = new HttpStreamContent(stream.AsInputStream());
|
||||
streamContent.Headers.ContentType = new HttpMediaTypeHeaderValue("application/json");
|
||||
#else
|
||||
streamContent = new StreamContent(stream);
|
||||
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||
#endif // WINDOWS_UWP
|
||||
}
|
||||
|
||||
return ReadJsonStream<T>(await this.PutAsync(uri, streamContent));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="WebSocket.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal Web socket message received event handler
|
||||
/// </summary>
|
||||
/// <param name="sender">sender <see cref="WebSocket{T}"/> object</param>
|
||||
/// <param name="args">Web socket message received args</param>
|
||||
/// <typeparam name="T">Return type for the websocket messages.</typeparam>
|
||||
internal delegate void WebSocketMessageReceivedEventInternalHandler<T>(WebSocket<T> sender, WebSocketMessageReceivedEventArgs<T> args);
|
||||
|
||||
/// <summary>
|
||||
/// Internal Web socket stream received event handler
|
||||
/// </summary>
|
||||
/// <param name="sender">sender <see cref="WebSocket{T}"/> object</param>
|
||||
/// <param name="args">Web socket message received args</param>
|
||||
/// <typeparam name="T">Return type for the websocket.</typeparam>
|
||||
internal delegate void WebSocketStreamReceivedEventInternalHandler<T>(WebSocket<T> sender, WebSocketMessageReceivedEventArgs<Stream> args);
|
||||
|
||||
/// <summary>
|
||||
/// HTTP Websocket Wrapper
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type for the websocket messages.</typeparam>
|
||||
internal partial class WebSocket<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The device that the <see cref="WebSocket{T}" /> is connected to.
|
||||
/// </summary>
|
||||
private IDevicePortalConnection deviceConnection;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the web socket should send streams instead of parsed <see cref="T" /> objects
|
||||
/// </summary>
|
||||
private bool sendStreams = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message received handler.
|
||||
/// </summary>
|
||||
public event WebSocketMessageReceivedEventInternalHandler<T> WebSocketMessageReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the stream received handler.
|
||||
/// </summary>
|
||||
public event WebSocketStreamReceivedEventInternalHandler<T> WebSocketStreamReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the web socket is connected.
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the web socket is listening for messages.
|
||||
/// </summary>
|
||||
public bool IsListeningForMessages
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a connection to the websocket.
|
||||
/// </summary>
|
||||
/// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param>
|
||||
/// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param>
|
||||
/// <returns>The task of opening the websocket connection.</returns>
|
||||
internal async Task ConnectAsync(string apiPath, string payload = null)
|
||||
{
|
||||
if (this.IsConnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = Utilities.BuildEndpoint(
|
||||
this.deviceConnection.WebSocketConnection,
|
||||
apiPath,
|
||||
payload);
|
||||
await this.ConnectInternalAsync(uri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection to the websocket and stop listening for messages.
|
||||
/// </summary>
|
||||
/// <returns>The task of closing the websocket connection.</returns>
|
||||
internal async Task CloseAsync()
|
||||
{
|
||||
if (this.IsConnected)
|
||||
{
|
||||
if (this.IsListeningForMessages)
|
||||
{
|
||||
await this.StopListeningForMessagesInternalAsync();
|
||||
}
|
||||
|
||||
await this.CloseInternalAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening for messages from the websocket. Once they are received they are parsed and the WebSocketMessageReceived event is raised.
|
||||
/// </summary>
|
||||
/// <returns>The task of listening for messages from the websocket.</returns>
|
||||
internal async Task ReceiveMessagesAsync()
|
||||
{
|
||||
if (this.IsConnected && !this.IsListeningForMessages)
|
||||
{
|
||||
await this.StartListeningForMessagesInternalAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to the server.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to send.</param>
|
||||
/// <returns>The task of sending a message to the websocket.</returns>
|
||||
internal async Task SendMessageAsync(string message)
|
||||
{
|
||||
if (this.IsConnected)
|
||||
{
|
||||
await this.SendMessageInternalAsync(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts received stream to a parsed <see cref="T" /> object and passes it to
|
||||
/// the WebSocketMessageReceived handler. The sendstreams property can be used to
|
||||
/// override this and send the <see cref="Stream" /> instead via the WebSocketStreamReceived handler.
|
||||
/// </summary>
|
||||
/// <param name="stream">The received stream.</param>
|
||||
private void ConvertStreamToMessage(Stream stream)
|
||||
{
|
||||
if (stream != null && stream.Length != 0)
|
||||
{
|
||||
if (this.sendStreams)
|
||||
{
|
||||
this.WebSocketStreamReceived?.Invoke(
|
||||
this,
|
||||
new WebSocketMessageReceivedEventArgs<Stream>(stream));
|
||||
}
|
||||
else
|
||||
{
|
||||
DataContractJsonSerializerSettings settings = new DataContractJsonSerializerSettings()
|
||||
{
|
||||
UseSimpleDictionaryFormat = true
|
||||
};
|
||||
|
||||
T message = DevicePortal.ReadJsonStream<T>(stream, settings);
|
||||
|
||||
this.WebSocketMessageReceived?.Invoke(
|
||||
this,
|
||||
new WebSocketMessageReceivedEventArgs<T>(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="IDevicePortalConnection.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
#if !WINDOWS_UWP
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
#endif
|
||||
using static Microsoft.Tools.WindowsDevicePortal.DevicePortal;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for creating a connection with the device portal.
|
||||
/// </summary>
|
||||
public interface IDevicePortalConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the base uri (ex: http://localhost) used to communicate with the device.
|
||||
/// </summary>
|
||||
Uri Connection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base uri (ex: ws://localhost) used to communicate with web sockets on the device.
|
||||
/// </summary>
|
||||
Uri WebSocketConnection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the credentials used when communicating with the device.
|
||||
/// </summary>
|
||||
NetworkCredential Credentials { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the family of the device (ex: Windows.Holographic).
|
||||
/// </summary>
|
||||
string Family { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets information describing the operating system installed on the device.
|
||||
/// </summary>
|
||||
OperatingSystemInformation OsInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the http security requirements for device communication.
|
||||
/// </summary>
|
||||
/// <param name="requiresHttps">True if an https connection is required, false otherwise.</param>
|
||||
void UpdateConnection(bool requiresHttps);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the connection details (IP address) and http security requirements used when communicating with the device.
|
||||
/// </summary>
|
||||
/// <param name="ipConfig">Object that describes the current network configuration.</param>
|
||||
/// <param name="requiresHttps">True if an https connection is required, false otherwise.</param>
|
||||
/// <param name="preservePort">True if the previous connection's port is to continue to be used, false otherwise.</param>
|
||||
void UpdateConnection(
|
||||
IpConfiguration ipConfig,
|
||||
bool requiresHttps,
|
||||
bool preservePort);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,65 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="RestDelete.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// .net 4.x implementation of HTTP DeleteAsync
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Submits the http delete request to the specified uri.
|
||||
/// </summary>
|
||||
/// <param name="uri">The uri to which the delete request will be issued.</param>
|
||||
/// <returns>Task tracking HTTP completion</returns>
|
||||
public async Task<Stream> DeleteAsync(Uri uri)
|
||||
{
|
||||
MemoryStream dataStream = null;
|
||||
|
||||
WebRequestHandler requestSettings = new WebRequestHandler();
|
||||
requestSettings.UseDefaultCredentials = false;
|
||||
requestSettings.Credentials = this.deviceConnection.Credentials;
|
||||
requestSettings.ServerCertificateValidationCallback = this.ServerCertificateValidation;
|
||||
|
||||
using (HttpClient client = new HttpClient(requestSettings))
|
||||
{
|
||||
this.ApplyHttpHeaders(client, HttpMethods.Delete);
|
||||
|
||||
using (HttpResponseMessage response = await client.DeleteAsync(uri).ConfigureAwait(false))
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw await DevicePortalException.CreateAsync(response);
|
||||
}
|
||||
|
||||
this.RetrieveCsrfToken(response);
|
||||
|
||||
if (response.Content != null)
|
||||
{
|
||||
using (HttpContent content = response.Content)
|
||||
{
|
||||
dataStream = new MemoryStream();
|
||||
|
||||
await content.CopyToAsync(dataStream).ConfigureAwait(false);
|
||||
|
||||
// Ensure we return with the stream pointed at the origin.
|
||||
dataStream.Position = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataStream;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="RestGet.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// .net 4.x implementation of HTTP GetAsync
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Submits the http get request to the specified uri.
|
||||
/// </summary>
|
||||
/// <param name="uri">The uri to which the get request will be issued.</param>
|
||||
/// <returns>Response data as a stream.</returns>
|
||||
public async Task<Stream> GetAsync(
|
||||
Uri uri)
|
||||
{
|
||||
MemoryStream dataStream = null;
|
||||
|
||||
WebRequestHandler handler = new WebRequestHandler();
|
||||
handler.UseDefaultCredentials = false;
|
||||
handler.Credentials = this.deviceConnection.Credentials;
|
||||
handler.ServerCertificateValidationCallback = this.ServerCertificateValidation;
|
||||
|
||||
using (HttpClient client = new HttpClient(handler))
|
||||
{
|
||||
this.ApplyHttpHeaders(client, HttpMethods.Get);
|
||||
|
||||
using (HttpResponseMessage response = await client.GetAsync(uri).ConfigureAwait(false))
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw await DevicePortalException.CreateAsync(response);
|
||||
}
|
||||
|
||||
this.RetrieveCsrfToken(response);
|
||||
|
||||
using (HttpContent content = response.Content)
|
||||
{
|
||||
dataStream = new MemoryStream();
|
||||
|
||||
await content.CopyToAsync(dataStream).ConfigureAwait(false);
|
||||
|
||||
// Ensure we return with the stream pointed at the origin.
|
||||
dataStream.Position = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataStream;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="RestPost.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// .net 4.x implementation of HTTP PostAsync
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Submits the http post request to the specified uri.
|
||||
/// </summary>
|
||||
/// <param name="uri">The uri to which the post request will be issued.</param>
|
||||
/// <param name="requestStream">Optional stream containing data for the request body.</param>
|
||||
/// <param name="requestStreamContentType">The type of that request body data.</param>
|
||||
/// <returns>Task tracking the completion of the POST request</returns>
|
||||
public async Task<Stream> PostAsync(
|
||||
Uri uri,
|
||||
Stream requestStream = null,
|
||||
string requestStreamContentType = null)
|
||||
{
|
||||
StreamContent requestContent = null;
|
||||
|
||||
if (requestStream != null)
|
||||
{
|
||||
requestContent = new StreamContent(requestStream);
|
||||
requestContent.Headers.Remove(ContentTypeHeaderName);
|
||||
requestContent.Headers.TryAddWithoutValidation(ContentTypeHeaderName, requestStreamContentType);
|
||||
}
|
||||
|
||||
return await this.PostAsync(uri, requestContent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submits the http post request to the specified uri.
|
||||
/// </summary>
|
||||
/// <param name="uri">The uri to which the post request will be issued.</param>
|
||||
/// <param name="requestContent">Optional content containing data for the request body.</param>
|
||||
/// <returns>Task tracking the completion of the POST request</returns>
|
||||
public async Task<Stream> PostAsync(
|
||||
Uri uri,
|
||||
HttpContent requestContent)
|
||||
{
|
||||
MemoryStream responseDataStream = null;
|
||||
|
||||
WebRequestHandler requestSettings = new WebRequestHandler();
|
||||
requestSettings.UseDefaultCredentials = false;
|
||||
requestSettings.Credentials = this.deviceConnection.Credentials;
|
||||
requestSettings.ServerCertificateValidationCallback = this.ServerCertificateValidation;
|
||||
|
||||
using (HttpClient client = new HttpClient(requestSettings))
|
||||
{
|
||||
this.ApplyHttpHeaders(client, HttpMethods.Post);
|
||||
|
||||
using (HttpResponseMessage response = await client.PostAsync(uri, requestContent).ConfigureAwait(false))
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw await DevicePortalException.CreateAsync(response);
|
||||
}
|
||||
|
||||
this.RetrieveCsrfToken(response);
|
||||
|
||||
if (response.Content != null)
|
||||
{
|
||||
using (HttpContent responseContent = response.Content)
|
||||
{
|
||||
responseDataStream = new MemoryStream();
|
||||
|
||||
await responseContent.CopyToAsync(responseDataStream).ConfigureAwait(false);
|
||||
|
||||
// Ensure we return with the stream pointed at the origin.
|
||||
responseDataStream.Position = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return responseDataStream;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="RestPut.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <content>
|
||||
/// .net 4.x implementation of HTTP PutAsync
|
||||
/// </content>
|
||||
public partial class DevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Submits the http put request to the specified uri.
|
||||
/// </summary>
|
||||
/// <param name="uri">The uri to which the put request will be issued.</param>
|
||||
/// <param name="body">The HTTP content comprising the body of the request.</param>
|
||||
/// <returns>Task tracking the PUT completion.</returns>
|
||||
public async Task<Stream> PutAsync(
|
||||
Uri uri,
|
||||
HttpContent body = null)
|
||||
{
|
||||
MemoryStream dataStream = null;
|
||||
|
||||
WebRequestHandler requestSettings = new WebRequestHandler();
|
||||
requestSettings.UseDefaultCredentials = false;
|
||||
requestSettings.Credentials = this.deviceConnection.Credentials;
|
||||
requestSettings.ServerCertificateValidationCallback = this.ServerCertificateValidation;
|
||||
|
||||
using (HttpClient client = new HttpClient(requestSettings))
|
||||
{
|
||||
this.ApplyHttpHeaders(client, HttpMethods.Put);
|
||||
|
||||
// Send the request
|
||||
using (HttpResponseMessage response = await client.PutAsync(uri, body).ConfigureAwait(false))
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw await DevicePortalException.CreateAsync(response);
|
||||
}
|
||||
|
||||
this.RetrieveCsrfToken(response);
|
||||
|
||||
if (response.Content != null)
|
||||
{
|
||||
using (HttpContent content = response.Content)
|
||||
{
|
||||
dataStream = new MemoryStream();
|
||||
|
||||
await content.CopyToAsync(dataStream).ConfigureAwait(false);
|
||||
|
||||
// Ensure we return with the stream pointed at the origin.
|
||||
dataStream.Position = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataStream;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="UnvalidatedCert.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Handler for when an unvalidated cert is received.
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender of the event.</param>
|
||||
/// <param name="certificate">The server's certificate.</param>
|
||||
/// <param name="chain">The cert chain.</param>
|
||||
/// <param name="sslPolicyErrors">Policy Errors.</param>
|
||||
/// <returns>whether the cert should still pass validation.</returns>
|
||||
public delegate bool UnvalidatedCertEventHandler(DevicePortal sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors);
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="Utilities.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using static Microsoft.Tools.WindowsDevicePortal.DevicePortal;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for common functions
|
||||
/// </summary>
|
||||
internal static class Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a fully formed REST API endpoint uri.
|
||||
/// </summary>
|
||||
/// <param name="baseUri">The base uri (typically, just scheme and authority).</param>
|
||||
/// <param name="path">The path to the REST API method (ex: api/control/restart).</param>
|
||||
/// <param name="payload">Parameterized data required by the REST API.</param>
|
||||
/// <returns>Uri object containing the complete path and query string required to issue the REST API call.</returns>
|
||||
public static Uri BuildEndpoint(
|
||||
Uri baseUri,
|
||||
string path,
|
||||
string payload = null)
|
||||
{
|
||||
string relativePart = !string.IsNullOrWhiteSpace(payload) ?
|
||||
string.Format("{0}?{1}", path, payload) : path;
|
||||
return new Uri(baseUri, relativePart);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a query string from key value pairs.
|
||||
/// </summary>
|
||||
/// <param name="payload">The key value pairs containing the query parameters.</param>
|
||||
/// <returns>Properly formatted query string.</returns>
|
||||
public static string BuildQueryString(Dictionary<string, string> payload)
|
||||
{
|
||||
string query = string.Empty;
|
||||
|
||||
foreach (KeyValuePair<string, string> pair in payload)
|
||||
{
|
||||
query += pair.Key + "=" + pair.Value + "&";
|
||||
}
|
||||
|
||||
// Trim off the final ampersand.
|
||||
query = query.Trim('&');
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the specified string as a base64 value.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to encode.</param>
|
||||
/// <returns>Base64 encoded version of the string data.</returns>
|
||||
public static string Hex64Encode(string str)
|
||||
{
|
||||
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(str));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this device is a hololens.
|
||||
/// </summary>
|
||||
/// <param name="platform">The platform.</param>
|
||||
/// <param name="deviceFamily">The device family.</param>
|
||||
/// <returns>Whether this is a hololens.</returns>
|
||||
public static bool IsHoloLens(
|
||||
DevicePortalPlatforms platform,
|
||||
string deviceFamily)
|
||||
{
|
||||
bool isHoloLens = false;
|
||||
|
||||
if ((platform == DevicePortalPlatforms.HoloLens) ||
|
||||
((platform == DevicePortalPlatforms.VirtualMachine) && (deviceFamily == "Windows.Holographic")))
|
||||
{
|
||||
isHoloLens = true;
|
||||
}
|
||||
|
||||
return isHoloLens;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies an endpoint to match the way we store it in a file.
|
||||
/// This involves replacing a number of characters with underscores.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The endpoint that is being modified.</param>
|
||||
public static void ModifyEndpointForFilename(ref string endpoint)
|
||||
{
|
||||
char[] invalidChars = Path.GetInvalidFileNameChars();
|
||||
|
||||
foreach (char character in invalidChars)
|
||||
{
|
||||
endpoint = endpoint.Replace(character, '_');
|
||||
}
|
||||
|
||||
endpoint = endpoint.Replace('-', '_');
|
||||
endpoint = endpoint.Replace('.', '_');
|
||||
endpoint = endpoint.Replace('=', '_');
|
||||
endpoint = endpoint.Replace('&', '_');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
//----------------------------------------------------------------------------------------------
|
||||
// <copyright file="WebSocket.cs" company="Microsoft Corporation">
|
||||
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
|
||||
// </copyright>
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Net.WebSockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Tools.WindowsDevicePortal
|
||||
{
|
||||
/// <summary>
|
||||
/// HTTP Websocket Wrapper
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type for the websocket messages.</typeparam>
|
||||
internal partial class WebSocket<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The hresult for the connection being reset by the peer.
|
||||
/// </summary>
|
||||
private const int WSAECONNRESET = 0x2746;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of bytes that can be received in a single chunk.
|
||||
/// </summary>
|
||||
private static readonly uint MaxChunkSizeInBytes = 1024;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ClientWebSocket" /> that is being wrapped.
|
||||
/// </summary>
|
||||
private ClientWebSocket websocket;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Task" /> for receiving messages.
|
||||
/// </summary>
|
||||
private Task receivingMessagesTask;
|
||||
|
||||
/// <summary>
|
||||
/// The handler used to validate server certificates.
|
||||
/// </summary>
|
||||
private Func<object, X509Certificate, X509Chain, SslPolicyErrors, bool> serverCertificateValidationHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WebSocket{T}" /> class.
|
||||
/// </summary>
|
||||
/// <param name="connection">Implementation of a connection object.</param>
|
||||
/// <param name="serverCertificateValidationHandler">Server certificate handler.</param>
|
||||
/// <param name="sendStreams">specifies whether the web socket should send streams (useful for creating mock data).</param>
|
||||
public WebSocket(IDevicePortalConnection connection, Func<object, X509Certificate, X509Chain, SslPolicyErrors, bool> serverCertificateValidationHandler, bool sendStreams = false)
|
||||
{
|
||||
this.sendStreams = sendStreams;
|
||||
this.deviceConnection = connection;
|
||||
this.IsListeningForMessages = false;
|
||||
this.serverCertificateValidationHandler = serverCertificateValidationHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a connection to the specified websocket API.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The uri that the weboscket should connect to</param>
|
||||
/// <returns>The task of opening a connection to the websocket.</returns>
|
||||
private async Task ConnectInternalAsync(
|
||||
Uri endpoint)
|
||||
{
|
||||
this.websocket = new ClientWebSocket();
|
||||
this.websocket.Options.UseDefaultCredentials = false;
|
||||
this.websocket.Options.Credentials = this.deviceConnection.Credentials;
|
||||
this.websocket.Options.SetRequestHeader("Origin", this.deviceConnection.Connection.AbsoluteUri);
|
||||
|
||||
// There is no way to set a ServerCertificateValidationCallback for a single web socket, hence the workaround.
|
||||
ServicePointManager.ServerCertificateValidationCallback = delegate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors policyErrors)
|
||||
{
|
||||
return this.serverCertificateValidationHandler(sender, cert, chain, policyErrors);
|
||||
};
|
||||
|
||||
await this.websocket.ConnectAsync(endpoint, CancellationToken.None);
|
||||
this.IsConnected = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection to the websocket.
|
||||
/// </summary>
|
||||
/// <returns>The task of closing the websocket connection.</returns>
|
||||
private async Task CloseInternalAsync()
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
this.websocket.Dispose();
|
||||
this.websocket = null;
|
||||
this.IsConnected = false;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops listening for messages.
|
||||
/// </summary>
|
||||
/// <returns>The task of closing the websocket connection.</returns>
|
||||
private async Task StopListeningForMessagesInternalAsync()
|
||||
{
|
||||
await this.websocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||
|
||||
// Wait for web socket to no longer be receiving messages.
|
||||
if (this.IsListeningForMessages)
|
||||
{
|
||||
await this.receivingMessagesTask;
|
||||
this.receivingMessagesTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening for messages from the websocket. Once they are received they are parsed and the WebSocketMessageReceived event is raised.
|
||||
/// </summary>
|
||||
/// <returns>The task of listening for messages from the websocket.</returns>
|
||||
private async Task StartListeningForMessagesInternalAsync()
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
this.StartListeningForMessagesInternal();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening for messages from the websocket. Once they are received they are parsed and the WebSocketMessageReceived event is raised.
|
||||
/// </summary>
|
||||
private void StartListeningForMessagesInternal()
|
||||
{
|
||||
this.IsListeningForMessages = true;
|
||||
|
||||
this.receivingMessagesTask = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[MaxChunkSizeInBytes]);
|
||||
|
||||
// Once close message is sent do not try to get any more messages as WDP will abort the web socket connection.
|
||||
while (this.websocket.State == WebSocketState.Open)
|
||||
{
|
||||
// Receive single message in chunks.
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
WebSocketReceiveResult result;
|
||||
|
||||
do
|
||||
{
|
||||
result = await this.websocket.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
if (result.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await this.websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.Count > MaxChunkSizeInBytes)
|
||||
{
|
||||
throw new InvalidOperationException("Buffer not large enough");
|
||||
}
|
||||
|
||||
ms.Write(buffer.Array, buffer.Offset, result.Count);
|
||||
}
|
||||
while (!result.EndOfMessage);
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
if (result.MessageType == WebSocketMessageType.Text)
|
||||
{
|
||||
Stream stream = new MemoryStream();
|
||||
|
||||
await ms.CopyToAsync(stream);
|
||||
|
||||
// Ensure we return with the stream pointed at the origin.
|
||||
stream.Position = 0;
|
||||
|
||||
this.ConvertStreamToMessage(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (WebSocketException e)
|
||||
{
|
||||
// If WDP aborted the web socket connection ignore the exception.
|
||||
SocketException socketException = e.InnerException?.InnerException as SocketException;
|
||||
if (socketException != null)
|
||||
{
|
||||
if (socketException.NativeErrorCode == WSAECONNRESET)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.IsListeningForMessages = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to the server.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to send.</param>
|
||||
/// <returns>The task of sending a message to the websocket.</returns>
|
||||
private async Task SendMessageInternalAsync(string message)
|
||||
{
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(message);
|
||||
ArraySegment<byte> buffer = new ArraySegment<byte>(bytes);
|
||||
|
||||
await this.websocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче