Public/Get-VMConnectConfig.ps1

#.ExternalHelp VMConnectConfig-help.xml
function Get-VMConnectConfig
{
    [CmdletBinding(DefaultParameterSetName = 'EmptyParamSet', HelpURI='https://thegraffix.github.io/VMConnectConfig/get-vmconnectconfig.html')]
    [Alias('gvmc')]
    [OutputType([System.Management.Automation.PSObject[]])]
    param (
        [Parameter(Mandatory, ParameterSetName = 'All')]
        [switch]$All,

        [Parameter(Mandatory, ParameterSetName = 'Deleted')]
        [switch]$Deleted,

        [Parameter(Mandatory, ParameterSetName = 'Id', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('VMId')]
        [ValidateNotNullOrEmpty()]
        [System.Guid[]]$Id,

        [Parameter(Mandatory, ParameterSetName = 'LiteralPath', ValueFromPipelineByPropertyName)]
        [Alias('FullName')]
        [ValidateNotNullOrEmpty()]
        [System.String[]]$LiteralPath,

        [Parameter(Mandatory, ParameterSetName = 'Name', Position = 0, ValueFromPipeline)]
        [SupportsWildcards()]
        [ValidateNotNullOrEmpty()]
        [System.String[]]$Name,

        [Parameter(Mandatory, ParameterSetName = 'Path')]
        [SupportsWildcards()]
        [ValidateNotNullOrEmpty()]
        [System.String[]]$Path
    )

    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'}

        $cimCaption = 'Virtual Machine'
        $cimClassName = 'Msvm_ComputerSystem'
        $cimNamespace = 'root/virtualization/v2'
        $cimPropertyList = @('Caption', 'ElementName', 'EnhancedSessionModeState', 'Name', 'StatusDescriptions')
        $cimVmVersionClassName = 'Msvm_VirtualSystemSettingData'
        [System.IO.FileInfo[]]$configFileInfoList = @()
        $legacyVMConnectConfigTypeDisplayPropertySet = @('Name',
                                                         'Id',
                                                         'Path',
                                                         'DateModified',
                                                         'VMExists',
                                                         'VMVersion',
                                                         'AudioCaptureRedirectionMode',
                                                         'AudioPlaybackRedirectionMode',
                                                         'ClipboardRedirection',
                                                         'DesktopSize',
                                                         'FullScreen',
                                                         'PrinterRedirection',
                                                         'RedirectedDrives',
                                                         'RedirectedPnpDevices',
                                                         'RedirectedUsbDevices',
                                                         'SaveButtonChecked',
                                                         'SmartCardsRedirection',
                                                         'UseAllMonitors',
                                                         'VMServerName'
        )
        $modernVMConnectConfigTypeDisplayPropertySet = @('Name',
                                                         'Id',
                                                         'Path',
                                                         'DateModified',
                                                         'VMExists',
                                                         'VMVersion',
                                                         'AudioCaptureRedirectionMode',
                                                         'AudioPlaybackRedirectionMode',
                                                         'ClipboardRedirection',
                                                         'DesktopSize',
                                                         'EnablePrinterRedirection',
                                                         'FullScreen',
                                                         'PrinterRedirection',
                                                         'RedirectedDrives',
                                                         'RedirectedPnpDevices',
                                                         'RedirectedUsbDevices',
                                                         'SaveButtonChecked',
                                                         'SmartCardsRedirection',
                                                         'UseAllMonitors',
                                                         'VMServerName'
        )
        $webAuthnVMConnectConfigTypeDisplayPropertySet = @('Name',
                                                         'Id',
                                                         'Path',
                                                         'DateModified',
                                                         'VMExists',
                                                         'VMVersion',
                                                         'AudioCaptureRedirectionMode',
                                                         'AudioPlaybackRedirectionMode',
                                                         'ClipboardRedirection',
                                                         'DesktopSize',
                                                         'EnablePrinterRedirection',
                                                         'FullScreen',
                                                         'PrinterRedirection',
                                                         'RedirectedDrives',
                                                         'RedirectedPnpDevices',
                                                         'RedirectedUsbDevices',
                                                         'SaveButtonChecked',
                                                         'SmartCardsRedirection',
                                                         'UseAllMonitors',
                                                         'VMServerName',
                                                         'WebAuthnRedirection'
        )
        $vmHostObjArray = @()
    } #begin

    process
    {
        if ($PSCmdlet.ParameterSetName -notlike '*Path')
        {
            $null = Test-VMConfigFolder
            $allConfigFiles = Get-AllConfigFiles
        }

        switch ($PSCmdlet.ParameterSetName)
        {
            {$_ -eq 'All' -or $_ -eq 'Deleted' -or $_ -eq 'EmptyParamSet'}
            {
                $configFileInfoList = $allConfigFiles
                break
            } # -All or -Deleted or <no parameters used>.

            'Id'
            {
                foreach ($vmGuid in $Id.Guid)
                {
                    $files = @()
                    $files = ($allConfigFiles | Where-Object {$_.Name -match "vmconnect\.rdp\.$vmGuid.*\.config"})

                    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)
                        {
                            $configFileInfoList += $file
                        }
                    }
                } #foreach $vmGuid

                break
            } # -Id

            'LiteralPath'
            {
                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)
                        {
                            $configFileInfoList += [System.IO.FileInfo]$resolvedLiteralPath
                        }
                    }
                } #foreach $literalItem

                break
            } # -LiteralPath

            'Name'
            {
                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)
                        {
                            $configFileInfoList += [System.IO.FileInfo]$file
                        }
                    }
                } #foreach $vmName

                break
            } # -Name

            'Path'
            {
                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)
                        {
                            $configFileInfoList += [System.IO.FileInfo]$resolvedPath
                        }
                    }
                } #foreach ($item in $Path)

                break
            } # -Path
        } #switch ($PSCmdlet.ParameterSetName)
    } #process

    end
    {
        $configFileInfoList = $configFileInfoList | Select-Object -Unique

        $vmHostNameList = @()
        [System.IO.FileInfo[]]$validConfigFileInfoList = @()

        foreach ($configFile in $configFileInfoList)
        {
            $configFilePath = $configFile.FullName

            if ((Test-VMConnectConfig -LiteralPath $configFilePath) -eq $false)
            {
                $errMsg = ($MsgTable.InvalidConfigFileError -f $configFilePath)

                $errParams = @{
                    Category = $ErrorCatInvalidArgument
                    Exception = New-Object -TypeName 'System.ArgumentException' -ArgumentList $errMsg
                    Message = $errMsg
                    TargetObject = $configFilePath
                }

                Write-Error @errParams
                continue
            }

            $validConfigFileInfoList += $configFile

            try
            {
                $xmlFile = New-Object -TypeName 'System.Xml.XmlDocument'
                $xmlFile.Load($configFilePath)
                [System.String]$vmServerNameValue = $xmlFile.SelectSingleNode($VMServerNameXPath).value

                if (([System.String]::IsNullOrEmpty($vmServerNameValue) -eq $false) -and
                    ($vmServerNameValue -inotin $vmHostNameList) -and
                    ([System.String]::IsNullOrEmpty(($configFile.Name | Select-String -Pattern $GuidRegexPattern).Matches.Value) -eq $false)
                )
                {
                    # Only filenames with valid GUIDS should have their VMServerName value added to $vmHostNameList.
                    $vmHostNameList += $vmServerNameValue
                }
            } #try
            catch
            {
                continue
            } #catch
        } #foreach ($configFile in $configFileInfoList)

        [System.UInt64]$freeMemoryBytes = (Get-CimInstance -ClassName Win32_OperatingSystem -Verbose:$false).FreePhysicalMemory * 1kb

        # Maximum number of parallel runspaces is calculated by: 25% of free memory / 100 MB
        # Why 100 MB? Because under average circumstances a single PowerShell runspace is ~100 MB or less.
        [System.Int32]$runspacePoolMax = ($freeMemoryBytes * 0.25) / 100mb

        if ($runspacePoolMax -lt 1)
        {
            $runspacePoolMax = 1
        }

        $runspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(1, $runspacePoolMax)
        $runspacePool.Open()

        $tcScriptBlock = {
            param($tcHostName)

            if ($tcHostName -eq $HostName)
            {
                $testConnectionResult = $true
            }
            else
            {
                $tcParams = @{
                    ComputerName = $tcHostName
                    Count = 1
                    Quiet = $true
                }

                $testConnectionResult = Test-Connection @tcParams
            }

            if ($testConnectionResult -eq $true)
            {
                # Create parallel CIM session(s).
                $cimSessionOption = New-CimSessionOption -Protocol Wsman

                $newCimSessionParam = @{
                    ComputerName = $tcHostName
                    ErrorAction = 'Stop'
                    SessionOption = $cimSessionOption
                }

                try
                {
                    $cimErrorMessage = $null
                    $tcCimSession = New-CimSession @newCimSessionParam
                } #try
                catch
                {
                    try
                    {
                        # Try DCOM if WSMan protocol fails.
                        $newCimSessionParam.SessionOption = New-CimSessionOption -Protocol Dcom
                        $tcCimSession = New-CimSession @newCimSessionParam
                    } #try
                    catch
                    {
                        # Save the error message to the psobject.
                        $cimErrorMessage = $_.Exception.Message
                    } #catch
                } #catch
            } #if $tcHostName is ONLINE.

            [pscustomobject] @{
                TestConnectionHostName = $tcHostName
                TestConnectionResult = $testConnectionResult
                CimSessionObject = $tcCimSession
                CimErrorMessage = $cimErrorMessage
            }

            # Perform garbage collection.
            [System.GC]::Collect()
            [System.GC]::WaitForPendingFinalizers()
        } #$tcScriptBlock

        $runspaces = foreach ($vmHostName in $vmHostNameList)
        {
            # Only display "Testing connection..." message if there's more than 1x VmHostName || there's only 1x and it's not the local computer ($env:ComputerName).
            if ($vmHostNameList.Count -gt 1 -or ($vmHostNameList.Count -eq 1 -and $vmHostNameList[0] -ne $HostName))
            {
                Write-VMVerbose -FunctionName Get-VMConnectConfig -Category Testing -Message ($MsgTable.TestingConnectionMsg -f $vmHostName)
            }

            $rsParams = @{
                tcHostName = $vmHostName
            }

            $psInstance = [System.Management.Automation.PowerShell]::Create().AddScript($tcScriptBlock).AddParameters($rsParams)
            $psInstance.RunspacePool = $runspacePool

            [pscustomobject] @{
                Instance = $psInstance
                Status = $psInstance.BeginInvoke()
            }
        } #$runspaces

        # Display either singular or plural "Waiting for connection test/tests" verbose message (depending on the value of $vmHostNameList.Count).
        if ($vmHostNameList.Count -gt 1)
        {
            Write-VMVerbose -FunctionName Get-VMConnectConfig -Category Testing -Message ($MsgTable.TestingConnectionWaitingParallelMsg)
        }
        elseif ($vmHostNameList.Count -eq 1 -and $vmHostNameList[0] -ne $HostName)
        {
            Write-VMVerbose -FunctionName Get-VMConnectConfig -Category Testing -Message ($MsgTable.TestingConnectionWaitingMsg)
        }

        # Wait for runspaces to finish running.
        while ($runspaces.Status.IsCompleted -contains $false)
        {
            Start-Sleep -Milliseconds 100
        }

        foreach ($rs in $runspaces)
        {
            $connectionResult = $rs.Instance.EndInvoke($rs.Status)
            $null = $rs.Instance.Dispose()

            $vmHostComputerName = $connectionResult.TestConnectionHostName
            $cimSessionObject = $connectionResult.CimSessionObject
            $cimErrMsg = $connectionResult.CimErrorMessage

            if ($connectionResult.TestConnectionResult -eq $true)
            {
                $vmHostStatus = 'Online'
                Write-VMVerbose -FunctionName Get-VMConnectConfig -Category Testing -Message ($MsgTable.ConnectionSuccessMsg -f $vmHostComputerName)
                Write-VMVerbose -FunctionName Get-VMConnectConfig -Category Constructing -Message ($MsgTable.CreatingCimSessionMsg -f $vmHostComputerName)
            }
            else
            {
                $vmHostStatus = 'Offline'
                Write-VMVerbose -FunctionName Get-VMConnectConfig -Category Warning -Message ($MsgTable.ConnectionError -f $vmHostComputerName)
            }

            # Add the VMHostName and the Test-Connection result to [psobject[]]$vmHostObjArray.
            $vmHostObjArray += [pscustomobject] @{
                VMHostName = $vmHostComputerName
                ConnectionStatus = $vmHostStatus
                CimSessionObject = $cimSessionObject
                CimErrorMsg = $cimErrMsg
            }
        } #foreach runspace

        foreach ($configFile in $validConfigFileInfoList)
        {
            $cimErrorMessage = $null
            $cimVmObject = $null
            $configFilePath = $configFile.FullName
            $currentRunspaceObject = $null
            $errMsg = $null
            $errParams = $null
            $vmccObjErrorReason = $null

            try
            {
                $xmlFile = New-Object -TypeName 'System.Xml.XmlDocument'
                $xmlFile.Load($configFilePath)
                $xmlElementList = $xmlFile.SelectNodes($XPath)

                [System.Object]$vmccObjVmExists = $null
                [System.Object]$vmccObjVmId = $null
                [System.String]$vmccObjVmName = $xmlFile.SelectSingleNode($VMNameXPath).value
                [System.String]$vmccObjVmServerName = $xmlFile.SelectSingleNode($VMServerNameXPath).value
                [System.Object]$vmccObjVmVersion = $null

                $vmIdString = ($configFile.Name | Select-String -Pattern $GuidRegexPattern).Matches.Value

                if ([System.String]::IsNullOrEmpty($vmIdString) -eq $false)
                {
                    [System.Guid]$vmccObjVmId = $vmIdString
                    $vmIdErrorString = " ($vmIdString)"
                }

                $currentRunspaceObject = $vmHostObjArray | Where-Object {$_.VMHostName -eq $vmccObjVmServerName}

                if ($null -ne $vmccObjVmId)
                {
                    if ($currentRunspaceObject.ConnectionStatus -eq 'Online')
                    {
                        $cimSessionObj = $currentRunspaceObject.CimSessionObject
                        $cimErrorMessage = $currentRunspaceObject.CimErrorMsg

                        if ($null -ne $currentRunspaceObject.CimSessionObject)
                        {
                            $cimVmObjParams = @{
                                CimSession = $cimSessionObj
                                ClassName = $cimClassName
                                ErrorAction = 'Stop'
                                Filter = "Caption='$cimCaption' AND ElementName='$vmccObjVmName' AND Name='$vmccObjVmId'"
                                NameSpace = $cimNamespace
                                Property = $cimPropertyList
                                Verbose = $false
                            }

                            $cimVmVersionParams = @{
                                CimSession = $cimSessionObj
                                ClassName = $cimVmVersionClassName
                                ErrorAction = 'Stop'
                                Filter = "ElementName='$vmccObjVmName' AND VirtualSystemIdentifier='$vmccObjVmId'"
                                NameSpace = $cimNamespace
                                Property = 'Version'
                                Verbose = $false
                            }

                            try
                            {
                                $cimVmObject = Get-CimInstance @cimVmObjParams
                                $cimVmVersionObject = Get-CimInstance @cimVmVersionParams

                                if ($null -ne $cimVmObject)
                                {
                                    $vmccObjVmExists = $true
                                }
                                else
                                {
                                    $vmccObjVmExists = $false
                                    $vmccObjErrorReason = ($MsgTable.VmNotFoundError -f 'Get-VMConnectConfig', $vmccObjVmName, $vmIdErrorString, $vmccObjVmServerName)
                                }

                                if ($null -ne $cimVmVersionObject.Version)
                                {
                                    [System.Version]$vmccObjVmVersion = ($cimVmVersionObject).Version
                                }
                                else
                                {
                                    $statusDescriptions = $cimVmObject.StatusDescriptions

                                    if (($statusDescriptions.Count -gt 1) -and ([System.String]::IsNullOrEmpty($statusDescriptions[1]) -eq $false))
                                    {
                                        # If there are multiple status descriptions, the secondary status description ($statusDescription[1]) reflects the most critical status description.
                                        # Also, ensure that the status description ends with a period.
                                        $vmccObjErrorReason = ($statusDescriptions[1] -replace '\.$') + "."
                                    }
                                    elseif ($statusDescriptions.Count -ne 0)
                                    {
                                        $vmccObjErrorReason = ($statusDescriptions[0] -replace '\.$') + "."
                                    }
                                    elseif ($vmccObjVmExists)
                                    {
                                        $vmccObjErrorReason = $MsgTable.UnknownError
                                    }
                                }
                            } #try
                            catch
                            {
                                # If the WMI namespace "ROOT/virtualization/v2" doesn't exist, $_.Exception.NativeErrorCode will be "InvalidNameSpace".
                                # The presence of that error code should be sufficient for determining if a computer has the Hyper-V role/feature installed.
                                $ex = $_

                                if ($ex.Exception.NativeErrorCode -eq 'InvalidNameSpace')
                                {
                                    $vmccObjErrorReason = ($MsgTable.VmHostHyperVRoleError -f $vmccObjVmServerName)
                                }
                                else
                                {
                                    $vmccObjErrorReason = $ex
                                }
                            } #catch
                        } # if no Cim error message.
                        elseif ([System.String]::IsNullOrEmpty($cimErrorMessage) -eq $false)
                        {
                            $vmccObjErrorReason = $cimErrorMessage
                        }
                        else
                        {
                            $vmccObjErrorReason = ($MsgTable.CimSessionError -f $vmccObjVmServerName)
                        }
                    } #if $vmccObjVmServerName is ONLINE.
                    elseif ($currentRunspaceObject.ConnectionStatus -eq 'Offline')
                    {
                        $vmccObjErrorReason = ($MsgTable.ConnectionError -f $vmccObjVmServerName)
                    } #if $vmccObjVmServerName is OFFLINE.
                } #if valid GUID found in filename.
                else
                {
                    $vmccObjErrorReason = ($MsgTable.GuidNotFoundInFilenameError -f ($configFile.Name))
                }

                # If the .VMExists property is $false, the .config file's VMServerName was successfully queried for that VM -- and the VM was not found; only these VMs are included in the -Deleted param set.
                # If the .VMExists property is $true, the .config file's VMServerName was successfully queried for that VM -- and the VM was found; these VMs are included in the default <empty param set>.
                # If the .VMExists property is $null, the .config file's VMServerName was not able to be connected to or queried; these VMs are also included in the default <empty param set>.
                if ($PSCmdlet.ParameterSetName -eq 'Deleted' -and $vmccObjVmExists -ne $false)
                {
                    continue
                }
                elseif ($PSCmdlet.ParameterSetName -eq 'EmptyParamSet' -and $vmccObjVmExists -eq $false)
                {
                    continue
                } # <EmptyParamSet>

                if ([System.String]::IsNullOrEmpty($vmccObjVmServerName) -and [System.String]::IsNullOrEmpty($vmccObjErrorReason))
                {
                    $vmccObjErrorReason = ($MsgTable.VmServerNameNotFound -f $configFilePath)
                }

                if ([System.String]::IsNullOrEmpty($vmccObjErrorReason) -eq $false)
                {
                    if ($null -ne $vmccObjVmId)
                    {
                        $vmIdErrorString = " ($vmccObjVmId)"
                    }

                    if ([System.String]::IsNullOrEmpty($vmIdString))
                    {
                        Write-VMVerbose -FunctionName Get-VMConnectConfig -Category Warning -Message ($MsgTable.FilenameGuidUndefinedError -f $vmccObjVmName, $vmccObjErrorReason)
                        # The .Id property is used when querying the VMServerName, and the results of that query determine the values of the VMExists and VMVersion properties.
                        # If the .Id property is missing/null then there will be no way to determine the .VMExists and .VMVersion properties.
                        $vmccObjErrorReason = ($MsgTable.VmIdNeededToCalculatePropertiesError)
                    }

                    if (($null -eq $vmccObjVmExists) -and ($null -eq $vmccObjVmVersion))
                    {
                        if ([System.String]::IsNullOrEmpty($vmccObjVmServerName))
                        {
                            Write-VMVerbose -FunctionName Get-VMConnectConfig -Category Warning -Message ($MsgTable.VmExistsAndVmVersionAndVmServerNameUndefinedError -f $vmccObjVmName, $vmIdErrorString, $vmccObjErrorReason)
                        }
                        else
                        {
                            Write-VMVerbose -FunctionName Get-VMConnectConfig -Category Warning -Message ($MsgTable.VmExistsAndVmVersionUndefinedError -f $vmccObjVmName, $vmIdErrorString, $vmccObjVmServerName, $vmccObjErrorReason)
                        }
                    }
                    elseif ($null -eq $vmccObjVmExists)
                    {
                        Write-VMVerbose -FunctionName Get-VMConnectConfig -Category Warning -Message ($MsgTable.VmExistsUndefinedError -f $vmccObjVmName, $vmIdErrorString, $vmccObjErrorReason)
                    }
                    elseif ($null -eq $vmccObjVmVersion)
                    {
                        Write-VMVerbose -FunctionName Get-VMConnectConfig -Category Warning -Message ($MsgTable.VmVersionUndefinedError -f $vmccObjVmName, $vmIdErrorString, $vmccObjErrorReason)
                    }
                }

                $vmConnectConfigObj = @{}
                $vmConnectConfigObj.Add('PSTypeName', 'VMConnectConfig')
                $vmConnectConfigObj.Add('Name', $vmccObjVmName)
                $vmConnectConfigObj.Add('Id', $vmccObjVmId)
                $vmConnectConfigObj.Add('Path', $configFilePath)
                $vmConnectConfigObj.Add('DateModified', ($configFile.LastWriteTime))
                $vmConnectConfigObj.Add('VMExists', $vmccObjVmExists)
                $vmConnectConfigObj.Add('VMVersion', $vmccObjVmVersion)

                [System.Boolean]$vmccObjAudioCaptureRedirectionMode = [System.Convert]::ToBoolean($xmlFile.SelectSingleNode("//setting[@name='AudioCaptureRedirectionMode']").value)
                $vmConnectConfigObj.Add('AudioCaptureRedirectionMode', $vmccObjAudioCaptureRedirectionMode)

                [System.String]$vmccObjAudioPlaybackRedirectionMode = $xmlFile.SelectSingleNode("//setting[@name='AudioPlaybackRedirectionMode']").value
                $vmConnectConfigObj.Add('AudioPlaybackRedirectionMode', $vmccObjAudioPlaybackRedirectionMode)

                [System.Boolean]$vmccObjClipboardRedirection = [System.Convert]::ToBoolean($xmlFile.SelectSingleNode("//setting[@name='ClipboardRedirection']").value)
                $vmConnectConfigObj.Add('ClipboardRedirection', $vmccObjClipboardRedirection)

                [System.Drawing.Size]$vmccObjDesktopSize = $xmlFile.SelectSingleNode("//setting[@name='DesktopSize']").value
                $vmConnectConfigObj.Add('DesktopSize', $vmccObjDesktopSize)

                if ($xmlElementList.Count -gt 15)
                {
                    [System.Boolean]$vmccObjEnablePrinterRedirection = [System.Convert]::ToBoolean($xmlFile.SelectSingleNode("//setting[@name='EnablePrinterRedirection']").value)
                    $vmConnectConfigObj.Add('EnablePrinterRedirection', $vmccObjEnablePrinterRedirection)
                }

                [System.Boolean]$vmccObjFullScreen = [System.Convert]::ToBoolean($xmlFile.SelectSingleNode("//setting[@name='FullScreen']").value)
                $vmConnectConfigObj.Add('FullScreen', $vmccObjFullScreen)

                [System.Boolean]$vmccObjPrinterRedirection = [System.Convert]::ToBoolean($xmlFile.SelectSingleNode("//setting[@name='PrinterRedirection']").value)
                $vmConnectConfigObj.Add('PrinterRedirection', $vmccObjPrinterRedirection)

                [System.String]$vmccObjRedirectedDrives = $xmlFile.SelectSingleNode("//setting[@name='RedirectedDrives']").value
                $vmConnectConfigObj.Add('RedirectedDrives', $vmccObjRedirectedDrives)

                [System.String]$vmccObjRedirectedPnpDevices = $xmlFile.SelectSingleNode("//setting[@name='RedirectedPnpDevices']").value
                $vmConnectConfigObj.Add('RedirectedPnpDevices', $vmccObjRedirectedPnpDevices)

                [System.String]$vmccObjRedirectedUsbDevices = $xmlFile.SelectSingleNode("//setting[@name='RedirectedUsbDevices']").value
                $vmConnectConfigObj.Add('RedirectedUsbDevices', $vmccObjRedirectedUsbDevices)

                [System.Boolean]$vmccObjSaveButtonChecked = [System.Convert]::ToBoolean($xmlFile.SelectSingleNode("//setting[@name='SaveButtonChecked']").value)
                $vmConnectConfigObj.Add('SaveButtonChecked', $vmccObjSaveButtonChecked)

                [System.Boolean]$vmccObjSmartCardsRedirection = [System.Convert]::ToBoolean($xmlFile.SelectSingleNode("//setting[@name='SmartCardsRedirection']").value)
                $vmConnectConfigObj.Add('SmartCardsRedirection', $vmccObjSmartCardsRedirection)

                [System.Boolean]$vmccObjUseAllMonitors = [System.Convert]::ToBoolean($xmlFile.SelectSingleNode("//setting[@name='UseAllMonitors']").value)
                $vmConnectConfigObj.Add('UseAllMonitors', $vmccObjUseAllMonitors)

                $vmConnectConfigObj.Add('VMServerName', $vmccObjVmServerName)

                if ($xmlElementList.Count -eq 17)
                {
                    [System.Boolean]$vmccObjWebAuthnRedirection = [System.Convert]::ToBoolean($xmlFile.SelectSingleNode("//setting[@name='WebAuthnRedirection']").value)
                    $vmConnectConfigObj.Add('WebAuthnRedirection', $vmccObjWebAuthnRedirection)
                }

                $vmConnectConfigObj = [pscustomobject]$vmConnectConfigObj

                switch ($xmlElementList.Count)
                {
                    15
                    {
                        $null = Update-TypeData -TypeName 'VMConnectConfig' -DefaultDisplayPropertySet $legacyVMConnectConfigTypeDisplayPropertySet -Force -Confirm:$false -WhatIf:$false
                        break
                    }

                    16
                    {
                        $null = Update-TypeData -TypeName 'VMConnectConfig' -DefaultDisplayPropertySet $modernVMConnectConfigTypeDisplayPropertySet -Force -Confirm:$false -WhatIf:$false
                        break
                    }

                    17
                    {
                        $null = Update-TypeData -TypeName 'VMConnectConfig' -DefaultDisplayPropertySet $webAuthnVMConnectConfigTypeDisplayPropertySet -Force -Confirm:$false -WhatIf:$false
                        break
                    }

                } #switch ($xmlElementList.Count)

                Write-Output $vmConnectConfigObj
            } #try
            catch
            {
                $PSCmdlet.WriteError($_)
            } #catch
        } #foreach ($configFile in $configFileInfoList)

        # Perform garbage collection.
        [System.GC]::Collect()
        [System.GC]::WaitForPendingFinalizers()
    } #end
} #function Get-VMConnectConfig