2025-04-25 09:57:42 +08:00
|
|
|
<#
|
|
|
|
|
.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 {
|
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
|
|
|
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
|
|
|
|
|
}
|
2025-04-25 09:57:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$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."
|
|
|
|
|
}
|
[Build script] Polish powertoys build script (#40727)
<!-- 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
1. Add build parameters to build multiple types of installer
2. Add functionality to local cert management, to be able to export a
cert locally, so that the installer can be installed to other machine
3. Now the script does not need to be executed in root folder of
powertoys repo.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [ ] **Closes:** #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [X] **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
I build an installer locally, and verified packaged apps(New+,
powerRename, cmdpal/) etc can be installed successfully. And powertoys
can be installed without problem
2025-07-22 10:22:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
}
|
2025-04-29 18:31:21 +08:00
|
|
|
}
|