powershellcommstsc

Enumerate redirected PnP devices with the Remote Desktop ActiveX Control


I've tried a few different ways of enumerating redirected USB devices using the Remote Desktop ActiveX control in PowerShell but haven't been able to figure out a way to achieve this.

Add-Type -Path 'C:\mstscax\MSTSCLib.dll'

$rdpClient = [MSTSCLib.MsRdpClient10Class]::new()

# These return a System.__ComObject object
$rdpClient.get_DeviceCollection()
$rdpClient.DeviceCollection
$rdpClient.IMsRdpClientNonScriptable4_DeviceCollection
$rdpClient.IMsRdpClientNonScriptable5_DeviceCollection

# Casting fails:
[MSTSCLib.IMsRdpDeviceCollection]$rdpClient.get_DeviceCollection()
[MSTSCLib.IMsRdpDeviceCollection]$rdpClient.DeviceCollection
[MSTSCLib.IMsRdpDeviceCollection]$rdpClient.IMsRdpClientNonScriptable4_DeviceCollection
[MSTSCLib.IMsRdpDeviceCollection]$rdpClient.IMsRdpClientNonScriptable5_DeviceCollection

# Cannot convert the "System.__ComObject" value of type "System.__ComObject" to type "MSTSCLib.IMsRdpDeviceCollection".





