Public/Edit-VMConnectConfig.ps1
#.ExternalHelp VMConnectConfig-help.xml function Edit-VMConnectConfig { [CmdletBinding(ConfirmImpact = 'Medium', HelpURI='https://thegraffix.github.io/VMConnectConfig/edit-vmconnectconfig.html', SupportsShouldProcess)] [Alias('edvmc')] [OutputType([System.Management.Automation.PSObject[]])] param ( [Parameter(Mandatory, ParameterSetName = 'Id', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Parameter(Mandatory, ParameterSetName = 'IdGUI', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('VMId')] [ValidateNotNullOrEmpty()] [System.Guid[]]$Id, [Parameter(Mandatory, ParameterSetName = 'InputObject', Position = 0, ValueFromPipeline)] [Parameter(Mandatory, ParameterSetName = 'InputObjectGUI', Position = 0, ValueFromPipeline)] [System.Management.Automation.PSTypeName('VMConnectConfig')]$InputObject, [Parameter(Mandatory, ParameterSetName = 'LiteralPath', ValueFromPipelineByPropertyName)] [Parameter(Mandatory, ParameterSetName = 'LiteralPathGUI', ValueFromPipelineByPropertyName)] [Alias('FullName')] [ValidateNotNullOrEmpty()] [System.String[]]$LiteralPath, [Parameter(Mandatory, ParameterSetName = 'Name', Position = 0, ValueFromPipeline)] [Parameter(Mandatory, ParameterSetName = 'NameGUI', Position = 0, ValueFromPipeline)] [SupportsWildcards()] [ValidateNotNullOrEmpty()] [System.String[]]$Name, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [switch]$PassThru, [Parameter(Mandatory, ParameterSetName = 'Path')] [Parameter(Mandatory, ParameterSetName = 'PathGUI')] [SupportsWildcards()] [ValidateNotNullOrEmpty()] [System.String[]]$Path, [Parameter(ParameterSetName = 'LiteralPathGUI')] [Parameter(ParameterSetName = 'PathGUI')] [Parameter(ParameterSetName = 'IdGUI')] [Parameter(ParameterSetName = 'InputObjectGUI')] [Parameter(ParameterSetName = 'NameGUI')] [switch]$ShowEnhancedConnectionDialog, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [System.Boolean]$AudioCaptureRedirectionMode, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [ValidateSet('AUDIO_MODE_NONE', 'AUDIO_MODE_REDIRECT', 'AUDIO_MODE_PLAY_ON_SERVER')] [System.String]$AudioPlaybackRedirectionMode, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [System.Boolean]$ClipboardRedirection, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [ValidateNotNullOrEmpty()] [System.Drawing.Size]$DesktopSize, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [System.Boolean]$EnablePrinterRedirection, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [System.Boolean]$FullScreen, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [System.Boolean]$PrinterRedirection, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [System.String[]]$RedirectedDrives, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [System.String[]]$RedirectedPnpDevices, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [System.String[]]$RedirectedUsbDevices, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [System.Boolean]$SaveButtonChecked, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [System.Boolean]$SmartCardsRedirection, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [System.Boolean]$UseAllMonitors, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'LiteralPath')] [Parameter(ParameterSetName = 'Name')] [Parameter(ParameterSetName = 'Path')] [System.Boolean]$WebAuthnRedirection ) begin { # This is a workaround for a bug where advanced functions can output an error if "-ErrorAction/-WarningAction Ignore" are used. if ($ErrorActionPreference -eq 'Ignore') {$ErrorActionPreference = 'Ignore'} if ($WarningPreference -eq 'Ignore') {$WarningPreference = 'Ignore'} # Validate $RedirectedDrives arguments if necessary. if ($PSBoundParameters.ContainsKey('RedirectedDrives')) { # Have to do this in the begin{} block instead of using ValidateSet() because $RedirectedDrives needs to be able to accept # $null/empty strings (in addition to drive letters, "*" and "dynamicdrives"). $allowedRedirectedDrivesValues = @($null, [System.String]::Empty, '*', 'dynamicdrives', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z') foreach ($redirectedDriveItem in $RedirectedDrives) { if ($redirectedDriveItem -inotin $allowedRedirectedDrivesValues) { $ex = New-Object -TypeName 'System.ArgumentException' -ArgumentList ($MsgTable.InvalidRedirectedDriveFormatError -f $redirectedDriveItem) throw $ex } } } if ($ShowEnhancedConnectionDialog) { # Win 6.1 (Win7/Server2008 R2) uses "%ProgramFiles%\Hyper-V\vmconnect.exe". # Win 6.2+ (Win8+/Server 2012+) uses "%windir%\System32\vmconnect.exe". $vmconnectExePath = Join-Path -Path $SystemDirectory -ChildPath 'vmconnect.exe' if ((Test-Path -Path $vmconnectExePath) -eq $false) { $vmconnectExePath = [System.IO.Path]::Combine($env:ProgramFiles, 'Hyper-V', 'vmconnect.exe') } $cimParamFilter = $null $os = (Get-CimInstance -ClassName 'Win32_OperatingSystem' -Property ProductType, Version -Verbose:$false) # (1=Workstation 2=DomainController 3=Server) $osProductType = $os.ProductType $isServer = (($osProductType -eq 2) -or ($osProductType -eq 3)) $osVersion = $os.Version # Win10+ $wmiFilterClient = 'Caption = "Hyper-V GUI Management Tools"' # Server 2016+ $wmiFilterServer = 'Name = "RSAT-Hyper-V-Tools-Feature"' # Win7, Server 2008 R2 $wmiFilterClientLegacy = 'Caption = "Hyper-V Tools"' # Win8/8.1, Server 2012/2012 R2 $wmiFilterServerLegacy = 'Name = "Microsoft-Hyper-V-Management-Clients"' if ($osVersion.StartsWith('6.1')) { # Windows Feature name for Win7/Server 2008 R2 is: "Hyper-V Tools" $windowsFeatureName = ($MsgTable.HyperVGuiMgmtToolsFeatureNameLegacy) } else { # Windows Feature for Win8+/Server 2012+ is: "Hyper-V GUI Management Tools" $windowsFeatureName = ($MsgTable.HyperVGuiMgmtToolsFeatureName) } if ($osVersion.StartsWith('10.')) { if ($isServer) { $cimParamFilter = $wmiFilterServer } else { $cimParamFilter = $wmiFilterClient } } elseif ($osVersion.StartsWith('6.2') -or $osVersion.StartsWith('6.3')) { $cimParamFilter = $wmiFilterServerLegacy } elseif ($osVersion.StartsWith('6.1')) { if ($isServer) { $cimParamFilter = $wmiFilterServerLegacy } else { $cimParamFilter = $wmiFilterClientLegacy } } $cimParams = @{ ClassName = 'Win32_OptionalFeature' ErrorAction = 'Ignore' Filter = $cimParamFilter Property = 'InstallState' Verbose = $false } if ([System.String]::IsNullOrEmpty($cimParamFilter)) { $ex = New-Object -TypeName 'System.Exception' -ArgumentList ($MsgTable.HyperVGuiToolsWmiError) throw $ex } elseif ((Get-CimInstance @cimParams).InstallState -ne 1) { $ex = New-Object -TypeName 'System.ComponentModel.Win32Exception' -ArgumentList 0x80370114, ($MsgTable.HyperVGuiMgmtToolsError -f $windowsFeatureName) throw $ex } elseif ((Test-Path -Path $vmconnectExePath) -eq $false) { # Check %windir%\System32\vmconnect.exe first, then %programfiles%\Hyper-V\vmconnect.exe -- throw an exception if neither are found. $ex = New-Object -TypeName 'System.IO.FileNotFoundException' -ArgumentList ($MsgTable.VmconnectExeNotFoundError -f $vmconnectExePath, $windowsFeatureName) throw $ex } } # This is for checking if at least 1x "settings" parameter is used. $settingsArray = @('AudioCaptureRedirectionMode', 'AudioPlaybackRedirectionMode', 'ClipboardRedirection', 'DesktopSize', 'EnablePrinterRedirection', 'FullScreen', 'PrinterRedirection', 'RedirectedDrives', 'RedirectedPnpDevices', 'RedirectedUsbDevices', 'SaveButtonChecked', 'SmartCardsRedirection', 'UseAllMonitors', 'WebAuthnRedirection') [System.String[]]$configFilePathList = @() } #begin process { if ($PSCmdlet.ParameterSetName -notlike '*Path') { $null = Test-VMConfigFolder $allConfigFiles = Get-AllConfigFiles } $configSettingParamsList = $null $params = $null if ($ShowEnhancedConnectionDialog -eq $false) { [System.String[]]$params = $PSBoundParameters.Keys $configSettingParamsList = (Compare-Object -ReferenceObject $params -DifferenceObject $settingsArray -ExcludeDifferent -IncludeEqual).InputObject # Throw an exception if not using the "-ShowEnhancedConnectionDialog" switch && no "settings" parameters are used. if ([System.String]::IsNullOrEmpty($configSettingParamsList)) { $paramMissingString = ($settingsArray | Sort-Object) -join ', ' $exceptionMsg = ($MsgTable.ParameterMissingError -f $paramMissingString) $ex = New-Object -TypeName 'System.Management.Automation.ParameterBindingException' -ArgumentList $exceptionMsg throw $ex } } switch ($PSCmdlet.ParameterSetName) { {($_ -eq 'Id') -or ($_ -eq 'IdGUI')} { foreach ($vmGuid in $Id) { $files = @() $files = [System.String[]](($allConfigFiles | Where-Object {$_.Name -match "vmconnect\.rdp\.$vmGuid.*\.config"}).FullName) if (($files.Count -eq 0) -or ([System.String]::IsNullOrEmpty($files))) { $errMsg = ($MsgTable.ConfigFileNotFoundIdError -f $VMConfigFolder, $vmGuid) $errParams = @{ Category = $ErrorCatObjectNotFound Exception = New-Object -TypeName 'System.ArgumentException' -ArgumentList $errMsg Message = $errMsg } Write-Error @errParams } #if no .config file(s) found. else { foreach ($file in $files) { $configFilePathList += $file } } #else } #foreach $vmGuid break } # -Id [-ShowEnhancedConnectionDialog] {($_ -eq 'InputObject') -or ($_ -eq 'InputObjectGUI')} { foreach ($file in $InputObject.Path) { $configFilePathList += $file } break } # -InputObject [-ShowEnhancedConnectionDialog] {($_ -eq 'LiteralPath') -or ($_ -eq 'LiteralPathGUI')} { foreach ($literalItem in $LiteralPath) { $literalPathError = $null $resolvedLiteralPaths = @() $resolvedLiteralPaths = [System.String[]]((Resolve-Path -LiteralPath $literalItem -ErrorVariable 'literalPathError').Path | Where-Object {[System.IO.File]::Exists($_)}) if ((($resolvedLiteralPaths.Count -eq 0) -or ([System.String]::IsNullOrEmpty($resolvedLiteralPaths))) -and [System.String]::IsNullOrEmpty($literalPathError)) { # Output an error message when Resolve-Path returns 0 items and doesn't output an error message. $resolvedLiteralItemPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($literalItem) $errMsg = ($MsgTable.ConfigFileNotFoundPathError -f $resolvedLiteralItemPath) $errParams = @{ Category = $ErrorCatObjectNotFound Exception = New-Object -TypeName 'System.IO.FileNotFoundException' -ArgumentList $errMsg, $resolvedLiteralItemPath Message = $errMsg TargetObject = $resolvedLiteralItemPath } Write-Error @errParams } #if else { foreach ($resolvedLiteralPath in $resolvedLiteralPaths) { $configFilePathList += $resolvedLiteralPath } } #else } #foreach $literalItem break } # -LiteralPath [-ShowEnhancedConnectionDialog] {($_ -eq 'Name') -or ($_ -eq 'NameGUI')} { foreach ($vmName in $Name) { $files = @() try { $files = [System.String[]]((Select-Xml -Path $allConfigFiles.FullName -XPath $VMNameXPath | Where-Object {$_.Node.Value -like $vmName}).Path) } #try catch { # Do nothing. This is to fully suppress terminating errors from the Select-Xml cmdlet when the 1.0 directory exists but is empty. } #catch if (($files.Count -eq 0) -or ([System.String]::IsNullOrEmpty($files))) { $errMsg = ($MsgTable.ConfigFileNotFoundNameError -f $VMConfigFolder, $vmName) $errParams = @{ Category = $ErrorCatObjectNotFound Exception = New-Object -TypeName 'System.ArgumentException' -ArgumentList $errMsg Message = $errMsg } Write-Error @errParams } #if no .config file(s) found. else { foreach ($file in $files) { $configFilePathList += $file } } #else } #foreach $vmName break } # -Name [-ShowEnhancedConnectionDialog] {($_ -eq 'Path') -or ($_ -eq 'PathGUI')} { foreach ($item in $Path) { $pathError = $null $resolvedPaths = @() $resolvedPaths = [System.String[]]((Resolve-Path -Path $item -ErrorVariable 'pathError').Path | Where-Object {[System.IO.File]::Exists($_)}) if ((($resolvedPaths.Count -eq 0) -or ([System.String]::IsNullOrEmpty($resolvedPaths))) -and [System.String]::IsNullOrEmpty($pathError)) { # Output an error message when Resolve-Path returns 0 items and doesn't output an error message. $resolvedItemPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($item) $errMsg = ($MsgTable.ConfigFileNotFoundPathError -f $resolvedItemPath) $errParams = @{ Category = $ErrorCatObjectNotFound Exception = New-Object -TypeName 'System.IO.FileNotFoundException' -ArgumentList $errMsg, $resolvedItemPath Message = $errMsg TargetObject = $resolvedItemPath } Write-Error @errParams } #if else { foreach ($resolvedPath in $resolvedPaths) { $configFilePathList += $resolvedPath } } #else } #foreach $item break } # -Path [-ShowEnhancedConnectionDialog] } #switch ($PSCmdlet.ParameterSetName) } #process end { $configFilePathList = $configFilePathList | Select-Object -Unique [System.String[]]$passThruList = @() [System.String[]]$validConfigFilePathList = @() foreach ($configFile in $configFilePathList) { if ((Test-VMConnectConfig -LiteralPath $configFile -ErrorAction SilentlyContinue) -eq $false) { $errMsg = ($MsgTable.InvalidConfigFileError -f $configFile) $errParams = @{ Category = $ErrorCatInvalidArgument Exception = New-Object -TypeName 'System.ArgumentException' -ArgumentList $errMsg Message = $errMsg TargetObject = $configFile } Write-Error @errParams } else { $validConfigFilePathList += $configFile } } #foreach ($configFile in $configFilePathList) if ($ShowEnhancedConnectionDialog -and $validConfigFilePathList.Count -gt 0) { # The vmconnect.exe -C switch's value is an [Int32], but $vmconnectExeLaunchCount is a [UInt32] because: # - The -C command line option/switch does not accept negative values. # - If the -C parameter value is greater than [Int32]::MaxValue, vmconnect.exe will still use $vmconnectExeLaunchCount as an argument for the -C parameter and vmconnect.exe will output its own (friendlier and more useful) error messages rather than PowerShell generating an ArgumentTransformationMetadataException exception. [System.UInt32]$vmconnectExeLaunchCount = 0 $vmObjArray = @() foreach ($configFile in $validConfigFilePathList) { try { $xmlFile = New-Object -TypeName 'System.Xml.XmlDocument' $xmlFile.Load($configFile) $configFileName = Split-Path -Path $configFile -Leaf $vmPsObjId = ($configFileName | Select-String -Pattern $GuidRegexPattern).Matches.Value $vmPsObjName = $xmlFile.SelectSingleNode($VMNameXPath).value $vmPsObjServerName = $xmlFile.SelectSingleNode($VMServerNameXPath).value $vmObjArray += [pscustomobject] @{ VMId = $vmPsObjId VMName = $vmPsObjName VMServerName = $vmPsObjServerName } } #try catch { $PSCmdlet.WriteError($_) } #catch } #foreach ($configFile in $validConfigFilePathList) # Remove duplicate objects from vmObjArray so we don't call vmconnect.exe multiple times for the same VM Names/IDs. $vmObjArray = $vmObjArray | Select-Object -Property VMId, VMName, VMServerName -Unique foreach ($vmObj in $vmObjArray) { $vmId = $vmObj.VMId $vmName = $vmObj.VMName $vmServerName = $vmObj.VMServerName # Only run vmconnect.exe if there's a valid VM ID in the filename. if ([System.String]::IsNullOrEmpty($vmId)) { $errMsg = ($MsgTable.GuidNotFoundInFilenameError -f $configFile) $errParams = @{ Category = $ErrorCatInvalidArgument Exception = New-Object -TypeName 'System.ArgumentException' -ArgumentList $errMsg Message = $errMsg TargetObject = $configFile } Write-Error @errParams } #if no VMId. else { $actionString = ($MsgTable.ActionMsgStartingVmconnectExe -f $vmconnectExePath) $targetString = ($MsgTable.TargetMsgStartingVmconnectExe -f $vmName, $vmId, $vmServerName) try { if ($PSCmdlet.ShouldProcess($targetString, $actionString)) { if ((Get-VMEnhancedSessionModeState -Id $vmId -VMServerName $vmServerName -Verbose:$false) -ne 2) { $errMsg = ($MsgTable.EnhancedRdpSessionError -f $vmName, $vmId, $vmServerName) $errParams = @{ Category = $ErrorCatInvalidOperation Exception = New-Object -TypeName 'System.InvalidOperationException' -ArgumentList $errMsg Message = $errMsg } Write-Error @errParams } #if VM can't establish an enhanced RDP session. else { $vmconnectExeArgs = @( "`"$vmServerName`"", "`"$vmName`"", "-G `"$vmId`"", "-C `"$vmconnectExeLaunchCount`"", '/edit' ) $vmconnectExeParams = @{ FilePath = $vmconnectExePath ArgumentList = $vmconnectExeArgs Confirm = $false } Start-Process @vmconnectExeParams $vmconnectExeLaunchCount++ } #else } #ShouldProcess } #try catch { $PSCmdlet.WriteError($_) } #catch } #else } #foreach ($vmObj in $vmObjArray) } # if -ShowEnhancedConnectionDialog else { foreach ($configFile in $validConfigFilePathList) { Write-VMVerbose -FunctionName Edit-VMConnectConfig -Category Reading -Message ($MsgTable.LoadingFileMsg -f $configFile) try { $xmlFile = New-Object -TypeName 'System.Xml.XmlDocument' $xmlFile.Load($configFile) $configFileName = Split-Path -Path $configFile -Leaf $vmName = $xmlFile.SelectSingleNode($VMNameXPath).value $vmNameActionString = $vmName $vmId = ($configFileName | Select-String -Pattern $GuidRegexPattern).Matches.Value if ([System.String]::IsNullOrEmpty($vmId)) { $vmIdErrorMsgString = $null } else { $vmIdErrorMsgString = " ($vmId)" } $vmIdActionString = $null if ([System.String]::IsNullOrEmpty($vmId) -eq $false) { $vmIdActionString = " ($vmId)" } $actionString = ($MsgTable.ActionMsgModifyConfigFile -f $vmNameActionString, $vmIdActionString) if ($PSCmdlet.ShouldProcess($configFile, $actionString)) { $xmlElementListCount = $xmlFile.SelectNodes($XPath).Count $flagChangesMade = $false switch ($configSettingParamsList) { 'AudioCaptureRedirectionMode' { $xmlFile.SelectSingleNode("//setting[@name='AudioCaptureRedirectionMode']").Value = [System.String]$AudioCaptureRedirectionMode $flagChangesMade = $true } 'AudioPlaybackRedirectionMode' { $xmlFile.SelectSingleNode("//setting[@name='AudioPlaybackRedirectionMode']").Value = $AudioPlaybackRedirectionMode $flagChangesMade = $true } 'ClipboardRedirection' { $xmlFile.SelectSingleNode("//setting[@name='ClipboardRedirection']").Value = [System.String]$ClipboardRedirection $flagChangesMade = $true } 'DesktopSize' { $xmlFile.SelectSingleNode("//setting[@name='DesktopSize']").Value = ((New-Object -TypeName 'System.Drawing.SizeConverter').ConvertToString($DesktopSize)) $flagChangesMade = $true } 'EnablePrinterRedirection' { if ($xmlElementListCount -eq 15) { Write-Warning ($MsgTable.UnsupportedConfigurationSettingError -f $configFile, $vmName, $vmIdErrorMsgString, 'EnablePrinterRedirection') } else { $xmlFile.SelectSingleNode("//setting[@name='EnablePrinterRedirection']").Value = [System.String]$EnablePrinterRedirection $flagChangesMade = $true } } 'FullScreen' { $xmlFile.SelectSingleNode("//setting[@name='FullScreen']").Value = [System.String]$FullScreen $flagChangesMade = $true } 'PrinterRedirection' { $xmlFile.SelectSingleNode("//setting[@name='PrinterRedirection']").Value = [System.String]$PrinterRedirection $flagChangesMade = $true } 'RedirectedDrives' { if ([System.String]::IsNullOrEmpty($RedirectedDrives)) { $xmlFile.SelectSingleNode("//setting[@name='RedirectedDrives']/value").IsEmpty = $true } else { $redirectedDrivesString = ($RedirectedDrives -join ';').Trim().ToLower() $xmlFile.SelectSingleNode("//setting[@name='RedirectedDrives']").Value = $redirectedDrivesString } $flagChangesMade = $true } 'RedirectedPnpDevices' { if ([System.String]::IsNullOrEmpty($RedirectedPnpDevices)) { $xmlFile.SelectSingleNode("//setting[@name='RedirectedPnpDevices']/value").IsEmpty = $true } else { $redirectedPnpDevicesString = ($RedirectedPnpDevices -join ';').Trim().ToLower() $xmlFile.SelectSingleNode("//setting[@name='RedirectedPnpDevices']").Value = $redirectedPnpDevicesString } $flagChangesMade = $true } 'RedirectedUsbDevices' { if ([System.String]::IsNullOrEmpty($RedirectedUsbDevices)) { $xmlFile.SelectSingleNode("//setting[@name='RedirectedUsbDevices']/value").IsEmpty = $true } else { $redirectedUsbDevicesString = ($RedirectedUsbDevices -join ';').Trim().ToLower() $xmlFile.SelectSingleNode("//setting[@name='RedirectedUsbDevices']").Value = $redirectedUsbDevicesString } $flagChangesMade = $true } 'SaveButtonChecked' { $xmlFile.SelectSingleNode("//setting[@name='SaveButtonChecked']").Value = [System.String]$SaveButtonChecked $flagChangesMade = $true } 'SmartCardsRedirection' { $xmlFile.SelectSingleNode("//setting[@name='SmartCardsRedirection']").Value = [System.String]$SmartCardsRedirection $flagChangesMade = $true } 'UseAllMonitors' { $xmlFile.SelectSingleNode("//setting[@name='UseAllMonitors']").Value = [System.String]$UseAllMonitors if ($UseAllMonitors) { # If the UseAllMonitors setting is enabled, the FullScreen setting will be automatically enabled. $xmlFile.SelectSingleNode("//setting[@name='FullScreen']").Value = [System.String]$UseAllMonitors } $flagChangesMade = $true } 'WebAuthnRedirection' { if ($xmlElementListCount.Count -lt 17) { Write-Warning ($MsgTable.UnsupportedConfigurationSettingError -f $configFile, $vmName, $vmIdErrorMsgString, 'WebAuthnRedirection') } else { $xmlFile.SelectSingleNode("//setting[@name='WebAuthnRedirection']").Value = [System.String]$WebAuthnRedirection $flagChangesMade = $true } } } #switch ($configSettingParamsList) if ($flagChangesMade) { $xmlWriterSettings = New-Object -TypeName 'System.Xml.XmlWriterSettings' $xmlWriterSettings.Indent = $true # Indent 4x spaces. $xmlWriterSettings.IndentChars = New-Object -TypeName 'System.String' -ArgumentList ' ', 4 $xmlWriterSettings.Encoding = $XmlEncoding $tmpConfigFile = [System.IO.Path]::GetTempFileName() # This throws a terminating error on purpose. $tmpConfigFilePath = (Get-Item -Path $tmpConfigFile -ErrorAction Stop).FullName Write-VMVerbose -FunctionName Edit-VMConnectConfig -Category Writing -Message ($MsgTable.SavingToTempPathMsg -f $tmpConfigFilePath) $xmlWriter = [System.Xml.XmlWriter]::Create($tmpConfigFilePath, $xmlWriterSettings) $xmlFile.Save($xmlWriter) $xmlWriter.Dispose() Write-VMVerbose -FunctionName Edit-VMConnectConfig -Category Writing -Message ($MsgTable.CopyingToTempPathMsg -f $tmpConfigFilePath, $configFile) Copy-Item -LiteralPath $tmpConfigFilePath -Destination $configFile -Force:$true -Confirm:$false } #if .config file was modified and saved. else { Write-VMVerbose -FunctionName Edit-VMConnectConfig -Category Writing -Message ($MsgTable.NoChangesMadeMsg -f $configFile) } if ($PassThru) { $passThruList += $configFile } #if -PassThru } #ShouldProcess } #try catch { $PSCmdlet.WriteError($_) } #catch finally { if ($null -ne $xmlWriter) { $xmlWriter.Dispose() } if ([System.IO.File]::Exists($tmpConfigFilePath)) { $null = Remove-Item -LiteralPath $tmpConfigFilePath -Force:$true -Confirm:$false -ErrorAction SilentlyContinue } } #finally } #foreach ($configFile in $validConfigFilePathList) if ($passThruList.Count -gt 0) { Get-VMConnectConfig -LiteralPath $passThruList } } #else (-ShowEnhancedConnectionDialog NOT used) # Perform garbage collection. [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() } #end } #function Edit-VMConnectConfig |