Files
PowerToys/tools/build/cert-management.ps1
Kai Tao f8c5ff8c0c Build: Fix build script for a local installer (#44168)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Not maintained since wix5 upgrade, so make it build locally for an
installer
1. Do elevation when dev cert is not added to root store
2. Set up version to build arg to build, and build cmdpal version same
with CI
3. cmdpal AOT local build
4. Make sure every msix file is signed successfully

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Verify the script can build an installer on a new devbox, and cmdpal,
filelocksmith etc can be run without problem
<img width="872" height="275" alt="image"
src="https://github.com/user-attachments/assets/cf4cff0d-0d90-4496-a7f8-50c582d9c340"
/>
<img width="1251" height="555" alt="image"
src="https://github.com/user-attachments/assets/6529c1a8-a532-4dfc-9f74-2c2fd37e28e6"
/>
Output for msix packages:
PackageFullName Version
--------------- -------
Microsoft.PowerToys.SparseApp_0.96.2.0_neutral__8wekyb3d8bbwe 0.96.2.0

Microsoft.PowerToys.FileLocksmithContextMenu_0.96.2.0_neutral__8wekyb3d8bbwe
0.96.2.0

Microsoft.PowerToys.ImageResizerContextMenu_0.96.2.0_neutral__8wekyb3d8bbwe
0.96.2.0
Microsoft.PowerToys.NewPlusContextMenu_0.96.2.0_neutral__8wekyb3d8bbwe
0.96.2.0

Microsoft.PowerToys.PowerRenameContextMenu_0.96.2.0_neutral__8wekyb3d8bbwe
0.96.2.0

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-11 13:17:42 +08:00

198 lines
7.3 KiB
PowerShell

<#
.SYNOPSIS
Ensures a code signing certificate exists and is trusted in all necessary certificate stores.
.DESCRIPTION
This script provides two functions:
1. EnsureCertificate:
- Searches for an existing code signing certificate by subject name.
- If not found, creates a new self-signed certificate.
- Exports the certificate and attempts to import it into:
- CurrentUser\TrustedPeople
- CurrentUser\Root
- LocalMachine\Root (admin privileges may be required)
2. ImportAndVerifyCertificate:
- Imports a `.cer` file into the specified certificate store if not already present.
- Verifies the certificate is successfully imported by checking thumbprint.
This is useful in build or signing pipelines to ensure a valid and trusted certificate is available before signing MSIX or executable files.
.PARAMETER certSubject
The subject name of the certificate to search for or create. Default is:
"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
.PARAMETER cerPath
(ImportAndVerifyCertificate only) The file path to a `.cer` certificate file to import.
.PARAMETER storePath
(ImportAndVerifyCertificate only) The destination certificate store path (e.g. Cert:\CurrentUser\Root).
.EXAMPLE
$cert = EnsureCertificate
Ensures the default certificate exists and is trusted, and returns the certificate object.
.EXAMPLE
ImportAndVerifyCertificate -cerPath "$env:TEMP\temp_cert.cer" -storePath "Cert:\CurrentUser\Root"
Imports a certificate into the CurrentUser Root store and verifies its presence.
.NOTES
- For full trust, administrative privileges may be needed to import into LocalMachine\Root.
- Certificates are created using RSA and SHA256 and marked as CodeSigningCert.
#>
function ImportAndVerifyCertificate {
param (
[string]$cerPath,
[string]$storePath
)
$thumbprint = (Get-PfxCertificate -FilePath $cerPath).Thumbprint
$existingCert = Get-ChildItem -Path $storePath | Where-Object { $_.Thumbprint -eq $thumbprint }
if ($existingCert) {
Write-Host "Certificate already exists in $storePath"
return $true
}
try {
$null = Import-Certificate -FilePath $cerPath -CertStoreLocation $storePath -ErrorAction Stop
} catch {
if ($_.Exception.Message -match "Access is denied" -or $_.Exception.InnerException.Message -match "Access is denied") {
Write-Warning "Access denied to $storePath. Attempting to import with admin privileges..."
try {
Start-Process powershell -ArgumentList "-NoProfile", "-Command", "& { Import-Certificate -FilePath '$cerPath' -CertStoreLocation '$storePath' }" -Verb RunAs -Wait
} catch {
Write-Warning "Failed to request admin privileges: $_"
return $false
}
} else {
Write-Warning "Failed to import certificate to $storePath : $_"
return $false
}
}
$imported = Get-ChildItem -Path $storePath | Where-Object { $_.Thumbprint -eq $thumbprint }
if ($imported) {
Write-Host "Certificate successfully imported to $storePath"
return $true
} else {
Write-Warning "Certificate not found in $storePath after import"
return $false
}
}
function EnsureCertificate {
param (
[string]$certSubject = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
)
$cert = Get-ChildItem -Path Cert:\CurrentUser\My |
Where-Object { $_.Subject -eq $certSubject } |
Sort-Object NotAfter -Descending |
Select-Object -First 1
if (-not $cert) {
Write-Host "Certificate not found. Creating a new one..."
$cert = New-SelfSignedCertificate -Subject $certSubject `
-CertStoreLocation "Cert:\CurrentUser\My" `
-KeyAlgorithm RSA `
-Type CodeSigningCert `
-HashAlgorithm SHA256
if (-not $cert) {
Write-Error "Failed to create a new certificate."
return $null
}
Write-Host "New certificate created with thumbprint: $($cert.Thumbprint)"
}
else {
Write-Host "Using existing certificate with thumbprint: $($cert.Thumbprint)"
}
$cerPath = "$env:TEMP\temp_cert.cer"
[void](Export-Certificate -Cert $cert -FilePath $cerPath -Force)
if (-not (ImportAndVerifyCertificate -cerPath $cerPath -storePath "Cert:\CurrentUser\TrustedPeople")) { return $null }
if (-not (ImportAndVerifyCertificate -cerPath $cerPath -storePath "Cert:\CurrentUser\Root")) { return $null }
if (-not (ImportAndVerifyCertificate -cerPath $cerPath -storePath "Cert:\LocalMachine\Root")) {
Write-Warning "Failed to import to LocalMachine\Root (admin may be required)"
return $null
}
return $cert
}
function Export-CertificateFiles {
param (
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
[string]$CerPath,
[string]$PfxPath,
[securestring]$PfxPassword
)
if (-not $Certificate) {
Write-Error "No certificate provided to export."
return
}
if ($CerPath) {
try {
Export-Certificate -Cert $Certificate -FilePath $CerPath -Force | Out-Null
Write-Host "Exported CER to: $CerPath"
} catch {
Write-Warning "Failed to export CER file: $_"
}
}
if ($PfxPath -and $PfxPassword) {
try {
Export-PfxCertificate -Cert $Certificate -FilePath $PfxPath -Password $PfxPassword -Force | Out-Null
Write-Host "Exported PFX to: $PfxPath"
} catch {
Write-Warning "Failed to export PFX file: $_"
}
}
if (-not $CerPath -and -not $PfxPath) {
Write-Warning "No output path specified. Nothing was exported."
}
}
# Main execution when script is run directly
if ($MyInvocation.InvocationName -ne '.') {
Write-Host "=== PowerToys Certificate Management ===" -ForegroundColor Green
Write-Host ""
# Ensure certificate exists and is trusted
Write-Host "Checking for existing certificate or creating new one..." -ForegroundColor Yellow
$cert = EnsureCertificate
if ($cert) {
# Export the certificate to a .cer file
$exportPath = Join-Path (Get-Location) "PowerToys-CodeSigning.cer"
Write-Host ""
Write-Host "Exporting certificate..." -ForegroundColor Yellow
Export-CertificateFiles -Certificate $cert -CerPath $exportPath
Write-Host ""
Write-Host "=== IMPORTANT NOTES ===" -ForegroundColor Red
Write-Host "The certificate has been exported to: $exportPath" -ForegroundColor White
Write-Host ""
Write-Host "To use this certificate for code signing, you need to:" -ForegroundColor Yellow
Write-Host "1. Import this certificate into 'Trusted People' store" -ForegroundColor White
Write-Host "2. Import this certificate into 'Trusted Root Certification Authorities' store" -ForegroundColor White
Write-Host "Certificate Details:" -ForegroundColor Green
Write-Host "Subject: $($cert.Subject)" -ForegroundColor White
Write-Host "Thumbprint: $($cert.Thumbprint)" -ForegroundColor White
Write-Host "Valid Until: $($cert.NotAfter)" -ForegroundColor White
} else {
Write-Error "Failed to create or find certificate. Please check the error messages above."
exit 1
}
}