Private/DeviceLinkNative.cs

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

namespace WinPEDeviceLink.Native
{
    public sealed class ProbeResult { public bool Success { get; set; } public string Message { get; set; } }

    public static class DeviceLinkClient
    {
        private const int DeviceLinkFormatJsonBase64 = 33;
        private const int AsyncStatusCompleted = 1;
        private const int AsyncStatusCanceled = 2;
        private const int AsyncStatusError = 3;
        private static readonly Guid IAsyncInfoId = new Guid("00000036-0000-0000-C000-000000000046");
        private static readonly Guid IDeviceLinkUtilitiesId = new Guid("6410BEE7-60A9-5627-9EB2-FEDA7518A4EB");
        private const string RuntimeClassName = "ModernDeployment.Autopilot.Core.DeviceLinkUtilities";

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr LoadLibraryEx(string fileName, IntPtr file, uint flags);
        [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] private static extern IntPtr GetProcAddress(IntPtr module, string name);
        [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 DllGetActivationFactoryDelegate(IntPtr activatableClassId, out IntPtr activationFactory);
        [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int QueryInterfaceDelegate(IntPtr self, ref Guid iid, out IntPtr result);
        [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int ActivateInstanceDelegate(IntPtr self, out IntPtr instance);
        [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int ExportDeviceLinkInfoCsvAsyncDelegate(IntPtr self, IntPtr folder, int format, out IntPtr operation);
        [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetDeviceLinkInfoAsyncDelegate(IntPtr self, int format, out IntPtr operation);
        [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetAsyncStatusDelegate(IntPtr self, out int status);
        [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetAsyncErrorCodeDelegate(IntPtr self, out int errorCode);
        [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetStringResultsDelegate(IntPtr self, out IntPtr result);

        public static ProbeResult TestRegistered() { return ProbeRegistered(); }
        private static ProbeResult ProbeRegistered()
        {
            IntPtr instance=IntPtr.Zero, utilities=IntPtr.Zero, className=IntPtr.Zero; bool uninit=false;
            try { int r=RoInitialize(0); uninit=r>=0; ThrowIfFailed(WindowsCreateString(RuntimeClassName,RuntimeClassName.Length,out className),"WindowsCreateString"); ThrowIfFailed(RoActivateInstance(className,out instance),"RoActivateInstance"); ThrowIfFailed(QueryInterface(instance,IDeviceLinkUtilitiesId,out utilities),"QueryInterface(IDeviceLinkUtilities)"); return new ProbeResult{Success=true,Message="Registered DeviceLink runtime activation succeeded."}; }
            catch(Exception e){ return new ProbeResult{Success=false,Message=e.Message}; }
            finally { Release(utilities); Release(instance); if(className!=IntPtr.Zero)WindowsDeleteString(className); if(uninit)RoUninitialize(); }
        }

        public static ProbeResult Test(string dllPath)
        {
            IntPtr factory=IntPtr.Zero,instance=IntPtr.Zero,utilities=IntPtr.Zero,className=IntPtr.Zero; bool uninit=false;
            try { int r=RoInitialize(0); uninit=r>=0; ActivateDirect(dllPath,out factory,out instance,out utilities,out className); return new ProbeResult{Success=true,Message="DeviceLink activation succeeded."}; }
            catch(Exception e){ return new ProbeResult{Success=false,Message=e.Message}; }
            finally { Release(utilities);Release(instance);Release(factory);if(className!=IntPtr.Zero)WindowsDeleteString(className);if(uninit)RoUninitialize(); }
        }

        public static string GetDeviceLinkRegistered(int timeoutSeconds)
        {
            IntPtr instance=IntPtr.Zero,utilities=IntPtr.Zero,className=IntPtr.Zero; bool uninit=false;
            try { int r=RoInitialize(0);uninit=r>=0;ThrowIfFailed(WindowsCreateString(RuntimeClassName,RuntimeClassName.Length,out className),"WindowsCreateString");ThrowIfFailed(RoActivateInstance(className,out instance),"RoActivateInstance");ThrowIfFailed(QueryInterface(instance,IDeviceLinkUtilitiesId,out utilities),"QueryInterface(IDeviceLinkUtilities)");return GetDeviceLinkFromUtilities(utilities,timeoutSeconds); }
            finally { Release(utilities);Release(instance);if(className!=IntPtr.Zero)WindowsDeleteString(className);if(uninit)RoUninitialize(); }
        }

        public static string GetDeviceLink(string dllPath,int timeoutSeconds)
        {
            IntPtr factory=IntPtr.Zero,instance=IntPtr.Zero,utilities=IntPtr.Zero,className=IntPtr.Zero;bool uninit=false;
            try { int r=RoInitialize(0);uninit=r>=0;ActivateDirect(dllPath,out factory,out instance,out utilities,out className);return GetDeviceLinkFromUtilities(utilities,timeoutSeconds); }
            finally { Release(utilities);Release(instance);Release(factory);if(className!=IntPtr.Zero)WindowsDeleteString(className);if(uninit)RoUninitialize(); }
        }

        public static string ExportCsvRegistered(string folder,int timeoutSeconds)
        {
            IntPtr instance=IntPtr.Zero,utilities=IntPtr.Zero,className=IntPtr.Zero;bool uninit=false;
            try { int r=RoInitialize(1);uninit=r>=0;ThrowIfFailed(WindowsCreateString(RuntimeClassName,RuntimeClassName.Length,out className),"WindowsCreateString");ThrowIfFailed(RoActivateInstance(className,out instance),"RoActivateInstance");ThrowIfFailed(QueryInterface(instance,IDeviceLinkUtilitiesId,out utilities),"QueryInterface(IDeviceLinkUtilities)");return ExportCsvFromUtilities(utilities,folder,timeoutSeconds); }
            finally { Release(utilities);Release(instance);if(className!=IntPtr.Zero)WindowsDeleteString(className);if(uninit)RoUninitialize(); }
        }

        public static string ExportCsv(string dllPath,string folder,int timeoutSeconds)
        {
            IntPtr factory=IntPtr.Zero,instance=IntPtr.Zero,utilities=IntPtr.Zero,className=IntPtr.Zero;bool uninit=false;
            try { int r=RoInitialize(1);uninit=r>=0;ActivateDirect(dllPath,out factory,out instance,out utilities,out className);return ExportCsvFromUtilities(utilities,folder,timeoutSeconds); }
            finally { Release(utilities);Release(instance);Release(factory);if(className!=IntPtr.Zero)WindowsDeleteString(className);if(uninit)RoUninitialize(); }
        }

        private static string ExportCsvFromUtilities(IntPtr utilities,string folder,int timeoutSeconds)
        {
            if(!Directory.Exists(folder))Directory.CreateDirectory(folder);
            IntPtr folderString=IntPtr.Zero,operation=IntPtr.Zero,asyncInfo=IntPtr.Zero;
            try {
                ThrowIfFailed(WindowsCreateString(folder,folder.Length,out folderString),"WindowsCreateString(folder)");
                var export=GetVtableDelegate<ExportDeviceLinkInfoCsvAsyncDelegate>(utilities,6);
                int hr=export(utilities,folderString,DeviceLinkFormatJsonBase64,out operation);
                ThrowIfFailed(hr,"ExportDeviceLinkInfoCsvAsync(format=33, folder="+folder+")");
                ThrowIfFailed(QueryInterface(operation,IAsyncInfoId,out asyncInfo),"QueryInterface(IAsyncInfo)");
                WaitForCompletion(asyncInfo,timeoutSeconds,"ExportDeviceLinkInfoCsvAsync");
                string newest=null;DateTime newestTime=DateTime.MinValue;
                foreach(string file in Directory.GetFiles(folder,"*.devicelink.csv")) { DateTime t=File.GetLastWriteTimeUtc(file);if(t>=newestTime){newestTime=t;newest=file;} }
                if(newest==null)throw new InvalidOperationException("ExportDeviceLinkInfoCsvAsync completed but no *.devicelink.csv was created in '"+folder+"'.");
                return newest;
            }
            finally { Release(asyncInfo);Release(operation);if(folderString!=IntPtr.Zero)WindowsDeleteString(folderString); }
        }

        private static string GetDeviceLinkFromUtilities(IntPtr utilities,int timeoutSeconds)
        {
            IntPtr operation=IntPtr.Zero,asyncInfo=IntPtr.Zero,result=IntPtr.Zero;
            try { var get=GetVtableDelegate<GetDeviceLinkInfoAsyncDelegate>(utilities,7);ThrowIfFailed(get(utilities,DeviceLinkFormatJsonBase64,out operation),"GetDeviceLinkInfoAsync");ThrowIfFailed(QueryInterface(operation,IAsyncInfoId,out asyncInfo),"QueryInterface(IAsyncInfo)");WaitForCompletion(asyncInfo,timeoutSeconds,"DeviceLink generation");var results=GetVtableDelegate<GetStringResultsDelegate>(operation,8);ThrowIfFailed(results(operation,out result),"GetResults");uint len;IntPtr buffer=WindowsGetStringRawBuffer(result,out len);if(buffer==IntPtr.Zero||len==0)throw new InvalidOperationException("DeviceLink generation returned no data.");return Marshal.PtrToStringUni(buffer,checked((int)len)); }
            finally { if(result!=IntPtr.Zero)WindowsDeleteString(result);Release(asyncInfo);Release(operation); }
        }

        private static void WaitForCompletion(IntPtr asyncInfo,int timeoutSeconds,string name)
        {
            var statusFn=GetVtableDelegate<GetAsyncStatusDelegate>(asyncInfo,7);var sw=Stopwatch.StartNew();int status;
            while(true){ThrowIfFailed(statusFn(asyncInfo,out status),"IAsyncInfo.Status");if(status!=0)break;if(sw.Elapsed.TotalSeconds>=timeoutSeconds)throw new TimeoutException(name+" timed out.");Thread.Sleep(250);}
            if(status==AsyncStatusError){int ec;var err=GetVtableDelegate<GetAsyncErrorCodeDelegate>(asyncInfo,8);ThrowIfFailed(err(asyncInfo,out ec),"IAsyncInfo.ErrorCode");throw new COMException(name+" failed with HRESULT 0x"+ec.ToString("X8")+".",ec);}if(status==AsyncStatusCanceled)throw new OperationCanceledException(name+" was canceled.");if(status!=AsyncStatusCompleted)throw new InvalidOperationException(name+" returned async status "+status+".");
        }

        private static void ActivateDirect(string dllPath,out IntPtr factory,out IntPtr instance,out IntPtr utilities,out IntPtr className)
        {
            factory=instance=utilities=className=IntPtr.Zero;IntPtr module=LoadLibraryEx(dllPath,IntPtr.Zero,0x00001100);if(module==IntPtr.Zero)throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(),"LoadLibraryEx failed");IntPtr ep=GetProcAddress(module,"DllGetActivationFactory");if(ep==IntPtr.Zero)throw new EntryPointNotFoundException("DllGetActivationFactory");ThrowIfFailed(WindowsCreateString(RuntimeClassName,RuntimeClassName.Length,out className),"WindowsCreateString");var gf=Marshal.GetDelegateForFunctionPointer<DllGetActivationFactoryDelegate>(ep);ThrowIfFailed(gf(className,out factory),"DllGetActivationFactory");var activate=GetVtableDelegate<ActivateInstanceDelegate>(factory,6);ThrowIfFailed(activate(factory,out instance),"ActivateInstance");ThrowIfFailed(QueryInterface(instance,IDeviceLinkUtilitiesId,out utilities),"QueryInterface(IDeviceLinkUtilities)");
        }
        private static int QueryInterface(IntPtr value,Guid iid,out IntPtr result){var q=GetVtableDelegate<QueryInterfaceDelegate>(value,0);return q(value,ref iid,out result);}
        private static T GetVtableDelegate<T>(IntPtr instance,int slot) where T:class{IntPtr table=Marshal.ReadIntPtr(instance);IntPtr method=Marshal.ReadIntPtr(table,slot*IntPtr.Size);return Marshal.GetDelegateForFunctionPointer(method,typeof(T)) as T;}
        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);}
    }
}