Public/Test-VMConnectConfig.ps1

#.ExternalHelp VMConnectConfig-help.xml
function Test-VMConnectConfig
{
    [CmdletBinding(DefaultParameterSetName = 'Path', HelpURI='https://thegraffix.github.io/VMConnectConfig/test-vmconnectconfig.html')]
    [Alias('tvmc')]
    [OutputType([System.Boolean])]
    param (
        [Parameter(Mandatory, ParameterSetName = 'LiteralPath', ValueFromPipelineByPropertyName)]
        [Alias('FullName')]
        [ValidateNotNullOrEmpty()]
        [System.String[]]$LiteralPath,

        [Parameter(Mandatory, ParameterSetName = 'Path', Position = 0)]
        [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'}
    } #begin

    process
    {
        [System.String[]]$configFilePathList = @()

        switch ($PSCmdlet.ParameterSetName)
        {
            '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)

                        Write-VMVerbose -FunctionName Test-VMConnectConfig -Category Error -Message ($MsgTable.ConfigFileNotFoundPathError -f $resolvedLiteralItemPath)
                        Write-Output $false
                    }
                    elseif ([System.String]::IsNullOrEmpty($resolvedLiteralpaths))
                    {
                        Write-Output $false
                    }
                    else
                    {
                        foreach ($resolvedLiteralPath in $resolvedLiteralPaths)
                        {
                            $configFilePathList += $resolvedLiteralPath
                        }
                    }
                } #foreach

                break
            } # -LiteralPath

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

                        Write-VMVerbose -FunctionName Test-VMConnectConfig -Category Error -Message ($MsgTable.ConfigFileNotFoundPathError -f $resolvedItemPath)
                        Write-Output $false
                    }
                    elseif (($resolvedPaths.Count -eq 0) -or ([System.String]::IsNullOrEmpty($resolvedPaths)))
                    {
                        Write-Output $false
                    }
                    else
                    {
                        $resolvedPaths | ForEach-Object {$configFilePathList += $_}
                    }
                } #foreach

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

        foreach ($file in $configFilePathList)
        {
            $xmlFile = $null
            $xmlSchema = $null
            $xmlStrReader = $null
            $xmlNodeList = $null
            $verboseErrorMsg = $null
            $configFileIsValid = $false

            if ([System.IO.Path]::GetExtension($file) -ne '.config')
            {
                Write-VMVerbose -FunctionName Test-VMConnectConfig -Category Error -Message ($MsgTable.InvalidConfigFileExtensionError -f $file)
                Write-Output $false
                continue
            }

            try
            {
                $xmlFile = New-Object -TypeName 'System.Xml.XmlDocument'

                # Test if $file is a well-formed XML document.
                Write-VMVerbose -FunctionName Test-VMConnectConfig -Category Reading -Message ($MsgTable.LoadingFileMsg -f $file)
                $xmlFile.Load($file)

                # Validate $file against XML schema.
                Write-VMVerbose -FunctionName Test-VMConnectConfig -Category Testing -Message ($MsgTable.XmlSchemaValidatingFileMsg -f $file)
                $xmlStrReader = New-Object -TypeName 'System.IO.StringReader' -ArgumentList $VMConnectConfigXmlSchema
                $xmlSchema = [System.Xml.Schema.XmlSchema]::Read($xmlStrReader, $null)
                $null = $xmlFile.Schemas.Add($xmlSchema)
                $xmlFile.Validate($null)

                $xmlNodeList = ($xmlFile.SelectNodes($XPath)).Name

                $diffObj = $null
                if ($xmlNodeList.Count -eq 15)
                {
                    $diffObj = $LegacySettingsList
                }
                elseif ($xmlNodeList.Count -eq 16)
                {
                    $diffObj = $ModernSettingsList
                }
                elseif ($xmlNodeList.Count -eq 17)
                {
                    $diffObj = $WebAuthnSettingsList
                }

                if ($null -ne (Compare-Object -ReferenceObject $xmlNodeList -DifferenceObject $diffObj))
                {
                    $verboseErrorMsg = ($MsgTable.InvalidXmlElementError -f $file)
                }
                else
                {
                    $configFileIsValid = $true
                }
            } #try
            catch [System.IO.FileNotFoundException], [System.IO.DirectoryNotFoundException]
            {
                $errMsg = ($MsgTable.PathNotFoundError -f $file)

                $errParams = @{
                    Category = $ErrorCatObjectNotFound
                    Exception = New-Object -TypeName 'System.IO.FileNotFoundException' -ArgumentList $errMsg, $file
                    Message = $errMsg
                    TargetObject = $file
                }

                Write-Error @errParams
            }
            catch [System.Xml.XmlException]
            {
                $verboseErrorMsg = ($MsgTable.InvalidXmlFileError -f $file)
            }
            catch [System.Xml.Schema.XmlSchemaValidationException]
            {
                $verboseErrorMsg = ($MsgTable.InvalidConfigFileError -f $file)
            }
            catch
            {
                $PSCmdlet.WriteError($_)
            } #catch
            finally
            {
                if ($xmlStrReader)
                {
                    $xmlStrReader.Close()
                }

                if ($verboseErrorMsg)
                {
                    Write-VMVerbose -FunctionName Test-VMConnectConfig -Category Error -Message $verboseErrorMsg
                }

                Write-Output $configFileIsValid
            } #finally
        } #foreach $file
    } #process

    end
    {
        # Perform garbage collection.
        [System.GC]::Collect()
        [System.GC]::WaitForPendingFinalizers()
    } #end
} #function Test-VmConnectConfig