VMConnectConfig.psm1
#Requires -RunAsAdministrator #.ExternalHelp VMConnectConfig-help.xml data MsgTable { # culture="en-US" ConvertFrom-StringData @' #################################################################################################################################################################################### ## Error Message Strings #################################################################################################################################################################################### CimSessionError=Unable to establish a CIM session to computer '{0}' (unknown error). ClientSettingsFileDesktopSizeError=Unable to read the DesktopSize value from '{0}'. Using a default value of '{1}'. ConfigFileNotFoundIdError=Unable to find a valid vmconnect configuration file in '{0}' for a virtual machine with ID '{1}'. ConfigFileNotFoundNameError=Unable to find a valid vmconnect configuration file in '{0}' for a virtual machine with name '{1}'. ConfigFileNotFoundPathError=A valid vmconnect configuration file could not be found at the specified path '{0}'. ConnectionError=Unable to connect to computer '{0}'. DirectoryNotFoundError=Cannot find directory '{0}' because it does not exist. EnhancedRdpSessionError=Cannot establish an enhanced RDP session for the virtual machine '{0}' ({1}) on computer '{2}'. Please check configuration and try again. FilenameGuidUndefinedError=The 'Id' property cannot be determined for the virtual machine '{0}' because: {1} GuidNotFoundInFilenameError=A valid GUID was not detected in the filename '{0}'. HyperVGuiMgmtToolsError=Error 0x80370114 - The operation could not be started. Please verify that the Windows feature '{0}' is installed and try again. HyperVGuiToolsWmiError=An unknown error occurred while trying to detect the installation of the Hyper-V GUI Management Tools. InvalidConfigFileError='{0}' is not a valid vmconnect configuration file. InvalidConfigFileExtensionError=The file '{0}' does not have a valid '.config' extension. InvalidRedirectedDriveFormatError='{0}' is not a valid redirected drive value. Please specify a single drive letter(s). The values "*" and "dynamicdrives" are also allowed. InvalidXmlElementError=The file '{0}' contains one or more invalid vmconnect configuration XML elements. InvalidXmlFileError='{0}' is not a valid XML file. ParameterMissingError=Please include at least one additional parameter from the set '{0}'. PathNotFoundError=Cannot find path '{0}' because it does not exist. UnknownError=Unknown error. UnsupportedConfigurationSettingError=The vmconnect configuration file '{0}' for the virtual machine '{1}'{2} does not support the '{3}' configuration setting. VmconnectExeNotFoundError=Unable to locate '{0}'. Please verify that the '{1}' Windows feature is installed and try again. VmExistsAndVmVersionUndefinedError=The properties 'VMExists' and 'VMVersion' cannot be determined for the virtual machine '{0}'{1} on computer '{2}' because: {3} VmExistsAndVmVersionAndVmServerNameUndefinedError=The properties 'VMExists' and 'VMVersion' cannot be determined for the virtual machine '{0}'{1} because: {2} VmExistsUndefinedError=The property 'VMExists' cannot be determined for the virtual machine '{0}'{1} because: {2} VmHostHyperVRoleError=The Hyper-V role is not installed on the destination host '{0}'. VmIdNeededToCalculatePropertiesError=A valid virtual machine ID is needed to determine the properties 'VMExists' and 'VMVersion'. VmNotFoundError={0} was unable to find the virtual machine '{1}'{2} on computer '{3}'. VmServerNameNotFound=The VmServerName value is empty or missing in the file '{0}'. VmVersionUndefinedError=The property 'VMVersion' cannot be determined for the virtual machine '{0}'{1} because: {2} #################################################################################################################################################################################### ## Non-Error Status Message Strings #################################################################################################################################################################################### ActionMsgCreateConfigFile=Create vmconnect configuration file for the virtual machine '{0}'{1} ActionMsgDeletePermanently=Remove File ActionMsgModifyConfigFile=Modify enhanced session configuration for the virtual machine '{0}'{1} ActionMsgMoveToRecycleBin=Send to Recycle Bin ActionMsgRemoveDirectory=Remove Directory ActionMsgResetVmConfigFile=Reset vmconnect configuration for the virtual machine '{0}'{1} ActionMsgStartingVmconnectExe=Start '{0}' ConnectionSuccessMsg=Successful connection test to computer '{0}'. ConstructingOutputObjMsg=Constructing VMConnectConfig output object for '{0}'. CopyingToTempPathMsg=Copying temp file '{0}' to '{1}'. CreatingCimSessionMsg=Attempting to create a new CIM session connection to computer '{0}'. CreatingConfigFileMsg=Creating vmconnect configuration file for the virtual machine '{0}'{1} DeletingMsg=Moving '{0}' to the Recycle Bin. DeletingPermanentlyMsg=Permanently deleting '{0}'. GettingConfigFilesForDeletedVirtualMachines=Getting a list of .config files in '{0}' whose corresponding virtual machines no longer exist. HyperVGuiMgmtToolsFeatureName=Hyper-V GUI Management Tools HyperVGuiMgmtToolsFeatureNameLegacy=Hyper-V Tools LoadingFileMsg=Loading '{0}'. NoChangesMadeMsg=No changes were made to file '{0}'. NoFilesToDelete=Operation canceled. No files to delete. ResetVmConfigFileMsg=Resetting vmconnect configuration file '{0}' to default settings. SavingToTempPathMsg=Saving to temp file '{0}'. TargetMsgStartingVmconnectExe=Virtual machine '{0}' ({1}) hosted on computer '{2}' TestingConnectionMsg=Testing connection to '{0}'. TestingConnectionWaitingMsg=Waiting for connection test to finish. TestingConnectionWaitingParallelMsg=Waiting for connection tests to finish running in parallel. UsingHostNameForVmServerName=VMServerName was not specified. By default, the local host name '{0}' will be used. XmlSchemaValidatingFileMsg=Validating '{0}' against XML schema. '@ } Import-LocalizedData -BindingVariable 'MsgTable' -FileName 'VMConnectConfigResources' $GuidRegexPattern = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' $ModuleRoot = $PSScriptRoot $VMConfigFolder = [System.IO.Path]::Combine([System.String[]]@($env:APPDATA, 'Microsoft', 'Windows', 'Hyper-V', 'Client', '1.0')) $ClientSettingsConfigFile = (Join-Path -Path $VMConfigFolder -ChildPath 'clientsettings.config') $ConfigFilePattern = "^vmconnect\.rdp\.$GuidRegexPattern.*\.config$" $ErrorCatInvalidArgument = [System.Management.Automation.ErrorCategory]::InvalidArgument $ErrorCatInvalidOperation = [System.Management.Automation.ErrorCategory]::InvalidOperation $ErrorCatObjectNotFound = [System.Management.Automation.ErrorCategory]::ObjectNotFound # The [DefaultSettingValue] of [System.Drawing.Size]DesktopSize is hardcoded in Microsoft.Virtualization.Client.dll and is usually 1366 x 768. $DefaultDesktopSize = '1366, 768' $HostName = [System.Net.Dns]::GetHostName() $LegacySettingsList = @('AudioCaptureRedirectionMode', 'SaveButtonChecked', 'FullScreen', 'SmartCardsRedirection', 'RedirectedPnpDevices', 'ClipboardRedirection', 'DesktopSize', 'VmServerName', 'RedirectedUsbDevices', 'SavedConfigExists', 'UseAllMonitors', 'AudioPlaybackRedirectionMode', 'PrinterRedirection', 'RedirectedDrives', 'VmName' ) $ModernSettingsList = @('AudioCaptureRedirectionMode', 'EnablePrinterRedirection', 'FullScreen', 'SmartCardsRedirection', 'RedirectedPnpDevices', 'ClipboardRedirection', 'DesktopSize', 'VmServerName', 'RedirectedUsbDevices', 'SavedConfigExists', 'UseAllMonitors', 'AudioPlaybackRedirectionMode', 'PrinterRedirection', 'RedirectedDrives', 'VmName', 'SaveButtonChecked' ) $WebAuthnSettingsList = @('AudioCaptureRedirectionMode', 'EnablePrinterRedirection', 'FullScreen', 'SmartCardsRedirection', 'RedirectedPnpDevices', 'ClipboardRedirection', 'DesktopSize', 'VmServerName', 'RedirectedUsbDevices', 'SavedConfigExists', 'UseAllMonitors', 'AudioPlaybackRedirectionMode', 'PrinterRedirection', 'WebAuthnRedirection', 'RedirectedDrives', 'VmName', 'SaveButtonChecked' ) $VMConnectConfigXmlSchema = @" <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="configuration"> <xs:complexType> <xs:sequence> <xs:element name="Microsoft.Virtualization.Client.RdpOptions"> <xs:complexType> <xs:sequence> <xs:element name="setting" minOccurs="15" maxOccurs="17"> <xs:complexType> <xs:sequence> <xs:element type="xs:string" name="value"/> </xs:sequence> <xs:attribute type="xs:string" name="name" use="required"/> <xs:attribute type="xs:string" name="type" use="required"/> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema> "@ $LegacyConfigFileXml = @' <?xml version="1.0" encoding="utf-8"?> <configuration> <Microsoft.Virtualization.Client.RdpOptions> <setting name="AudioCaptureRedirectionMode" type="System.Boolean"> <value>False</value> </setting> <setting name="SaveButtonChecked" type="System.Boolean"> <value>True</value> </setting> <setting name="FullScreen" type="System.Boolean"> <value>False</value> </setting> <setting name="SmartCardsRedirection" type="System.Boolean"> <value>True</value> </setting> <setting name="RedirectedPnpDevices" type="System.String"> <value /> </setting> <setting name="ClipboardRedirection" type="System.Boolean"> <value>True</value> </setting> <setting name="DesktopSize" type="System.Drawing.Size"> <value>{0}</value> </setting> <setting name="VmServerName" type="System.String"> <value>{1}</value> </setting> <setting name="RedirectedUsbDevices" type="System.String"> <value /> </setting> <setting name="SavedConfigExists" type="System.Boolean"> <value>True</value> </setting> <setting name="UseAllMonitors" type="System.Boolean"> <value>False</value> </setting> <setting name="AudioPlaybackRedirectionMode" type="Microsoft.Virtualization.Client.RdpOptions+AudioPlaybackRedirectionType"> <value>AUDIO_MODE_REDIRECT</value> </setting> <setting name="PrinterRedirection" type="System.Boolean"> <value>True</value> </setting> <setting name="RedirectedDrives" type="System.String"> <value /> </setting> <setting name="VmName" type="System.String"> <value>{2}</value> </setting> </Microsoft.Virtualization.Client.RdpOptions> </configuration> '@ $ModernConfigFileXml = @' <?xml version="1.0" encoding="utf-8"?> <configuration> <Microsoft.Virtualization.Client.RdpOptions> <setting name="AudioCaptureRedirectionMode" type="System.Boolean"> <value>False</value> </setting> <setting name="EnablePrinterRedirection" type="System.Boolean"> <value>True</value> </setting> <setting name="FullScreen" type="System.Boolean"> <value>False</value> </setting> <setting name="SmartCardsRedirection" type="System.Boolean"> <value>True</value> </setting> <setting name="RedirectedPnpDevices" type="System.String"> <value /> </setting> <setting name="ClipboardRedirection" type="System.Boolean"> <value>True</value> </setting> <setting name="DesktopSize" type="System.Drawing.Size"> <value>{0}</value> </setting> <setting name="VmServerName" type="System.String"> <value>{1}</value> </setting> <setting name="RedirectedUsbDevices" type="System.String"> <value /> </setting> <setting name="SavedConfigExists" type="System.Boolean"> <value>True</value> </setting> <setting name="UseAllMonitors" type="System.Boolean"> <value>False</value> </setting> <setting name="AudioPlaybackRedirectionMode" type="Microsoft.Virtualization.Client.RdpOptions+AudioPlaybackRedirectionType"> <value>AUDIO_MODE_REDIRECT</value> </setting> <setting name="PrinterRedirection" type="System.Boolean"> <value>True</value> </setting> <setting name="RedirectedDrives" type="System.String"> <value /> </setting> <setting name="VmName" type="System.String"> <value>{2}</value> </setting> <setting name="SaveButtonChecked" type="System.Boolean"> <value>True</value> </setting> </Microsoft.Virtualization.Client.RdpOptions> </configuration> '@ $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); } DeviceType devType; foreach (string dType in Enum.GetNames(typeof(DeviceType))) { if (dType == "PnP") { devType = DeviceType.PnP; } else { devType = DeviceType.USB; } RedirectedUsbDevice laterDevices = new RedirectedUsbDevice { Name = "Redirect all supported devices that are connected later.", Description = "Redirect all supported devices that are connected later.", InstanceId = "dynamicdevices", DeviceType = devType }; RedirectedUsbDevice allDevices = new RedirectedUsbDevice { Name = "Redirect all supported devices, including ones that are connected later.", Description = "Redirect all supported devices, including ones that are connected later.", InstanceId = "*", DeviceType = devType }; WriteObject(laterDevices); WriteObject(allDevices); } } catch { // Since this is being used for tab/menu completion for Edit-VMConnectConfig's -RedirectedPnpDevices and -RedirectedUsbDevices parameters // don't output an error record object if an exception occurs. // WriteError(new ErrorRecord(ex, "USB device enumeration failed", ErrorCategory.InvalidOperation, null)); } } } '@ $WebAuthnConfigFileXml = @' <?xml version="1.0" encoding="utf-8"?> <configuration> <Microsoft.Virtualization.Client.RdpOptions> <setting name="AudioCaptureRedirectionMode" type="System.Boolean"> <value>False</value> </setting> <setting name="EnablePrinterRedirection" type="System.Boolean"> <value>True</value> </setting> <setting name="FullScreen" type="System.Boolean"> <value>False</value> </setting> <setting name="SmartCardsRedirection" type="System.Boolean"> <value>True</value> </setting> <setting name="RedirectedPnpDevices" type="System.String"> <value /> </setting> <setting name="ClipboardRedirection" type="System.Boolean"> <value>True</value> </setting> <setting name="DesktopSize" type="System.Drawing.Size"> <value>{0}</value> </setting> <setting name="VmServerName" type="System.String"> <value>{1}</value> </setting> <setting name="RedirectedUsbDevices" type="System.String"> <value /> </setting> <setting name="SavedConfigExists" type="System.Boolean"> <value>True</value> </setting> <setting name="UseAllMonitors" type="System.Boolean"> <value>False</value> </setting> <setting name="AudioPlaybackRedirectionMode" type="Microsoft.Virtualization.Client.RdpOptions+AudioPlaybackRedirectionType"> <value>AUDIO_MODE_REDIRECT</value> </setting> <setting name="PrinterRedirection" type="System.Boolean"> <value>True</value> </setting> <setting name="WebAuthnRedirection" type="System.Boolean"> <value>True</value> </setting> <setting name="RedirectedDrives" type="System.String"> <value /> </setting> <setting name="VmName" type="System.String"> <value>{2}</value> </setting> <setting name="SaveButtonChecked" type="System.Boolean"> <value>True</value> </setting> </Microsoft.Virtualization.Client.RdpOptions> </configuration> '@ $SystemDirectory = [System.Environment]::SystemDirectory $VMConnectConfigFilenamePattern = 'vmconnect.rdp.{0}.config' $VMNameXPath = "//setting[@name='VmName']" $VMServerNameXPath = "//setting[@name='VmServerName']" $XmlEncoding = New-Object -TypeName 'System.Text.UTF8Encoding' -ArgumentList $false $XPath = 'configuration/Microsoft.Virtualization.Client.RdpOptions/setting' $privateFunctionsPath = Join-Path -Path $ModuleRoot -ChildPath 'Private' $publicFunctionsPath = Join-Path -Path $ModuleRoot -ChildPath 'Public' foreach ($privateFunction in (Get-ChildItem -Path $privateFunctionsPath -Filter '*.ps1' -ErrorAction Ignore)) { . $privateFunction.FullName } foreach ($publicFunction in (Get-ChildItem -Path $publicFunctionsPath -Filter '*.ps1' -ErrorAction Ignore)) { . $publicFunction.FullName } if ($PSVersionTable.PSVersion.Major -ge 5) { $argCompleterFunctionList = @('Edit-VMConnectConfig', 'Get-VMConnectConfig', 'Remove-VMConnectConfig', 'Reset-VMConnectConfig') $nameArgCompleterScriptBlock = { param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) $files = Get-AllConfigFiles | Where-Object {Test-VMConnectConfig -LiteralPath $_.FullName -Verbose:$false} $vmNameList = @() $files | ForEach-Object { ($xmlFile = [System.Xml.XmlDocument]::new()).Load($_.FullName) $vmName = $xmlFile.SelectSingleNode($VMNameXPath).value $vmNameList += $vmName } #ForEach-Object $vmNameList | Sort-Object -Unique | Where-Object {$_ -like "*$WordToComplete*"} | ForEach-Object { $resultName = $_ if ($resultName -match '\s') { $resultName = "'$resultName'" } [System.Management.Automation.CompletionResult]::new($resultName, $_, 'ParameterValue', $_) } #ForEach-Object } $idArgCompleterScriptBlock = { param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) $files = Get-AllConfigFiles | Where-Object {Test-VMConnectConfig -LiteralPath $_.FullName -Verbose:$false} $vmObjects = @() $files | ForEach-Object { ($xmlFile = [System.Xml.XmlDocument]::new()).Load($_.FullName) $vmName = $xmlFile.SelectSingleNode($VMNameXPath).value $vmId = ($_.Name | Select-String -Pattern $GuidRegexPattern).Matches.Value $vmObjects += [pscustomobject] @{Name = $vmName; Id = $vmId} } #ForEach-Object $vmObjects | Sort-Object -Property Id | Where-Object {$_.Id -like "*$WordToComplete*"} | ForEach-Object { $resultId = "'" + $_.Id + "'" $toolTip = "Id:`t`t$($_.Id)`nName:`t`t$($_.Name)" [System.Management.Automation.CompletionResult]::new($resultId, $_.Id, 'ParameterValue', $toolTip) } #ForEach-Object } $redirectedUsbDevicesArgCompleterScriptBlock = { param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) $devices = Get-RedirectedUsbDevice -DeviceType 'USB' if ($devices.Count -gt 0) { $devices | Where-Object {$_.InstanceId -like "*$WordToComplete*"} | ForEach-Object { $result = "'" + $_.InstanceId + "'" $toolTip = "InstanceId: $($_.InstanceId)`nDevice Name: $($_.Name)" [System.Management.Automation.CompletionResult]::new($result, $_.InstanceId, 'ParameterValue', $toolTip) } } } $redirectedPnPDevicesArgCompleterScriptBlock = { param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) $devices = @() $devices = Get-RedirectedUsbDevice -DeviceType 'PnP' $devices = $devices | Sort-Object -Property 'Name' -Unique if ($devices.Count -gt 0) { $devices | Where-Object {$_.InstanceId -like "*$WordToComplete*"} | ForEach-Object { $result = "'" + $_.InstanceId + "'" $toolTip = "InstanceId: $($_.InstanceId)`nDevice Name: $($_.Name)" [System.Management.Automation.CompletionResult]::new($result, $_.InstanceId, 'ParameterValue', $toolTip) } } } Register-ArgumentCompleter -CommandName $argCompleterFunctionList -ParameterName 'Id' -ScriptBlock $idArgCompleterScriptBlock Register-ArgumentCompleter -CommandName $argCompleterFunctionList -ParameterName 'Name' -ScriptBlock $nameArgCompleterScriptBlock Register-ArgumentCompleter -CommandName 'Edit-VMConnectConfig' -ParameterName 'RedirectedPnpDevices' -ScriptBlock $redirectedPnPDevicesArgCompleterScriptBlock Register-ArgumentCompleter -CommandName 'Edit-VMConnectConfig' -ParameterName 'RedirectedUsbDevices' -ScriptBlock $redirectedUsbDevicesArgCompleterScriptBlock } # if ($PSVersionTable.PSVersion.Major -ge 5) # In PowerShell 4.0 sometimes the module manifest doesn't export aliases and sometimes the function [Alias()] attribute doesn't work reliably. if ($PSVersionTable.PSVersion.Major -lt 5) { $functionAliasList = @() $functionAliasList += [pscustomobject] @{FunctionName = 'Edit-VMConnectConfig'; AliasName = 'edvmc'} $functionAliasList += [pscustomobject] @{FunctionName = 'Get-VMConnectConfig'; AliasName = 'gvmc'} $functionAliasList += [pscustomobject] @{FunctionName = 'New-VMConnectConfig'; AliasName = 'nvmc'} $functionAliasList += [pscustomobject] @{FunctionName = 'Remove-VMConnectConfig'; AliasName = 'rvmc'} $functionAliasList += [pscustomobject] @{FunctionName = 'Reset-VMConnectConfig'; AliasName = 'rsvmc'} $functionAliasList += [pscustomobject] @{FunctionName = 'Test-VMConnectConfig'; AliasName = 'tvmc'} foreach ($function in $functionAliasList) { New-Alias -Name $function.AliasName -Value $function.FunctionName -Force Export-ModuleMember -Function $function.FunctionName -Alias $function.AliasName } } |