Public/New-VMConnectConfig.ps1
#.ExternalHelp VMConnectConfig-help.xml function New-VMConnectConfig { [CmdletBinding(HelpURI='https://thegraffix.github.io/VMConnectConfig/new-vmconnectconfig.html', SupportsShouldProcess)] [Alias('nvmc')] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter(Position = 2)] [ValidateNotNullOrEmpty()] [ValidateSet('Legacy', 'Modern', 'WebAuthn')] [System.String]$ConfigFileType, [switch]$Force, [Parameter(Mandatory, ParameterSetName = 'Id')] [ValidateNotNullOrEmpty()] [System.Guid]$Id, [Parameter(Mandatory, Position = 0)] [ValidateNotNullOrEmpty()] [System.String]$Name, [switch]$PassThru, [Parameter(ParameterSetName = 'Id')] [Parameter(Mandatory, ParameterSetName = 'Path')] [ValidateNotNullOrEmpty()] [System.String]$Path, [Parameter(Position = 1)] [System.String]$VMServerName ) 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'} if ($PSBoundParameters.ContainsKey('ConfigFileType') -eq $false) { $osVersion = [System.Version](Get-CimInstance -ClassName 'Win32_OperatingSystem' -Property Version -Verbose:$false).Version if ($osVersion.Major -eq 10) { $ConfigFileType = 'Modern' if ($osVersion.Build -ge 22000) { $ConfigFileType = 'WebAuthn' } } else { $ConfigFileType = 'Legacy' } } #if -ConfigFileType not specified } #begin process { # Nothing to do here because there's no pipeline input to process. Future releases might implement pipeline input here. } #process end { $newConfigFileDesktopSize = $null if (Test-Path -Path $ClientSettingsConfigFile -PathType Leaf) { try { $clientSettingsXmlFile = New-Object -TypeName 'System.Xml.XmlDocument' $clientSettingsXmlFile.Load($ClientSettingsConfigFile) $newConfigFileDesktopSize = $clientSettingsXmlFile.SelectSingleNode("//setting[@name='DesktopSize']").value } #try catch {} } if ([System.String]::IsNullOrEmpty($newConfigFileDesktopSize)) { Write-VMVerbose -FunctionName New-VMConnectConfig -Category Error -Message ($MsgTable.ClientSettingsFileDesktopSizeError -f $ClientSettingsConfigFile, $DefaultDesktopSize) $newConfigFileDesktopSize = $DefaultDesktopSize } try { switch ($PSCmdlet.ParameterSetName) { 'Id' { if ($PSBoundParameters.ContainsKey('Path')) { # -Id and -Path were used, so $Path must be a path to a container, not an item. if (Test-Path -Path $Path -PathType Container) { $childPath = ($VMConnectConfigFilenamePattern -f $Id.ToString()) $unresolvedConfigFilePath = Join-Path -Path $Path -ChildPath $childPath } else { $exMsg = ($MsgTable.DirectoryNotFoundError -f $Path) $ex = New-Object 'System.IO.DirectoryNotFoundException' -ArgumentList $exMsg throw $ex } } else { # Throw an exception if no -Path is specified && the "Hyper-V\Client\1.0" directory does not exist. $null = Test-VMConfigFolder # Name the file "vmconnect.rdp.$Id.config" and save it in the "Hyper-V\Client\1.0" directory since -Path wasn't specified. $childPath = ($VMConnectConfigFilenamePattern -f $Id.ToString()) $unresolvedConfigFilePath = Join-Path -Path $VMConfigFolder -ChildPath $childPath } break } 'Path' { $unresolvedConfigFilePath = $Path break } } $configFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($unresolvedConfigFilePath) if ([System.IO.Path]::GetExtension($configFilePath) -ne '.config') { $errMsg = ($MsgTable.InvalidConfigFileExtensionError -f $configFilePath) $errParams = @{ Category = $ErrorCatInvalidArgument Exception = New-Object -TypeName 'System.ArgumentException' -ArgumentList $errMsg Message = $errMsg TargetObject = $configFilePath } Write-Error @errParams return } # Below is to accommodate cases where Reset-VMConnectConfig retrieves an empty string/$null value from the .config file's VMServerName element content and # ends up passing an empty string/$null argument to "New-VMConnectConfig -VMServerName $null". # - The $VMServerName parameter should therefor accept empty string/$null values. # - Since the $VMServerName parameter can accept empty string/$null arguments, a default value of $HostName is assigned here if $VMServerName IsNullOrEmpty() # rather than assigning the parameter's default value in the param() block. if ([System.String]::IsNullOrEmpty($VMServerName)) { $VMServerName = $HostName Write-VMVerbose -FunctionName New-VMConnectConfig -Category Warning -Message ($MsgTable.UsingHostNameForVmServerName -f $HostName) } switch ($ConfigFileType) { 'Legacy' { $newConfigFileXml = ($LegacyConfigFileXml -f $newConfigFileDesktopSize, $VMServerName, $Name) break } 'Modern' { $newConfigFileXml = ($ModernConfigFileXml -f $newConfigFileDesktopSize, $VMServerName, $Name) break } 'WebAuthn' { $newConfigFileXml = ($WebAuthnConfigFileXml -f $newConfigFileDesktopSize, $VMServerName, $Name) break } } #switch ($ConfigFileType) } #try catch { $PSCmdlet.WriteError($_) return } #catch $actionVmId = $null $vmId = $null $vmId = (Split-Path -Path $configFilePath -Leaf | Select-String -Pattern $GuidRegexPattern).Matches.Value if ([System.String]::IsNullOrEmpty($vmId) -eq $false) { $actionVmId = " ($vmId)" } $actionString = ($MsgTable.ActionMsgCreateConfigFile -f $Name, $actionVmId) if (($Force) -or ($PSCmdlet.ShouldProcess($configFilePath, $actionString))) { try { Write-VMVerbose -FunctionName New-VMConnectConfig -Category Constructing -Message ($MsgTable.CreatingConfigFileMsg -f $Name, $actionVmId) $newConfigFile = New-Object -TypeName 'System.Xml.XmlDocument' $newConfigFile.LoadXml($newConfigFileXml) $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. Using a tmp file helps to avoid corrupting the original .config file should the write operation fail and result in corrupted data. $tmpConfigFilePath = (Get-Item -Path $tmpConfigFile -ErrorAction Stop).FullName Write-VMVerbose -FunctionName New-VMConnectConfig -Category Writing -Message ($MsgTable.SavingToTempPathMsg -f $tmpConfigFilePath) $xmlWriter = [System.Xml.XmlWriter]::Create($tmpConfigFilePath, $xmlWriterSettings) $newConfigFile.Save($xmlWriter) $xmlWriter.Dispose() Write-VMVerbose -FunctionName New-VMConnectConfig -Category Writing -Message ($MsgTable.CopyingToTempPathMsg -f $tmpConfigFilePath, $configFilePath) # Using Copy() instead of Copy-Item because System.IO exception messages are more descriptive and helpful compared to Copy-Item error messages. [System.IO.File]::Copy($tmpConfigFilePath, $configFilePath, $Force) if ($PassThru) { Write-VMVerbose -FunctionName New-VMConnectConfig -Category Constructing -Message ($MsgTable.ConstructingOutputObjMsg -f $configFilePath) Get-VMConnectConfig -LiteralPath $configFilePath } } #try catch { $PSCmdlet.WriteError($_) return } #catch finally { if ($xmlWriter) { $xmlWriter.Dispose() } if ([System.IO.File]::Exists($tmpConfigFilePath)) { $null = Remove-Item -LiteralPath $tmpConfigFilePath -Force:$true -Confirm:$false -ErrorAction SilentlyContinue } } #finally } #ShouldProcess # Perform garbage collection. [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() } #end } #function New-VMConnectConfig |