Private/DeviceLinkManagerNative.cs
|
using System;
using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; namespace WinPEDeviceLink.Native { public sealed class DeviceLinkDiscoveryResult { public int ConfigureResultBefore { get; set; } public int ConfigureResultAfter { get; set; } public int DiscoveryResult { get; set; } public string DiscoveryUrl { get; set; } public string TenantId { get; set; } public int AsyncStatus { get; set; } } public sealed class DeviceLinkConfigureResult { public int ConfigureResultBefore { get; set; } public int ConfigureResultAfter { get; set; } public int DiscoveryResult { get; set; } public string DiscoveryUrl { get; set; } public string TenantId { get; set; } public int DiscoveryAsyncStatus { get; set; } public int ConfigureAsyncStatus { get; set; } public int ConfigureAsyncError { get; set; } public int ConfigureOperationResult { get; set; } public double DiscoveryDurationSeconds { get; set; } public double ConfigureDurationSeconds { get; set; } public double NativeDurationSeconds { get; set; } } public static class DeviceLinkManagerClient { private const string RuntimeClassName = "ModernDeployment.Autopilot.Core.DeviceLinkManager"; private const int AsyncStatusStarted = 0; private const int AsyncStatusCompleted = 1; private const int AsyncStatusCanceled = 2; private const int AsyncStatusError = 3; private const int HeartbeatSeconds = 15; [ComImport, Guid("1F79101B-A792-5008-A82A-A4B232229026"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IDeviceLinkManager { [PreserveSig] int GetIids(out int count, out IntPtr values); [PreserveSig] int GetRuntimeClassName(out IntPtr name); [PreserveSig] int GetTrustLevel(out int level); [PreserveSig] int GetDiscoveryUrlRequestInfo(out IntPtr info); [PreserveSig] int RequestDiscoveryUrlAsync(IntPtr deviceLinkInfo, out IntPtr operation); [PreserveSig] int GetConfigureDeviceLinkResult(out int result); [PreserveSig] int ConfigureDeviceLinkAsync(IntPtr url, IntPtr tenant, IntPtr headers, out IntPtr operation); } [ComImport, Guid("B93C372F-472F-4BEA-B90B-9FAA9BDC178F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IDiscoveryUrlRequestInfo { [PreserveSig] int GetIids(out int count, out IntPtr values); [PreserveSig] int GetRuntimeClassName(out IntPtr name); [PreserveSig] int GetTrustLevel(out int level); [PreserveSig] int get_DiscoveryUrl(out IntPtr value); [PreserveSig] int get_TenantId(out IntPtr value); [PreserveSig] int get_DiscoveryUrlRequestResultValue(out int value); } [ComImport, Guid("00000036-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IAsyncInfo { [PreserveSig] int GetIids(out int count, out IntPtr values); [PreserveSig] int GetRuntimeClassName(out IntPtr name); [PreserveSig] int GetTrustLevel(out int level); [PreserveSig] int get_Id(out uint id); [PreserveSig] int get_Status(out int status); [PreserveSig] int get_ErrorCode(out int errorCode); [PreserveSig] int Cancel(); [PreserveSig] int Close(); } [DllImport("combase.dll")] private static extern int RoInitialize(uint initType); [DllImport("combase.dll")] private static extern void RoUninitialize(); [DllImport("combase.dll")] private static extern int RoActivateInstance(IntPtr activatableClassId, out IntPtr instance); [DllImport("combase.dll", CharSet = CharSet.Unicode)] private static extern int WindowsCreateString(string sourceString, int length, out IntPtr hstring); [DllImport("combase.dll")] private static extern int WindowsDeleteString(IntPtr hstring); [DllImport("combase.dll")] private static extern IntPtr WindowsGetStringRawBuffer(IntPtr hstring, out uint length); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetResultsObjectDelegate(IntPtr self, out IntPtr result); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetResultsIntDelegate(IntPtr self, out int result); public static DeviceLinkDiscoveryResult DiscoverRegistered(string deviceLinkBase64, int timeoutSeconds) { if (String.IsNullOrWhiteSpace(deviceLinkBase64)) throw new ArgumentException("DeviceLink identity is required.", "deviceLinkBase64"); if (timeoutSeconds < 5) throw new ArgumentOutOfRangeException("timeoutSeconds"); IntPtr className = IntPtr.Zero; IntPtr instance = IntPtr.Zero; IntPtr blob = IntPtr.Zero; IntPtr operation = IntPtr.Zero; IntPtr resultObject = IntPtr.Zero; IntPtr discoveryUrl = IntPtr.Zero; IntPtr tenantId = IntPtr.Zero; bool uninitialize = false; try { int init = RoInitialize(1); uninitialize = init >= 0; ThrowIfFailed(WindowsCreateString(RuntimeClassName, RuntimeClassName.Length, out className), "WindowsCreateString(class)"); ThrowIfFailed(RoActivateInstance(className, out instance), "RoActivateInstance(DeviceLinkManager)"); IDeviceLinkManager manager = (IDeviceLinkManager)Marshal.GetObjectForIUnknown(instance); int before; ThrowIfFailed(manager.GetConfigureDeviceLinkResult(out before), "GetConfigureDeviceLinkResult(before)"); ThrowIfFailed(WindowsCreateString(deviceLinkBase64, deviceLinkBase64.Length, out blob), "WindowsCreateString(DeviceLink)"); ThrowIfFailed(manager.RequestDiscoveryUrlAsync(blob, out operation), "RequestDiscoveryUrlAsync"); WindowsDeleteString(blob); blob = IntPtr.Zero; int status; WaitForTerminal((IAsyncInfo)Marshal.GetObjectForIUnknown(operation), timeoutSeconds, "RequestDiscoveryUrlAsync", false, out status, null, null); resultObject = GetObjectResult(operation, 8, "RequestDiscoveryUrlAsync.GetResults"); if (resultObject == IntPtr.Zero) throw new InvalidOperationException("RequestDiscoveryUrlAsync returned a null result object."); IDiscoveryUrlRequestInfo info = (IDiscoveryUrlRequestInfo)Marshal.GetObjectForIUnknown(resultObject); int discoveryResult; ThrowIfFailed(info.get_DiscoveryUrl(out discoveryUrl), "DiscoveryUrlRequestInfo.DiscoveryUrl"); ThrowIfFailed(info.get_TenantId(out tenantId), "DiscoveryUrlRequestInfo.TenantId"); ThrowIfFailed(info.get_DiscoveryUrlRequestResultValue(out discoveryResult), "DiscoveryUrlRequestInfo.ResultValue"); int after; ThrowIfFailed(manager.GetConfigureDeviceLinkResult(out after), "GetConfigureDeviceLinkResult(after)"); return new DeviceLinkDiscoveryResult { ConfigureResultBefore = before, ConfigureResultAfter = after, DiscoveryResult = discoveryResult, DiscoveryUrl = HStringToString(discoveryUrl), TenantId = HStringToString(tenantId), AsyncStatus = status }; } finally { if (discoveryUrl != IntPtr.Zero) WindowsDeleteString(discoveryUrl); if (tenantId != IntPtr.Zero) WindowsDeleteString(tenantId); Release(resultObject); Release(operation); if (blob != IntPtr.Zero) WindowsDeleteString(blob); Release(instance); if (className != IntPtr.Zero) WindowsDeleteString(className); if (uninitialize) RoUninitialize(); } } public static DeviceLinkConfigureResult ConfigureRegistered(string deviceLinkBase64, int timeoutSeconds) { return ConfigureRegistered(deviceLinkBase64, timeoutSeconds, null); } public static DeviceLinkConfigureResult ConfigureRegistered(string deviceLinkBase64, int timeoutSeconds, Action<string> progressCallback) { if (String.IsNullOrWhiteSpace(deviceLinkBase64)) throw new ArgumentException("DeviceLink identity is required.", "deviceLinkBase64"); if (timeoutSeconds < 5) throw new ArgumentOutOfRangeException("timeoutSeconds"); Stopwatch totalTimer = Stopwatch.StartNew(); Stopwatch discoveryTimer = new Stopwatch(); Stopwatch configureTimer = new Stopwatch(); IntPtr className = IntPtr.Zero; IntPtr instance = IntPtr.Zero; IntPtr blob = IntPtr.Zero; IntPtr discoveryOperation = IntPtr.Zero; IntPtr resultObject = IntPtr.Zero; IntPtr discoveryUrl = IntPtr.Zero; IntPtr tenantId = IntPtr.Zero; IntPtr configureOperation = IntPtr.Zero; bool uninitialize = false; try { int init = RoInitialize(1); uninitialize = init >= 0; ThrowIfFailed(WindowsCreateString(RuntimeClassName, RuntimeClassName.Length, out className), "WindowsCreateString(class)"); ThrowIfFailed(RoActivateInstance(className, out instance), "RoActivateInstance(DeviceLinkManager)"); IDeviceLinkManager manager = (IDeviceLinkManager)Marshal.GetObjectForIUnknown(instance); int before; ThrowIfFailed(manager.GetConfigureDeviceLinkResult(out before), "GetConfigureDeviceLinkResult(before)"); if (before != 0) { throw new InvalidOperationException("ConfigureDeviceLinkAsync was refused because the current configure result is " + before + ", expected 0."); } SafeProgress(progressCallback, "Starting DeviceLink discovery..."); discoveryTimer.Start(); ThrowIfFailed(WindowsCreateString(deviceLinkBase64, deviceLinkBase64.Length, out blob), "WindowsCreateString(DeviceLink)"); ThrowIfFailed(manager.RequestDiscoveryUrlAsync(blob, out discoveryOperation), "RequestDiscoveryUrlAsync"); WindowsDeleteString(blob); blob = IntPtr.Zero; int discoveryStatus; WaitForTerminal((IAsyncInfo)Marshal.GetObjectForIUnknown(discoveryOperation), timeoutSeconds, "RequestDiscoveryUrlAsync", false, out discoveryStatus, progressCallback, "DeviceLink discovery is still running"); resultObject = GetObjectResult(discoveryOperation, 8, "RequestDiscoveryUrlAsync.GetResults"); if (resultObject == IntPtr.Zero) throw new InvalidOperationException("RequestDiscoveryUrlAsync returned a null result object."); IDiscoveryUrlRequestInfo info = (IDiscoveryUrlRequestInfo)Marshal.GetObjectForIUnknown(resultObject); int discoveryResult; ThrowIfFailed(info.get_DiscoveryUrl(out discoveryUrl), "DiscoveryUrlRequestInfo.DiscoveryUrl"); ThrowIfFailed(info.get_TenantId(out tenantId), "DiscoveryUrlRequestInfo.TenantId"); ThrowIfFailed(info.get_DiscoveryUrlRequestResultValue(out discoveryResult), "DiscoveryUrlRequestInfo.ResultValue"); string url = HStringToString(discoveryUrl); string tenant = HStringToString(tenantId); discoveryTimer.Stop(); SafeProgress(progressCallback, "DeviceLink discovery completed in " + discoveryTimer.Elapsed.TotalSeconds.ToString("F1") + " seconds. Discovery result: " + discoveryResult + "."); if (discoveryResult != 1 && discoveryResult != 2) { throw new InvalidOperationException("ConfigureDeviceLinkAsync was refused because discovery result is " + discoveryResult + ", expected 1 or 2."); } if (String.IsNullOrWhiteSpace(url) || String.IsNullOrWhiteSpace(tenant)) { throw new InvalidOperationException("ConfigureDeviceLinkAsync was refused because discovery URL or tenant ID is empty."); } SafeProgress(progressCallback, "Starting native DeviceLink configuration..."); configureTimer.Start(); ThrowIfFailed(manager.ConfigureDeviceLinkAsync(discoveryUrl, tenantId, IntPtr.Zero, out configureOperation), "ConfigureDeviceLinkAsync"); if (configureOperation == IntPtr.Zero) throw new InvalidOperationException("ConfigureDeviceLinkAsync returned a null async operation."); int configureStatus; int configureError = WaitForTerminal((IAsyncInfo)Marshal.GetObjectForIUnknown(configureOperation), timeoutSeconds, "ConfigureDeviceLinkAsync", true, out configureStatus, progressCallback, "Waiting for Windows to retrieve, attest and apply the DeviceLink association"); int operationResult = -1; if (configureStatus == AsyncStatusCompleted && configureError == 0) { operationResult = GetIntResult(configureOperation, 10); } configureTimer.Stop(); SafeProgress(progressCallback, "Native DeviceLink configuration completed in " + configureTimer.Elapsed.TotalSeconds.ToString("F1") + " seconds. Operation result: " + operationResult + "."); int after; ThrowIfFailed(manager.GetConfigureDeviceLinkResult(out after), "GetConfigureDeviceLinkResult(after)"); totalTimer.Stop(); return new DeviceLinkConfigureResult { ConfigureResultBefore = before, ConfigureResultAfter = after, DiscoveryResult = discoveryResult, DiscoveryUrl = url, TenantId = tenant, DiscoveryAsyncStatus = discoveryStatus, ConfigureAsyncStatus = configureStatus, ConfigureAsyncError = configureError, ConfigureOperationResult = operationResult, DiscoveryDurationSeconds = discoveryTimer.Elapsed.TotalSeconds, ConfigureDurationSeconds = configureTimer.Elapsed.TotalSeconds, NativeDurationSeconds = totalTimer.Elapsed.TotalSeconds }; } finally { if (discoveryTimer.IsRunning) discoveryTimer.Stop(); if (configureTimer.IsRunning) configureTimer.Stop(); if (totalTimer.IsRunning) totalTimer.Stop(); Release(configureOperation); if (discoveryUrl != IntPtr.Zero) WindowsDeleteString(discoveryUrl); if (tenantId != IntPtr.Zero) WindowsDeleteString(tenantId); Release(resultObject); Release(discoveryOperation); if (blob != IntPtr.Zero) WindowsDeleteString(blob); Release(instance); if (className != IntPtr.Zero) WindowsDeleteString(className); if (uninitialize) RoUninitialize(); } } private static int WaitForTerminal(IAsyncInfo asyncInfo, int timeoutSeconds, string operation, bool returnErrorOnly, out int status, Action<string> progressCallback, string heartbeatMessage) { Stopwatch timer = Stopwatch.StartNew(); double nextHeartbeat = HeartbeatSeconds; do { Thread.Sleep(250); ThrowIfFailed(asyncInfo.get_Status(out status), operation + ".Status"); if (timer.Elapsed.TotalSeconds >= timeoutSeconds) throw new TimeoutException(operation + " timed out with status " + status + "."); if (status == AsyncStatusStarted && progressCallback != null && !String.IsNullOrWhiteSpace(heartbeatMessage) && timer.Elapsed.TotalSeconds >= nextHeartbeat) { SafeProgress(progressCallback, heartbeatMessage + "... " + ((int)timer.Elapsed.TotalSeconds) + " seconds elapsed."); nextHeartbeat += HeartbeatSeconds; } } while (status == AsyncStatusStarted); if (status == AsyncStatusError) { int errorCode; ThrowIfFailed(asyncInfo.get_ErrorCode(out errorCode), operation + ".ErrorCode"); if (returnErrorOnly) return errorCode; throw new COMException(operation + " failed with HRESULT 0x" + errorCode.ToString("X8") + ".", errorCode); } if (status == AsyncStatusCanceled) { if (returnErrorOnly) return unchecked((int)0x80004004); throw new OperationCanceledException(operation + " was canceled."); } if (status != AsyncStatusCompleted) { throw new InvalidOperationException(operation + " returned async status " + status + "."); } return 0; } private static void SafeProgress(Action<string> progressCallback, string message) { if (progressCallback == null || String.IsNullOrWhiteSpace(message)) return; try { progressCallback(message); } catch { // Progress reporting is observational only and must never affect DeviceLink state changes. } } private static IntPtr GetObjectResult(IntPtr operation, int slot, string operationName) { IntPtr vtable = Marshal.ReadIntPtr(operation); IntPtr method = Marshal.ReadIntPtr(vtable, slot * IntPtr.Size); GetResultsObjectDelegate getResults = (GetResultsObjectDelegate)Marshal.GetDelegateForFunctionPointer(method, typeof(GetResultsObjectDelegate)); IntPtr result; ThrowIfFailed(getResults(operation, out result), operationName); return result; } private static int GetIntResult(IntPtr operation, int slot) { IntPtr vtable = Marshal.ReadIntPtr(operation); IntPtr method = Marshal.ReadIntPtr(vtable, slot * IntPtr.Size); GetResultsIntDelegate getResults = (GetResultsIntDelegate)Marshal.GetDelegateForFunctionPointer(method, typeof(GetResultsIntDelegate)); int result; int hr = getResults(operation, out result); return hr < 0 ? -1 : result; } private static string HStringToString(IntPtr hstring) { if (hstring == IntPtr.Zero) return null; uint length; IntPtr buffer = WindowsGetStringRawBuffer(hstring, out length); return buffer == IntPtr.Zero ? null : Marshal.PtrToStringUni(buffer, checked((int)length)); } private static void ThrowIfFailed(int hr, string operation) { if (hr < 0) throw new COMException(operation + " failed with HRESULT 0x" + hr.ToString("X8") + ".", hr); } private static void Release(IntPtr value) { if (value != IntPtr.Zero) Marshal.Release(value); } } } |