|
| 1 | +<# |
| 2 | +.SYNOPSIS |
| 3 | + Generates a Catalog Definition File (.cdf) and optionally runs makecat.exe to |
| 4 | + produce a signed catalog (.cat) file. |
| 5 | +.DESCRIPTION |
| 6 | + Recursively scans a directory for files matching a filter and produces a .cdf |
| 7 | + file suitable for makecat.exe. Optionally invokes makecat.exe to generate the |
| 8 | + .cat file directly. |
| 9 | +
|
| 10 | + Used in component team pipelines (Arcade SDK, MicroBuild, or custom) to |
| 11 | + catalog-sign customer-modifiable or non-PE files that cannot use direct |
| 12 | + Authenticode signing. |
| 13 | +
|
| 14 | + If -RunMakecat is specified, the script finds and runs makecat.exe automatically. |
| 15 | + Otherwise, it prints the commands to run manually. |
| 16 | +.PARAMETER RootPath |
| 17 | + The directory containing files to include in the catalog. |
| 18 | +.PARAMETER CdfPath |
| 19 | + The output path for the .cdf file. If not specified and CatOutputPath is set, |
| 20 | + defaults to the CatOutputPath with a .cdf extension. |
| 21 | +.PARAMETER CatOutputPath |
| 22 | + The path where makecat.exe will create the .cat file. Defaults to the CDF |
| 23 | + path with a .cat extension. |
| 24 | +.PARAMETER Filter |
| 25 | + File filter pattern (e.g., '*.js', '*.xml', '*.ttf'). Default: '*.*' (all files). |
| 26 | +.PARAMETER RunMakecat |
| 27 | + If specified, finds and runs makecat.exe to produce the .cat file. |
| 28 | +.PARAMETER WindowsSdkDir |
| 29 | + Optional path to the Windows SDK. Used to locate makecat.exe when -RunMakecat is set. |
| 30 | +.PARAMETER ErrorIfMakecatNotFound |
| 31 | + If specified with -RunMakecat, throws an error when makecat.exe is not found |
| 32 | + instead of warning and skipping. Use in CI/official builds. |
| 33 | +.EXAMPLE |
| 34 | + # Generate CDF only (print manual steps): |
| 35 | + .\New-CatalogDefinitionFile.ps1 -RootPath ".\content" -CdfPath ".\obj\my-files.cdf" |
| 36 | +.EXAMPLE |
| 37 | + # Generate CDF and run makecat.exe: |
| 38 | + .\New-CatalogDefinitionFile.ps1 -RootPath ".\content" -CatOutputPath ".\obj\my-files.cat" -Filter "*.js" -RunMakecat |
| 39 | +.EXAMPLE |
| 40 | + # CI usage (error if makecat.exe not found): |
| 41 | + .\New-CatalogDefinitionFile.ps1 -RootPath "$(_ContentRoot)" -CatOutputPath "$(_CatOutputPath)" -Filter "*.js" -RunMakecat -ErrorIfMakecatNotFound |
| 42 | +#> |
| 43 | +[CmdletBinding()] |
| 44 | +param( |
| 45 | + [Parameter(Mandatory)] |
| 46 | + [string]$RootPath, |
| 47 | + |
| 48 | + [string]$CdfPath, |
| 49 | + |
| 50 | + [string]$CatOutputPath, |
| 51 | + |
| 52 | + [string]$Filter = '*.*', |
| 53 | + |
| 54 | + [string]$WindowsSdkDir = '', |
| 55 | + |
| 56 | + [switch]$RunMakecat, |
| 57 | + |
| 58 | + [switch]$ErrorIfMakecatNotFound |
| 59 | +) |
| 60 | + |
| 61 | +$ErrorActionPreference = 'Stop' |
| 62 | + |
| 63 | +if (-not (Test-Path $RootPath)) { |
| 64 | + Write-Error "Root path not found: $RootPath" |
| 65 | + return |
| 66 | +} |
| 67 | + |
| 68 | +# Resolve paths: need at least one of CdfPath or CatOutputPath |
| 69 | +if (-not $CdfPath -and -not $CatOutputPath) { |
| 70 | + Write-Error "Specify at least one of -CdfPath or -CatOutputPath." |
| 71 | + return |
| 72 | +} |
| 73 | +if (-not $CatOutputPath) { |
| 74 | + $CatOutputPath = [System.IO.Path]::ChangeExtension($CdfPath, '.cat') |
| 75 | +} |
| 76 | +if (-not $CdfPath) { |
| 77 | + $CdfPath = [System.IO.Path]::ChangeExtension($CatOutputPath, '.cdf') |
| 78 | +} |
| 79 | + |
| 80 | +# Ensure output directories exist |
| 81 | +$cdfDir = Split-Path $CdfPath -Parent |
| 82 | +if ($cdfDir -and -not (Test-Path $cdfDir)) { |
| 83 | + New-Item -ItemType Directory -Path $cdfDir -Force | Out-Null |
| 84 | +} |
| 85 | +$catDir = Split-Path $CatOutputPath -Parent |
| 86 | +if ($catDir -and -not (Test-Path $catDir)) { |
| 87 | + New-Item -ItemType Directory -Path $catDir -Force | Out-Null |
| 88 | +} |
| 89 | + |
| 90 | +$files = Get-ChildItem -Path $RootPath -Recurse -Filter $Filter -File |
| 91 | +if ($files.Count -eq 0) { |
| 92 | + Write-Warning "No files matching '$Filter' found under $RootPath - skipping catalog generation." |
| 93 | + return |
| 94 | +} |
| 95 | + |
| 96 | +$cdfContent = @() |
| 97 | +$cdfContent += "[CatalogHeader]" |
| 98 | +$cdfContent += "Name=$CatOutputPath" |
| 99 | +$cdfContent += "CatalogVersion=2" |
| 100 | +$cdfContent += "HashAlgorithms=SHA256" |
| 101 | +$cdfContent += "" |
| 102 | +$cdfContent += "[CatalogFiles]" |
| 103 | + |
| 104 | +$i = 0 |
| 105 | +foreach ($f in $files) { |
| 106 | + $ext = $f.Extension.TrimStart('.').ToLower() |
| 107 | + $label = "${ext}_${i}_" + ($f.Name -replace '[^\w\.-]', '_') |
| 108 | + $cdfContent += "<hash>$label=$($f.FullName)" |
| 109 | + $i++ |
| 110 | +} |
| 111 | + |
| 112 | +$cdfContent | Set-Content -Path $CdfPath -Encoding ASCII |
| 113 | + |
| 114 | +Write-Host "Generated CDF with $($files.Count) file(s) matching '$Filter' at $CdfPath" |
| 115 | + |
| 116 | +if ($RunMakecat) { |
| 117 | + # Find makecat.exe — ships with the Windows SDK |
| 118 | + $makecat = $null |
| 119 | + if ($WindowsSdkDir -and (Test-Path $WindowsSdkDir)) { |
| 120 | + $makecat = Get-ChildItem -Path (Join-Path $WindowsSdkDir 'bin') -Recurse -Filter 'makecat.exe' -File | |
| 121 | + Where-Object { $_.DirectoryName -match 'x64' } | |
| 122 | + Sort-Object DirectoryName -Descending | |
| 123 | + Select-Object -First 1 |
| 124 | + } |
| 125 | + if (-not $makecat) { $makecat = Get-Command makecat.exe -ErrorAction SilentlyContinue } |
| 126 | + if (-not $makecat) { |
| 127 | + $sdkRoot = "${env:ProgramFiles(x86)}\Windows Kits\10\bin" |
| 128 | + if (Test-Path $sdkRoot) { |
| 129 | + $makecat = Get-ChildItem -Path $sdkRoot -Recurse -Filter 'makecat.exe' -File | |
| 130 | + Where-Object { $_.DirectoryName -match 'x64' } | |
| 131 | + Sort-Object DirectoryName -Descending | |
| 132 | + Select-Object -First 1 |
| 133 | + } |
| 134 | + } |
| 135 | + if (-not $makecat) { |
| 136 | + if ($ErrorIfMakecatNotFound) { |
| 137 | + throw "makecat.exe not found. Catalog signing requires the Windows SDK." |
| 138 | + } |
| 139 | + Write-Warning "makecat.exe not found - skipping catalog generation. Install Windows SDK for catalog signing." |
| 140 | + return |
| 141 | + } |
| 142 | + |
| 143 | + $makecatPath = if ($makecat -is [System.Management.Automation.CommandInfo]) { $makecat.Source } else { $makecat.FullName } |
| 144 | + Write-Host "Using makecat.exe at: $makecatPath" |
| 145 | + |
| 146 | + & $makecatPath $CdfPath |
| 147 | + if ($LASTEXITCODE -ne 0) { |
| 148 | + throw "makecat.exe failed with exit code $LASTEXITCODE" |
| 149 | + } |
| 150 | + |
| 151 | + Write-Host "Generated catalog file: $CatOutputPath" |
| 152 | +} else { |
| 153 | + Write-Host "" |
| 154 | + Write-Host "Next steps:" |
| 155 | + Write-Host " 1. Run: makecat.exe `"$CdfPath`"" |
| 156 | + Write-Host " 2. Sign: dotnet ddsignfiles.dll -- /file:`"$CatOutputPath`" /certs:Microsoft400" |
| 157 | +} |
0 commit comments