mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 19:27:56 +01:00
[Tool] Script to build an installer locally (#39017)
* add script to build a installer * minor fix * fix search path for msix file * fix sign * fix sign * fix spelling * Fix powershell5 can't recognize emoji * ensure-wix * bring cmdpal available during local build * remove early quit * fix marco * add logger * doc * add a note * self review * fix macro def * add functionality to export cert so that other machine can install it. * spelling
This commit is contained in:
4
.github/actions/spell-check/expect.txt
vendored
4
.github/actions/spell-check/expect.txt
vendored
@@ -198,6 +198,7 @@ CLIPBOARDUPDATE
|
|||||||
CLIPCHILDREN
|
CLIPCHILDREN
|
||||||
CLIPSIBLINGS
|
CLIPSIBLINGS
|
||||||
closesocket
|
closesocket
|
||||||
|
clp
|
||||||
CLSCTX
|
CLSCTX
|
||||||
clsids
|
clsids
|
||||||
Clusion
|
Clusion
|
||||||
@@ -1045,6 +1046,7 @@ NOINHERITLAYOUT
|
|||||||
NOINTERFACE
|
NOINTERFACE
|
||||||
NOINVERT
|
NOINVERT
|
||||||
NOLINKINFO
|
NOLINKINFO
|
||||||
|
nologo
|
||||||
NOMCX
|
NOMCX
|
||||||
NOMINMAX
|
NOMINMAX
|
||||||
NOMIRRORBITMAP
|
NOMIRRORBITMAP
|
||||||
@@ -1277,6 +1279,7 @@ pstm
|
|||||||
PStr
|
PStr
|
||||||
pstream
|
pstream
|
||||||
pstrm
|
pstrm
|
||||||
|
pswd
|
||||||
PSYSTEM
|
PSYSTEM
|
||||||
psz
|
psz
|
||||||
ptb
|
ptb
|
||||||
@@ -1423,6 +1426,7 @@ searchterm
|
|||||||
SEARCHUI
|
SEARCHUI
|
||||||
SECONDARYDISPLAY
|
SECONDARYDISPLAY
|
||||||
secpol
|
secpol
|
||||||
|
securestring
|
||||||
SEEMASKINVOKEIDLIST
|
SEEMASKINVOKEIDLIST
|
||||||
SELCHANGE
|
SELCHANGE
|
||||||
SENDCHANGE
|
SENDCHANGE
|
||||||
|
|||||||
@@ -79,10 +79,7 @@
|
|||||||
<ComponentGroupRef Id="ToolComponentGroup" />
|
<ComponentGroupRef Id="ToolComponentGroup" />
|
||||||
<ComponentGroupRef Id="MonacoSRCHeatGenerated" />
|
<ComponentGroupRef Id="MonacoSRCHeatGenerated" />
|
||||||
<ComponentGroupRef Id="WorkspacesComponentGroup" />
|
<ComponentGroupRef Id="WorkspacesComponentGroup" />
|
||||||
|
<ComponentGroupRef Id="CmdPalComponentGroup" />
|
||||||
<?if $(var.CIBuild) = "true" ?>
|
|
||||||
<ComponentGroupRef Id="CmdPalComponentGroup" />
|
|
||||||
<?endif?>
|
|
||||||
</Feature>
|
</Feature>
|
||||||
|
|
||||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" />
|
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" />
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ namespace package
|
|||||||
if (!std::filesystem::exists(directoryPath))
|
if (!std::filesystem::exists(directoryPath))
|
||||||
{
|
{
|
||||||
Logger::error(L"The directory '" + directoryPath + L"' does not exist.");
|
Logger::error(L"The directory '" + directoryPath + L"' does not exist.");
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::regex pattern(R"(^.+\.(appx|msix|msixbundle)$)", std::regex_constants::icase);
|
const std::regex pattern(R"(^.+\.(appx|msix|msixbundle)$)", std::regex_constants::icase);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<Project DefaultTargets="Build"
|
<Project DefaultTargets="Build"
|
||||||
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||||
|
<Import Project="..\Microsoft.CmdPal.UI\CmdPal.pre.props" Condition="Exists('..\Microsoft.CmdPal.UI\CmdPal.pre.prop')" />
|
||||||
<PropertyGroup Label="Globals">
|
<PropertyGroup Label="Globals">
|
||||||
<VCProjectVersion>17.0</VCProjectVersion>
|
<VCProjectVersion>17.0</VCProjectVersion>
|
||||||
<Keyword>Win32Proj</Keyword>
|
<Keyword>Win32Proj</Keyword>
|
||||||
@@ -49,13 +50,21 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemDefinitionGroup>
|
<ItemDefinitionGroup>
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
<PreprocessorDefinitions>EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>
|
||||||
|
EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;
|
||||||
|
%(PreprocessorDefinitions);
|
||||||
|
$(CommandPaletteBranding)
|
||||||
|
</PreprocessorDefinitions>
|
||||||
|
<PreprocessorDefinitions Condition="'$(CommandPaletteBranding)'=='' or '$(CommandPaletteBranding)'=='Dev'">
|
||||||
|
IS_DEV_BRANDING;%(PreprocessorDefinitions)
|
||||||
|
</PreprocessorDefinitions>
|
||||||
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ public:
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::wstring packageName = L"Microsoft.CommandPalette";
|
std::wstring packageName = L"Microsoft.CommandPalette";
|
||||||
#ifdef _DEBUG
|
#ifdef IS_DEV_BRANDING
|
||||||
packageName = L"Microsoft.CommandPalette.Dev";
|
packageName = L"Microsoft.CommandPalette.Dev";
|
||||||
#endif
|
#endif
|
||||||
if (!package::GetRegisteredPackage(packageName, false).has_value())
|
if (!package::GetRegisteredPackage(packageName, false).has_value())
|
||||||
@@ -245,12 +245,21 @@ public:
|
|||||||
errorMessage += e.what();
|
errorMessage += e.what();
|
||||||
Logger::error(errorMessage);
|
Logger::error(errorMessage);
|
||||||
}
|
}
|
||||||
|
try
|
||||||
#if _DEBUG
|
{
|
||||||
LaunchApp(std::wstring{ L"shell:AppsFolder\\" } + L"Microsoft.CommandPalette.Dev_8wekyb3d8bbwe!App", L"RunFromPT", false);
|
#ifdef IS_DEV_BRANDING
|
||||||
|
LaunchApp(std::wstring{ L"shell:AppsFolder\\" } + L"Microsoft.CommandPalette.Dev_8wekyb3d8bbwe!App", L"RunFromPT", false);
|
||||||
#else
|
#else
|
||||||
LaunchApp(std::wstring{ L"shell:AppsFolder\\" } + L"Microsoft.CommandPalette_8wekyb3d8bbwe!App", L"RunFromPT", false);
|
LaunchApp(std::wstring{ L"shell:AppsFolder\\" } + L"Microsoft.CommandPalette_8wekyb3d8bbwe!App", L"RunFromPT", false);
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
std::string errorMessage{ "Exception thrown while trying to launch CmdPal: " };
|
||||||
|
errorMessage += e.what();
|
||||||
|
Logger::error(errorMessage);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void disable()
|
virtual void disable()
|
||||||
|
|||||||
122
tools/build/build-installer.ps1
Normal file
122
tools/build/build-installer.ps1
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Build and package PowerToys (CmdPal and installer) for a specific platform and configuration LOCALLY.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script automates the end-to-end build and packaging process for PowerToys, including:
|
||||||
|
- Restoring and building all necessary solutions (CmdPal, BugReportTool, StylesReportTool, etc.)
|
||||||
|
- Cleaning up old output
|
||||||
|
- Signing generated .msix packages
|
||||||
|
- Building the WiX-based MSI and bootstrapper installers
|
||||||
|
|
||||||
|
It is designed to work in local development.
|
||||||
|
|
||||||
|
.PARAMETER Platform
|
||||||
|
Specifies the target platform for the build (e.g., 'arm64', 'x64'). Default is 'arm64'.
|
||||||
|
|
||||||
|
.PARAMETER Configuration
|
||||||
|
Specifies the build configuration (e.g., 'Debug', 'Release'). Default is 'Release'.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\build-installer.ps1
|
||||||
|
Runs the installer build pipeline for ARM64 Release (default).
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\build-installer.ps1 -Platform x64 -Configuration Release
|
||||||
|
Runs the pipeline for x64 Debug.
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
- Requires MSBuild, WiX Toolset, and Git to be installed and accessible from your environment.
|
||||||
|
- Make sure to run this script from a Developer PowerShell (e.g., VS2022 Developer PowerShell).
|
||||||
|
- Generated MSIX files will be signed using cert-sign-package.ps1.
|
||||||
|
- This script will clean previous outputs under the build directories and installer directory (except *.exe files).
|
||||||
|
- First time run need admin permission to trust the certificate.
|
||||||
|
- The built installer will be placed under: installer/PowerToysSetup/[Platform]/[Configuration]/UserSetup
|
||||||
|
relative to the solution root directory.
|
||||||
|
- The installer can't be run right after the build, I need to copy it to another file before it can be run.
|
||||||
|
#>
|
||||||
|
|
||||||
|
|
||||||
|
param (
|
||||||
|
[string]$Platform = 'arm64',
|
||||||
|
[string]$Configuration = 'Release'
|
||||||
|
)
|
||||||
|
|
||||||
|
$repoRoot = Resolve-Path "$PSScriptRoot\..\.."
|
||||||
|
Set-Location $repoRoot
|
||||||
|
|
||||||
|
function RunMSBuild {
|
||||||
|
param (
|
||||||
|
[string]$Solution,
|
||||||
|
[string]$ExtraArgs
|
||||||
|
)
|
||||||
|
|
||||||
|
$base = @(
|
||||||
|
$Solution
|
||||||
|
"/p:Platform=`"$Platform`""
|
||||||
|
"/p:Configuration=$Configuration"
|
||||||
|
'/verbosity:normal'
|
||||||
|
'/clp:Summary;PerformanceSummary;ErrorsOnly;WarningsOnly'
|
||||||
|
'/nologo'
|
||||||
|
)
|
||||||
|
|
||||||
|
$cmd = $base + ($ExtraArgs -split ' ')
|
||||||
|
Write-Host ("[MSBUILD] {0} {1}" -f $Solution, ($cmd -join ' '))
|
||||||
|
& msbuild.exe @cmd
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error ("Build failed: {0} {1}" -f $Solution, $ExtraArgs)
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function RestoreThenBuild {
|
||||||
|
param ([string]$Solution)
|
||||||
|
|
||||||
|
# 1) restore
|
||||||
|
RunMSBuild $Solution '/t:restore /p:RestorePackagesConfig=true'
|
||||||
|
# 2) build -------------------------------------------------
|
||||||
|
RunMSBuild $Solution '/m'
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ("Make sure wix is installed and available")
|
||||||
|
& "$PSScriptRoot\ensure-wix.ps1"
|
||||||
|
|
||||||
|
Write-Host ("[PIPELINE] Start | Platform={0} Configuration={1}" -f $Platform, $Configuration)
|
||||||
|
Write-Host ''
|
||||||
|
|
||||||
|
$cmdpalOutputPath = Join-Path $repoRoot "$Platform\$Configuration\WinUI3Apps\CmdPal"
|
||||||
|
|
||||||
|
if (Test-Path $cmdpalOutputPath) {
|
||||||
|
Write-Host "[CLEAN] Removing previous output: $cmdpalOutputPath"
|
||||||
|
Remove-Item $cmdpalOutputPath -Recurse -Force -ErrorAction Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
RestoreThenBuild '.\PowerToys.sln'
|
||||||
|
|
||||||
|
$msixSearchRoot = Join-Path $repoRoot "$Platform\$Configuration"
|
||||||
|
$msixFiles = Get-ChildItem -Path $msixSearchRoot -Recurse -Filter *.msix |
|
||||||
|
Select-Object -ExpandProperty FullName
|
||||||
|
|
||||||
|
if ($msixFiles.Count) {
|
||||||
|
Write-Host ("[SIGN] .msix file(s): {0}" -f ($msixFiles -join '; '))
|
||||||
|
& "$PSScriptRoot\cert-sign-package.ps1" -TargetPaths $msixFiles
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Warning "[SIGN] No .msix files found in $msixSearchRoot"
|
||||||
|
}
|
||||||
|
|
||||||
|
RestoreThenBuild '.\tools\BugReportTool\BugReportTool.sln'
|
||||||
|
RestoreThenBuild '.\tools\StylesReportTool\StylesReportTool.sln'
|
||||||
|
|
||||||
|
Write-Host '[CLEAN] installer (keep *.exe)'
|
||||||
|
git clean -xfd -e '*.exe' -- .\installer\ | Out-Null
|
||||||
|
|
||||||
|
RunMSBuild '.\installer\PowerToysSetup.sln' '/t:restore /p:RestorePackagesConfig=true'
|
||||||
|
|
||||||
|
RunMSBuild '.\installer\PowerToysSetup.sln' '/m /t:PowerToysInstaller /p:PerUser=true'
|
||||||
|
|
||||||
|
RunMSBuild '.\installer\PowerToysSetup.sln' '/m /t:PowerToysBootstrapper /p:PerUser=true'
|
||||||
|
|
||||||
|
Write-Host '[PIPELINE] Completed'
|
||||||
159
tools/build/cert-management.ps1
Normal file
159
tools/build/cert-management.ps1
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<#
|
||||||
|
.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 {
|
||||||
|
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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$cert = EnsureCertificate
|
||||||
|
$pswd = ConvertTo-SecureString -String "MySecurePassword123!" -AsPlainText -Force
|
||||||
|
Export-CertificateFiles -Certificate $cert -CerPath "$env:TEMP\cert.cer" -PfxPath "$env:TEMP\cert.pfx" -PfxPassword $pswd
|
||||||
29
tools/build/cert-sign-package.ps1
Normal file
29
tools/build/cert-sign-package.ps1
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
param (
|
||||||
|
[string]$certSubject = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
|
||||||
|
[string[]]$TargetPaths = "C:\PowerToys\ARM64\Release\WinUI3Apps\CmdPal\AppPackages\Microsoft.CmdPal.UI_0.0.1.0_Test\Microsoft.CmdPal.UI_0.0.1.0_arm64.msix"
|
||||||
|
)
|
||||||
|
|
||||||
|
. "$PSScriptRoot\cert-management.ps1"
|
||||||
|
$cert = EnsureCertificate -certSubject $certSubject
|
||||||
|
|
||||||
|
if (-not $cert) {
|
||||||
|
Write-Error "Failed to prepare certificate."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Certificate ready: $($cert.Thumbprint)"
|
||||||
|
|
||||||
|
if (-not $TargetPaths -or $TargetPaths.Count -eq 0) {
|
||||||
|
Write-Error "No target files provided to sign."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($filePath in $TargetPaths) {
|
||||||
|
if (-not (Test-Path $filePath)) {
|
||||||
|
Write-Warning "Skipping: File does not exist - $filePath"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Signing: $filePath"
|
||||||
|
& signtool sign /sha1 $($cert.Thumbprint) /fd SHA256 /t http://timestamp.digicert.com "$filePath"
|
||||||
|
}
|
||||||
71
tools/build/ensure-wix.ps1
Normal file
71
tools/build/ensure-wix.ps1
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Ensure WiX Toolset 3.14 (build 3141) is installed and ready to use.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
- Skips installation if the toolset is already installed (unless -Force is used).
|
||||||
|
- Otherwise downloads the official installer and binaries, verifies SHA-256, installs silently,
|
||||||
|
and copies wix.targets into the installation directory.
|
||||||
|
.PARAMETER Force
|
||||||
|
Forces reinstallation even if the toolset is already detected.
|
||||||
|
.PARAMETER InstallDir
|
||||||
|
The target installation path. Default is 'C:\Program Files (x86)\WiX Toolset v3.14'.
|
||||||
|
.EXAMPLE
|
||||||
|
.\EnsureWix.ps1 # Ensure WiX is installed
|
||||||
|
.\EnsureWix.ps1 -Force # Force reinstall
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[switch]$Force,
|
||||||
|
[string]$InstallDir = 'C:\Program Files (x86)\WiX Toolset v3.14'
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
|
||||||
|
# Download URLs and expected SHA-256 hashes
|
||||||
|
$WixDownloadUrl = 'https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe'
|
||||||
|
$WixBinariesDownloadUrl = 'https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314-binaries.zip'
|
||||||
|
$InstallerHashExpected = '6BF6D03D6923D9EF827AE1D943B90B42B8EBB1B0F68EF6D55F868FA34C738A29'
|
||||||
|
$BinariesHashExpected = '6AC824E1642D6F7277D0ED7EA09411A508F6116BA6FAE0AA5F2C7DAA2FF43D31'
|
||||||
|
|
||||||
|
# Check if WiX is already installed
|
||||||
|
$candlePath = Join-Path $InstallDir 'bin\candle.exe'
|
||||||
|
if (-not $Force -and (Test-Path $candlePath)) {
|
||||||
|
Write-Host "WiX Toolset is already installed at `"$InstallDir`". Skipping installation."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# Temp file paths
|
||||||
|
$tmpDir = [IO.Path]::GetTempPath()
|
||||||
|
$installer = Join-Path $tmpDir 'wix314.exe'
|
||||||
|
$binariesZip = Join-Path $tmpDir 'wix314-binaries.zip'
|
||||||
|
|
||||||
|
# Download installer and binaries
|
||||||
|
Write-Host 'Downloading WiX installer...'
|
||||||
|
Invoke-WebRequest -Uri $WixDownloadUrl -OutFile $installer -UseBasicParsing
|
||||||
|
Write-Host 'Downloading WiX binaries...'
|
||||||
|
Invoke-WebRequest -Uri $WixBinariesDownloadUrl -OutFile $binariesZip -UseBasicParsing
|
||||||
|
|
||||||
|
# Verify SHA-256 hashes
|
||||||
|
Write-Host 'Verifying installer hash...'
|
||||||
|
if ((Get-FileHash -Algorithm SHA256 $installer).Hash -ne $InstallerHashExpected) {
|
||||||
|
throw 'wix314.exe SHA256 hash mismatch'
|
||||||
|
}
|
||||||
|
Write-Host 'Verifying binaries hash...'
|
||||||
|
if ((Get-FileHash -Algorithm SHA256 $binariesZip).Hash -ne $BinariesHashExpected) {
|
||||||
|
throw 'wix314-binaries.zip SHA256 hash mismatch'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Perform silent installation
|
||||||
|
Write-Host 'Installing WiX Toolset silently...'
|
||||||
|
Start-Process -FilePath $installer -ArgumentList '/install','/quiet' -Wait
|
||||||
|
|
||||||
|
# Extract binaries and copy wix.targets
|
||||||
|
$expandDir = Join-Path $tmpDir 'wix-binaries'
|
||||||
|
if (Test-Path $expandDir) { Remove-Item $expandDir -Recurse -Force }
|
||||||
|
Expand-Archive -Path $binariesZip -DestinationPath $expandDir -Force
|
||||||
|
Copy-Item -Path (Join-Path $expandDir 'wix.targets') `
|
||||||
|
-Destination (Join-Path $InstallDir 'wix.targets') -Force
|
||||||
|
|
||||||
|
Write-Host "WiX Toolset has been successfully installed at: $InstallDir"
|
||||||
Reference in New Issue
Block a user