Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion eng/Signing.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,23 @@
<FileSignInfo Include="SegoeUI-Semibold.ttf" CertificateName="3PartyScriptsSHA2" />
</ItemGroup>

<ItemGroup Label="Customer-modifiable template files">
<!-- Skip Authenticode - this JS is included in customer applications and users edit it.
Covered by catalog signing (.cat) instead - see GenerateCatalogFiles target in
src/Templates/src/Microsoft.Maui.Templates.csproj. -->
<FileSignInfo Include="ReconnectModal.razor.js" CertificateName="None" />
</ItemGroup>

<ItemGroup Label="Catalog signing">
<!-- Sign catalog files that cover customer-modifiable template content -->
<FileExtensionSignInfo Include=".cat" CertificateName="Microsoft400" />
</ItemGroup>

<ItemGroup>
<ItemsToSign Include="$(ArtifactsShippingPackagesDir)\**\*.msi" Condition="'$(PostBuildSign)' != 'true'" />
<ItemsToSign Include="$(ArtifactsShippingPackagesDir)**\*.wixpack.zip" Condition="'$(PostBuildSign)' != 'true'" />
<ItemsToSignPostBuild Include="$(ArtifactsShippingPackagesDir)\**\*.msi" Condition="'$(PostBuildSign)' == 'true'" />
<ItemsToSign Include="$(ArtifactsShippingPackagesDir)\**\*.zip" Condition="'$(PostBuildSign)' != 'true'" />
<ItemsToSignPostBuild Include="$(ArtifactsShippingPackagesDir)\**\*.zip" Condition="'$(PostBuildSign)' == 'true'" />
</ItemGroup>
</Project>
</Project>
102 changes: 102 additions & 0 deletions eng/generate-catalog.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<#
.SYNOPSIS
Generates a catalog (.cat) file for customer-modifiable template content.
.DESCRIPTION
Recursively scans a directory for files matching a filter and produces a
Catalog Definition File (.cdf), then runs makecat.exe to create the .cat.

Used to catalog-sign files that cannot use direct Authenticode signing
because customers are expected to modify them (e.g., template .js files).
.PARAMETER RootPath
The directory containing files to include in the catalog.
.PARAMETER CatOutputPath
The path where makecat.exe will create the .cat file.
.PARAMETER Filter
Comma-separated file filter patterns (e.g., '*.js' or '*.js,*.ttf'). Default: '*.*'.
.PARAMETER ErrorIfMakecatNotFound
Throws an error when makecat.exe is not found instead of warning and skipping.
Use in CI/official builds.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$RootPath,

[Parameter(Mandatory)]
[string]$CatOutputPath,

[string]$Filter = '*.*',

[switch]$ErrorIfMakecatNotFound
)

$ErrorActionPreference = 'Stop'

if (-not (Test-Path $RootPath)) {
Write-Error "Root path not found: $RootPath"
return
}

$CdfPath = [System.IO.Path]::ChangeExtension($CatOutputPath, '.cdf')

# Ensure output directory exists
$catDir = Split-Path $CatOutputPath -Parent
if ($catDir -and -not (Test-Path $catDir)) {
New-Item -ItemType Directory -Path $catDir -Force | Out-Null
}

$files = Get-ChildItem -Path $RootPath -Recurse -Include ($Filter -split ',') -File
if ($files.Count -eq 0) {
Write-Warning "No files matching '$Filter' found under $RootPath - skipping catalog generation."
return
}

# Build the CDF content
$cdfContent = @()
$cdfContent += "[CatalogHeader]"
$cdfContent += "Name=$CatOutputPath"
$cdfContent += "CatalogVersion=2"
$cdfContent += "HashAlgorithms=SHA256"
$cdfContent += ""
$cdfContent += "[CatalogFiles]"

$i = 0
foreach ($f in $files) {
$ext = $f.Extension.TrimStart('.').ToLower()
$label = "${ext}_${i}_" + ($f.Name -replace '[^\w\.-]', '_')
$cdfContent += "<hash>$label=$($f.FullName)"
$i++
}

$cdfContent | Set-Content -Path $CdfPath -Encoding ASCII
Write-Host "Generated CDF with $($files.Count) file(s) matching '$Filter' at $CdfPath"

# Find makecat.exe (ships with Windows SDK)
$makecat = Get-Command makecat.exe -ErrorAction SilentlyContinue
if (-not $makecat) {
$sdkRoot = "${env:ProgramFiles(x86)}\Windows Kits\10\bin"
if (Test-Path $sdkRoot) {
$makecat = Get-ChildItem -Path $sdkRoot -Recurse -Filter 'makecat.exe' -File |
Where-Object { $_.DirectoryName -match 'x64' } |
Sort-Object DirectoryName -Descending |
Select-Object -First 1
}
}

if (-not $makecat) {
if ($ErrorIfMakecatNotFound) {
throw "makecat.exe not found. Catalog signing requires the Windows SDK."
}
Write-Warning "makecat.exe not found - skipping catalog generation. Install Windows SDK for catalog signing."
return
}

$makecatPath = if ($makecat -is [System.Management.Automation.CommandInfo]) { $makecat.Source } else { $makecat.FullName }
Write-Host "Using makecat.exe at: $makecatPath"

