function Test-DSCConfigurationDataDeployment { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [string] $Path, [Parameter(Mandatory=$false)] [switch] $PassThru ) $ResolvedPath = Repair-ConfigurationDataPathEncoding -Path $Path $Issues = @() if(-not (Test-Path -LiteralPath $ResolvedPath -PathType Leaf)){ $Issues += [PSCustomObject]@{ Severity = "Error" Code = "DeploymentNotFound" Path = $ResolvedPath Message = "Deployment file was not found." } }else{ try { $DeploymentData = Import-PowerShellDataFile -LiteralPath $ResolvedPath } catch { $Issues += [PSCustomObject]@{ Severity = "Error" Code = "DeploymentImportFailed" Path = $ResolvedPath Message = $_.Exception.Message } } } if($null -ne $DeploymentData){ if(-not (Test-ConfigurationDataDictionaryKey -Dictionary $DeploymentData -Key "Metadata")){ $Issues += [PSCustomObject]@{ Severity = "Error"; Code = "MetadataMissing"; Path = $ResolvedPath; Message = "Metadata block is missing." } }elseif($DeploymentData.Metadata.TemplateType -ne "Deployment"){ $Issues += [PSCustomObject]@{ Severity = "Error"; Code = "InvalidTemplateType"; Path = $ResolvedPath; Message = "Metadata.TemplateType must be [Deployment]." } } foreach($Key in @("Parameters", "Variables", "Resources")){ if(-not (Test-ConfigurationDataDictionaryKey -Dictionary $DeploymentData -Key $Key)){ $Issues += [PSCustomObject]@{ Severity = "Error"; Code = "$Key`Missing"; Path = $ResolvedPath; Message = "Required block [$Key] is missing." } } } if((Test-ConfigurationDataDictionaryKey -Dictionary $DeploymentData -Key "Resources") -and (-not (Test-ConfigurationDataDictionaryKey -Dictionary $DeploymentData.Resources -Key "AllNodes"))){ $Issues += [PSCustomObject]@{ Severity = "Error"; Code = "AllNodesMissing"; Path = $ResolvedPath; Message = "Resources.AllNodes is missing." } } if((Test-ConfigurationDataDictionaryKey -Dictionary $DeploymentData -Key "Resources") -and (Test-ConfigurationDataDictionaryKey -Dictionary $DeploymentData.Resources -Key "AllNodes")){ $NodeNames = @{} foreach($Node in @($DeploymentData.Resources.AllNodes)){ if(-not (Test-ConfigurationDataDictionaryKey -Dictionary $Node -Key "NodeName") -or [string]::IsNullOrWhiteSpace([string]$Node.NodeName)){ $Issues += [PSCustomObject]@{ Severity = "Error"; Code = "NodeNameMissing"; Path = $ResolvedPath; Message = "An AllNodes item has no NodeName." } continue } $NodeName = [string]$Node.NodeName if($NodeNames.ContainsKey($NodeName)){ $Issues += [PSCustomObject]@{ Severity = "Error"; Code = "DuplicateNodeName"; Path = $ResolvedPath; Message = "Duplicate NodeName [$NodeName]." } }else{ $NodeNames[$NodeName] = $true } } } if($DeploymentData.Metadata -is [System.Collections.IDictionary] -and (Test-ConfigurationDataDictionaryKey -Dictionary $DeploymentData.Metadata -Key "Sources") -and $DeploymentData.Metadata.Sources -is [System.Collections.IDictionary] -and (Test-ConfigurationDataDictionaryKey -Dictionary $DeploymentData.Metadata.Sources -Key "Files")){ foreach($Source in @($DeploymentData.Metadata.Sources.Files)){ $SourcePath = if($Source -is [System.Collections.IDictionary]){ [string]$Source.Path } else { [string]$Source } if([string]::IsNullOrWhiteSpace($SourcePath)){ $Issues += [PSCustomObject]@{ Severity = "Warning"; Code = "SourcePathMissing"; Path = $ResolvedPath; Message = "A source file entry has no path." } continue } try { $ActualPath = Resolve-ConfigurationDataSourcePath -Path $SourcePath -DeploymentPath $ResolvedPath } catch { $Issues += [PSCustomObject]@{ Severity = "Warning"; Code = "SourceMissing"; Path = $SourcePath; Message = $_.Exception.Message } continue } if($Source -is [System.Collections.IDictionary] -and (Test-ConfigurationDataDictionaryKey -Dictionary $Source -Key "Hash")){ $Algorithm = [string](Get-ConfigurationDataDictionaryValue -Dictionary $Source -Key "Algorithm" -DefaultValue "SHA256") $ActualHash = Get-FileHash -LiteralPath $ActualPath -Algorithm $Algorithm if($ActualHash.Hash -ne [string]$Source.Hash){ $Issues += [PSCustomObject]@{ Severity = "Warning" Code = "SourceChanged" Path = $SourcePath Message = "Source file hash has changed." ExpectedHash = [string]$Source.Hash ActualHash = $ActualHash.Hash Algorithm = $Algorithm } } } } }else{ $Issues += [PSCustomObject]@{ Severity = "Warning"; Code = "SourceMetadataMissing"; Path = $ResolvedPath; Message = "Metadata.Sources.Files is missing." } } } $Result = [PSCustomObject]@{ Path = $ResolvedPath IsValid = @($Issues | Where-Object { $_.Severity -eq "Error" }).Count -eq 0 IsStale = @($Issues | Where-Object { $_.Code -in @("SourceChanged", "SourceMissing") }).Count -gt 0 Issues = $Issues } if($PassThru){ return $Result } return $Result.IsValid -and (-not $Result.IsStale) }