Solution

  • Enumerating redirected PnP devices in PowerShell is trivial

    function Get-RedirectedPnpDevice
    {
        $pnpDevices = Get-PnPdevice | Where-Object {$_.Class -eq "WPD" -and $_.Status -eq "OK"}
                
        foreach ($device in $pnpDevices)
        {
            [pscustomobject] @{
                Name = $device.Name
                InstanceId = $device.DeviceID
                Caption = $device.Caption
            }
        }
    }
    

    Enumerating redirected RemoteFX USB devices was not straightforward


    function Get-RedirectedUsbDevice
    {
        [CmdletBinding()]
        param (
            [Parameter(ParameterSetName = 'DeviceType', Position = 0)]
            [ValidateSet('PnP', 'USB')]
            [System.String]$DeviceType
        )
    
        $redirectedUsbDeviceCSCode = @'
    using System;
    using System.Management.Automation;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    
    public enum DeviceType
    {
        PnP,
        USB
    }
    
    public class RedirectedUsbDevice
    {
        public string Name {get; set;}
        public string Description { get; set; }
        public string InstanceId { get; set; }
        public DeviceType DeviceType { get; set; }
    }
    
    internal class RdpClientInterop
    {
        [ComImport]
        [Guid("B3378D90-0728-45C7-8ED7-B6159FB92219")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface IMsRdpClientNonScriptable3
        {
            [DispId(1)]
            string ClearTextPassword { set; }
    
            [DispId(2)]
            string PortablePassword { get; set; }
    
            [DispId(3)]
            string PortableSalt { get; set; }
    
            [DispId(4)]
            string BinaryPassword { get; set; }
    
            [DispId(5)]
            string BinarySalt { get; set; }
    
            void ResetPassword();
    
            void NotifyRedirectDeviceChange([In][ComAliasName("MSTSCLib.UINT_PTR")] uint wParam, [In][ComAliasName("MSTSCLib.LONG_PTR")] int lParam);
    
            void SendKeys([In] int numKeys, [In] ref bool pbArrayKeyUp, [In] ref int plKeyData);
    
            [DispId(13)]
            [ComAliasName("MSTSCLib.wireHWND")]
            IntPtr UIParentWindowHandle
            {
                [return: ComAliasName("MSTSCLib.wireHWND")]
                get;
    
                [param: ComAliasName("MSTSCLib.wireHWND")]
                set;
            }
    
            [DispId(14)]
            bool ShowRedirectionWarningDialog { get; set; }
    
            [DispId(15)]
            bool PromptForCredentials { get; set; }
    
            [DispId(16)]
            bool NegotiateSecurityLayer { get; set; }
    
            [DispId(17)]
            bool EnableCredSspSupport { get; set; }
    
            [DispId(21)]
            bool RedirectDynamicDrives { get; set; }
    
            [DispId(20)]
            bool RedirectDynamicDevices { get; set; }
    
            [DispId(18)]
            IMsRdpDeviceCollection DeviceCollection
            {
                [return: MarshalAs(UnmanagedType.Interface)]
                get;
            }
    
            [DispId(23)]
            bool WarnAboutSendingCredentials { get; set; }
    
            [DispId(22)]
            bool WarnAboutClipboardRedirection { get; set; }
    
            [DispId(24)]
            string ConnectionBarText { get; set; }
        }
    
        [Guid("56540617-D281-488C-8738-6A8FDF64A118")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface IMsRdpDeviceCollection
        {
            void RescanDevices(bool vboolDynRedir);
    
            IMsRdpDevice get_DeviceByIndex(uint index);
    
            IMsRdpDevice get_DeviceById(string devInstanceId);
    
            [DispId(225)]
            uint DeviceCount { get; }
        }
    
        [Guid("60C3B9C8-9E92-4F5E-A3E7-604A912093EA")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface IMsRdpDevice
        {
            [DispId(222)]
            string DeviceInstanceId { get; }
    
            [DispId(220)]
            string FriendlyName { get; }
    
            [DispId(221)]
            string DeviceDescription { get; }
    
            [DispId(223)]
            bool RedirectionState { get; set; }
        }
    
        [ComImport]
        [Guid("5fb94466-7661-42a8-98b7-01904c11668f")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface IMsRdpDeviceV2
        {
            string DeviceInstanceId();
            string FriendlyName();
            string DeviceDescription();
            void RedirectionState([In][MarshalAs(UnmanagedType.Bool)] bool RedirState);
            bool RedirectionState();
            string DeviceText();
            bool IsUSBDevice();
            bool IsCompositeDevice();
            uint DriveLetterBitmap();
        }
    
        [ComImport]
        [Guid("B2A5B5CE-3461-444A-91D4-ADD26D070638")]
        [TypeLibType(4160)]
        internal interface IMsRdpClient7
        {
        }
    
        internal static string GetDeviceName(IMsRdpDeviceV2 device)
        {
            try
            {
                string friendlyName = device.FriendlyName();
                if (!string.IsNullOrEmpty(friendlyName))
                {
                    return friendlyName;
                }
            }
            catch { }
    
            try
            {
                string deviceText = device.DeviceText();
                if (!string.IsNullOrEmpty(deviceText))
                {
                    return deviceText;
                }
            }
            catch { }
    
            try
            {
                string deviceDescription = device.DeviceDescription();
                if (!string.IsNullOrEmpty(deviceDescription))
                {
                    return deviceDescription;
                }
            }
            catch { }
            return "Unknown device name";
        }
    }
    
    [Cmdlet(VerbsCommon.Get, "RedirectedUsbDevice")]
    public class GetRedirectedUSBDeviceCommand : Cmdlet
    {
        protected override void ProcessRecord()
        {
            try
            {
                Type mstscaxType = Type.GetTypeFromProgID("mstscax.mstscax");
                var rdpClient = (RdpClientInterop.IMsRdpClient7)Activator.CreateInstance(mstscaxType);
                RdpClientInterop.IMsRdpClientNonScriptable3 msRdpClientNonScriptable = (RdpClientInterop.IMsRdpClientNonScriptable3)(object)rdpClient;
                RdpClientInterop.IMsRdpDeviceCollection deviceCollection = msRdpClientNonScriptable.DeviceCollection;
    
                for (int i = 0; i < deviceCollection.DeviceCount; i++)
                {
                    RdpClientInterop.IMsRdpDevice msRdpDevice = deviceCollection.get_DeviceByIndex((uint)i);
                    RdpClientInterop.IMsRdpDeviceV2 msRdpDeviceV2 = (RdpClientInterop.IMsRdpDeviceV2)msRdpDevice;
    
                    RedirectedUsbDevice redirectedUsbDevice = new RedirectedUsbDevice {
                        Name = RdpClientInterop.GetDeviceName(msRdpDeviceV2).Trim().Replace("\0", ""),
                        Description = msRdpDeviceV2.DeviceDescription().Trim().Replace("\0", ""),
                        InstanceId = msRdpDeviceV2.DeviceInstanceId().Trim().Replace("\0", ""),
                        DeviceType = msRdpDeviceV2.IsUSBDevice() ? DeviceType.USB : DeviceType.PnP
                    };
    
                    WriteObject(redirectedUsbDevice);
                }
            }
            catch (Exception ex)
            {
                WriteError(new ErrorRecord(ex, "USB device enumeration failed", ErrorCategory.InvalidOperation, null));
            }
        }
    }
    '@
    
        try
        {
            Add-Type -TypeDefinition $redirectedUsbDeviceCSCode -Language CSharp
    
            $getRedirectedUSBDeviceCommandObj = New-Object -TypeName GetRedirectedUSBDeviceCommand
    
            if ($PSCmdlet.ParameterSetName -eq 'DeviceType')
            {
                $outputObj = $getRedirectedUSBDeviceCommandObj.Invoke() | Where-Object {$_.DeviceType -eq $DeviceType}
            }
            else
            {
                $outputObj = $getRedirectedUSBDeviceCommandObj.Invoke() | Out-Default
            }
    
            if ($outputObj.Count -lt 1) {Write-Output $null}
            else {Write-Output $outputObj}
        }
        catch
        {
            Write-Error $_
        }
    }