& $makecatPath $CdfPath
if ($LASTEXITCODE -ne 0) {
throw "makecat.exe failed with exit code $LASTEXITCODE"
}

Write-Host "Generated catalog file: $CatOutputPath"
26 changes: 26 additions & 0 deletions src/Templates/src/Microsoft.Maui.Templates.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,30 @@
<!-- this target will get replaced by the nuget -->
<Target Name="LocalizeTemplatesAfterBuild" />

<!--
Generate a catalog (.cat) file for customer-modifiable template content.
The .cat is signed by Arcade (FileExtensionSignInfo in eng/Signing.props)
while the .js files themselves are marked CertificateName="None" because
customers are expected to edit them.
Windows-only: makecat.exe ships with the Windows SDK.
-->
<Target Name="GenerateCatalogFiles"
AfterTargets="Build"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Build & MSBuildGenerateCatalogFiles is hooked to AfterTargets="Build" and the Exec always passes -ErrorIfMakecatNotFound, so every ordinary Windows build of the templates project now requires Windows SDK makecat.exe even when not packing/signing. Catalog generation is only needed for package/signing output; scope this target to the pack/signing path, e.g. remove the AfterTargets="Build" hook and keep/replace it with an appropriate pack-only target/property guard.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Build & MSBuildGenerateCatalogFiles is wired with AfterTargets="Build"/BeforeTargets="Pack", which gives two bad failure modes: ordinary Windows builds now require makecat.exe because the target always passes -ErrorIfMakecatNotFound, and dotnet pack --no-build can run NuGet's pack dependency chain (BeforePack, GenerateNuspec, _GetPackageFiles) before this BeforeTargets="Pack" target adds the generated .cat to @(Content). Move catalog generation into the existing BeforePack dependency list (for example append GenerateCatalogFiles to <BeforePack>) and remove the AfterTargets/BeforeTargets hooks so the catalog is generated only for pack and before package content collection.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Build & MSBuild / safety — Hooking catalog generation to every Windows Build makes ordinary builds depend on makecat.exe: the Exec below always passes -ErrorIfMakecatNotFound, so a developer or CI leg that only builds this project on Windows without the Windows SDK catalog tool now fails even though it is not packing or signing. Restrict this target (and the hard-error behavior) to the pack/signing path instead of AfterTargets="Build".

BeforeTargets="Pack"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Build & packagingBeforeTargets="Pack" is too late for supported pack flows that skip the build. NuGet's Pack target runs $(BeforePack); _GetRestoreProjectStyle; _IntermediatePack; GenerateNuspec; ... as dependencies, and _GetPackageFiles runs inside GenerateNuspec before the Pack target itself. When dotnet pack --no-build (or any pack invocation where Build is not in GenerateNuspecDependsOn) is used, GenerateCatalogFiles only runs immediately before the empty Pack target body, after package content has already been collected, so the .cat can be omitted while ReconnectModal.razor.js is no longer Authenticode-signed. Hook catalog generation into $(BeforePack)/GenerateNuspecDependsOn before _GetPackageFiles, and avoid running it for ordinary non-pack builds.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Build & MSBuild / signing packaging semanticsBeforeTargets="Pack" is too late to make the dynamically-added .cat content reliable for no-build packing. SDK Pack runs GenerateNuspec/_GetPackageFiles as dependencies before the Pack target body; when packing with NoBuild=true (or any flow where Build is not run in this invocation), this hook fires only after NuGet has already snapshotted @(Content), so maui-template-content.cat can be omitted from the .nupkg. Move catalog generation into BeforePack/GenerateNuspecDependsOn before _GetPackageFiles (or add the Content item statically with an Exists condition) so no-build pack still packages the signed catalog.

Condition="'$(OS)' == 'Windows_NT'">
<PropertyGroup>
<_TemplateContentRoot>$(MSBuildProjectDirectory)\templates\</_TemplateContentRoot>
<_CatOutputPath>$(IntermediateOutputPath)maui-template-content.cat</_CatOutputPath>
</PropertyGroup>

<Exec Command="pwsh -NoProfile -NonInteractive -ExecutionPolicy Bypass -File &quot;$(MSBuildThisFileDirectory)..\..\..\eng\generate-catalog.ps1&quot; -RootPath &quot;$(_TemplateContentRoot)&quot; -CatOutputPath &quot;$(_CatOutputPath)&quot; -Filter &quot;*.js&quot; -ErrorIfMakecatNotFound"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Build & MSBuild - This target runs after every Windows Build and always passes -ErrorIfMakecatNotFound, so normal local/solution builds of the templates project now require a Windows SDK makecat.exe even though catalog generation is only needed for packing/signing. It also relies on BeforeTargets="Pack"; for dotnet pack --no-build, the pack item collection happens through GenerateNuspec dependencies before the Pack target body, so the .cat can be generated too late to be included. Please move this into the pack/signing path before _GetPackageFiles/GenerateNuspec, or condition the hard failure to official signing builds only.

IgnoreExitCode="false" />

<ItemGroup>
<Content Include="$(_CatOutputPath)" Pack="true" PackagePath="content" />
<FileWrites Include="$(_CatOutputPath)" />
<FileWrites Include="$(IntermediateOutputPath)maui-template-content.cdf" />
</ItemGroup>
</Target>

</Project>
Loading