#Requires -Version 5.1 Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function Get-RootPrefix { param( [Parameter(Mandatory = $true)] [string] $Root ) if ($Root.EndsWith([IO.Path]::DirectorySeparatorChar) -or $Root.EndsWith([IO.Path]::AltDirectorySeparatorChar)) { return $Root } return $Root + [IO.Path]::DirectorySeparatorChar } function Test-IsUnderRoot { param( [Parameter(Mandatory = $true)] [string] $Root, [Parameter(Mandatory = $true)] [string] $Candidate ) $prefix = Get-RootPrefix -Root $Root return $Candidate.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase) } function Find-ReparseAncestor { param( [Parameter(Mandatory = $true)] [string] $Root, [Parameter(Mandatory = $true)] [string] $Candidate ) $rootItem = Get-Item -LiteralPath $Root -Force if (($rootItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { return '.' } $prefix = Get-RootPrefix -Root $Root $relative = $Candidate.Substring($prefix.Length) $current = $Root foreach ($part in ($relative -split '[\\/]' | Where-Object { $_.Length -ne 0 })) { $current = Join-Path $current $part if (-not (Test-Path -LiteralPath $current)) { break } $item = Get-Item -LiteralPath $current -Force if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { return $part } } return $null } function Resolve-CoveragePath { param( [Parameter(Mandatory = $true)] [string] $Root, [Parameter(Mandatory = $true)] [string] $RelativePath ) if ([string]::IsNullOrWhiteSpace($RelativePath) -or [IO.Path]::IsPathRooted($RelativePath)) { return [pscustomobject][ordered]@{ Status = 'RootEscape' FullPath = $null } } try { $candidate = [IO.Path]::GetFullPath((Join-Path $Root $RelativePath)) } catch { return [pscustomobject][ordered]@{ Status = 'ManifestInvalid' FullPath = $null } } if (-not (Test-IsUnderRoot -Root $Root -Candidate $candidate)) { return [pscustomobject][ordered]@{ Status = 'RootEscape' FullPath = $null } } $reparse = Find-ReparseAncestor -Root $Root -Candidate $candidate if (-not [string]::IsNullOrEmpty($reparse)) { return [pscustomobject][ordered]@{ Status = 'Reparse' FullPath = $null } } return [pscustomobject][ordered]@{ Status = 'Resolved' FullPath = $candidate } } function Test-AssetDefinition { param( [Parameter(Mandatory = $true)] [string] $Kind, [Parameter(Mandatory = $true)] [string] $RelativePath ) $allowedExtensions = switch ($Kind) { 'Image' { @('.bmp', '.jpeg', '.jpg', '.png', '.tga') } 'Texture' { @('.bmp', '.jpeg', '.jpg', '.png', '.tga', '.vrv') } 'Video' { @('.vrv') } default { return $false } } $extension = [IO.Path]::GetExtension($RelativePath) return $allowedExtensions -contains $extension.ToLowerInvariant() } function Test-LegacyCutCoverageState { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $CutRoot, [Parameter(Mandatory = $true)] [string[]] $Aliases, [Parameter(Mandatory = $true)] [object[]] $AssetRequirements, [Parameter(Mandatory = $true)] [int] $ReachableBuilders ) if (-not [IO.Path]::IsPathRooted($CutRoot)) { throw 'CutRoot must be an absolute path.' } $root = [IO.Path]::GetFullPath($CutRoot) if (-not (Test-Path -LiteralPath $root -PathType Container)) { throw 'CutRoot does not identify an existing directory.' } $aliasFindings = [Collections.Generic.List[object]]::new() foreach ($alias in $Aliases) { $relativePath = $alias + '.t2s' $resolved = Resolve-CoveragePath -Root $root -RelativePath $relativePath $status = $resolved.Status if ($status -eq 'Resolved') { if (-not (Test-Path -LiteralPath $resolved.FullPath -PathType Leaf)) { $status = 'Missing' } else { $item = Get-Item -LiteralPath $resolved.FullPath -Force $status = if ($item.Length -le 0) { 'Empty' } else { 'Present' } } } $aliasFindings.Add([pscustomobject][ordered]@{ Alias = $alias RelativePath = $relativePath Status = $status }) } $assetFindings = [Collections.Generic.List[object]]::new() foreach ($requirement in $AssetRequirements) { $builder = [string] $requirement.Builder $kind = [string] $requirement.Kind $relativePath = [string] $requirement.Path $status = 'ManifestInvalid' if (-not [string]::IsNullOrWhiteSpace($builder) -and -not [string]::IsNullOrWhiteSpace($relativePath) -and (Test-AssetDefinition -Kind $kind -RelativePath $relativePath)) { $resolved = Resolve-CoveragePath -Root $root -RelativePath $relativePath $status = $resolved.Status if ($status -eq 'Resolved') { if (-not (Test-Path -LiteralPath $resolved.FullPath -PathType Leaf)) { $status = 'Missing' } else { $item = Get-Item -LiteralPath $resolved.FullPath -Force $status = if ($item.Length -le 0) { 'Empty' } else { 'Present' } } } } $assetFindings.Add([pscustomobject][ordered]@{ Builder = $builder Kind = $kind RelativePath = $relativePath Status = $status }) } $missingAliases = @($aliasFindings | Where-Object Status -eq 'Missing') $unsafeAliases = @($aliasFindings | Where-Object { $_.Status -ne 'Present' -and $_.Status -ne 'Missing' }) $missingAssets = @($assetFindings | Where-Object Status -eq 'Missing') $unsafeAssets = @($assetFindings | Where-Object { $_.Status -ne 'Present' -and $_.Status -ne 'Missing' }) $rootEscapes = @($assetFindings | Where-Object Status -eq 'RootEscape') $reparseAssets = @($assetFindings | Where-Object Status -eq 'Reparse') $invalidAssets = @($assetFindings | Where-Object Status -eq 'ManifestInvalid') $failed = $missingAliases.Count -ne 0 -or $unsafeAliases.Count -ne 0 -or $missingAssets.Count -ne 0 -or $unsafeAssets.Count -ne 0 return [pscustomobject][ordered]@{ Status = if ($failed) { 'Failed' } else { 'Passed' } ReachableBuilders = $ReachableBuilders ActiveAliases = $Aliases.Count Missing = $missingAliases.Count Unsafe = $unsafeAliases.Count RequiredAssets = $AssetRequirements.Count MissingAssets = $missingAssets.Count UnsafeAssets = $unsafeAssets.Count RootEscapes = $rootEscapes.Count ReparseAssets = $reparseAssets.Count InvalidAssetDefinitions = $invalidAssets.Count AliasFindings = $aliasFindings.ToArray() AssetFindings = $assetFindings.ToArray() } } Export-ModuleMember -Function Test-LegacyCutCoverageState