完善 Windows 发布构建与开发证书流程

This commit is contained in:
2026-08-17 09:53:28 +08:00
parent c3a8737fd6
commit 337390f53e
3 changed files with 171 additions and 37 deletions
+4 -1
View File
@@ -9,12 +9,15 @@ for %%A in (%*) do (
if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%"
cd /d "%ROOT%"
set "POWERSHELL_EXE=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"
if not exist "%POWERSHELL_EXE%" set "POWERSHELL_EXE=powershell.exe"
echo ========================================
echo YMhut Box - WinUI 3 Build
echo ========================================
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%ROOT%\scripts\build-winui.ps1" %*
"%POWERSHELL_EXE%" -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%ROOT%\scripts\build-winui.ps1" %*
set "EXITCODE=%ERRORLEVEL%"
if not "%EXITCODE%"=="0" (
echo.
+91 -36
View File
@@ -41,7 +41,9 @@ $ServerPublicRoot = Join-Path $Root 'server\update\public'
$ServerDownloadRoot = Join-Path $ServerPublicRoot 'downloads'
$ToolStateRoot = Join-Path $Root '.cache\tool_state'
$NuGetRoot = Join-Path $Root '.cache\nuget'
$AppDataRoot = Join-Path $ToolStateRoot 'appdata'
$TauriHostRoot = Join-Path $Root 'src\YMhut.Box.PluginTauriHost\src-tauri'
$RepositoryCargoHome = Join-Path $Root '.cache\rust-toolchain\cargo'
$RepositoryRustupHome = Join-Path $Root '.cache\rust-toolchain\rustup'
$PackageIdentityName = 'YMhut.Box'
$PackagePublisher = 'CN=YMhut'
@@ -50,6 +52,8 @@ $AppExecutable = 'YMhutBox.exe'
$PfxPassword = 'ymhut-box-local'
$PfxPath = Join-Path $OutputRoot 'certs\YMhutBox.pfx'
$CerPath = Join-Path $OutputRoot 'YMhutBox.cer'
$CertificateScript = Join-Path $PSScriptRoot 'dev-certificate.ps1'
$WindowsPowerShell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
$DownloadBaseUri = if ($env:YMHUT_DOWNLOAD_BASE_URI) { $env:YMHUT_DOWNLOAD_BASE_URI.TrimEnd('/') + '/' } else { 'https://update.ymhut.cn/downloads/' }
$UpdateBaseUri = if ($env:YMHUT_UPDATE_BASE_URI) { $env:YMHUT_UPDATE_BASE_URI.TrimEnd('/') + '/' } else { 'https://update.ymhut.cn/update-info/' }
@@ -290,6 +294,60 @@ function Find-Executable([string] $Name, [string[]] $Candidates = @(), [string[]
return $null
}
function Find-Cargo {
$fromPath = Get-Command 'cargo.exe' -ErrorAction SilentlyContinue
if ($fromPath) {
return $fromPath.Source
}
$repositoryCargo = Join-Path $RepositoryCargoHome 'bin\cargo.exe'
if ((Test-Path -LiteralPath $repositoryCargo) -and (Test-Path -LiteralPath $RepositoryRustupHome)) {
$env:CARGO_HOME = $RepositoryCargoHome
$env:RUSTUP_HOME = $RepositoryRustupHome
return $repositoryCargo
}
return $null
}
function Ensure-TauriPluginHost {
if (-not (Test-Path -LiteralPath (Join-Path $TauriHostRoot 'Cargo.toml'))) {
return $null
}
$profile = if ($Configuration -ieq 'Release') { 'release' } else { 'debug' }
$executable = Join-Path $TauriHostRoot "target\$profile\ymhut-box-plugin-tauri-host.exe"
$cargo = Find-Cargo
if (-not $cargo) {
if (Test-Path -LiteralPath $executable) {
Write-Warning "Cargo was not found. Reusing the existing Tauri plugin host: $executable"
return $executable
}
Write-Warning 'Cargo was not found, so the optional Tauri plugin host will not be included. Install the Rust stable MSVC toolchain to build ExternalRuntime support.'
return $null
}
$cargoArguments = @('build', '--locked')
if ($profile -eq 'release') {
$cargoArguments += '--release'
}
Push-Location $TauriHostRoot
try {
Invoke-Tool $cargo $cargoArguments 'Tauri plugin host build failed'
Write-Host " Tauri plugin host: $executable"
} finally {
Pop-Location
}
if (-not (Test-Path -LiteralPath $executable)) {
throw "Cargo completed without producing the Tauri plugin host: $executable"
}
return $executable
}
function Find-WindowsSdkTool([string] $Name) {
$roots = @(
(Join-Path $NuGetRoot 'microsoft.windows.sdk.buildtools'),
@@ -448,45 +506,38 @@ function Ensure-MsixAssets {
}
function Ensure-LocalDeveloperCertificate {
New-Directory (Split-Path -Parent $PfxPath)
if ((Test-Path -LiteralPath $PfxPath) -and (Test-Path -LiteralPath $CerPath)) {
$secure = ConvertTo-SecureString -String $PfxPassword -AsPlainText -Force
$pfxCert = [Security.Cryptography.X509Certificates.X509Certificate2]::new($PfxPath, $secure)
$cerCert = [Security.Cryptography.X509Certificates.X509Certificate2]::new($CerPath)
if ($pfxCert.Thumbprint -ne $cerCert.Thumbprint) {
Export-Certificate -Cert $pfxCert -FilePath $CerPath -Force | Out-Null
}
Import-Certificate -FilePath $CerPath -CertStoreLocation 'Cert:\CurrentUser\Root' | Out-Null
Import-Certificate -FilePath $CerPath -CertStoreLocation 'Cert:\CurrentUser\TrustedPeople' | Out-Null
if ($script:DeveloperCertificatePrepared) {
return
}
$secure = ConvertTo-SecureString -String $PfxPassword -AsPlainText -Force
$cert = New-SelfSignedCertificate `
-Type CodeSigningCert `
-Subject $PackagePublisher `
-CertStoreLocation 'Cert:\CurrentUser\My' `
-KeyExportPolicy Exportable `
-KeyUsage DigitalSignature `
-FriendlyName 'YMhut Box WinUI local signing'
if (-not (Test-Path -LiteralPath $WindowsPowerShell)) {
throw "Windows PowerShell was not found: $WindowsPowerShell"
}
if (-not (Test-Path -LiteralPath $CertificateScript)) {
throw "Developer certificate helper was not found: $CertificateScript"
}
Export-PfxCertificate -Cert $cert -FilePath $PfxPath -Password $secure | Out-Null
Export-Certificate -Cert $cert -FilePath $CerPath | Out-Null
Import-Certificate -FilePath $CerPath -CertStoreLocation 'Cert:\CurrentUser\Root' | Out-Null
Import-Certificate -FilePath $CerPath -CertStoreLocation 'Cert:\CurrentUser\TrustedPeople' | Out-Null
Invoke-ToolQuiet $WindowsPowerShell @(
'-NoLogo', '-NoProfile', '-ExecutionPolicy', 'Bypass',
'-File', $CertificateScript,
'-PfxPath', $PfxPath,
'-CerPath', $CerPath,
'-Password', $PfxPassword,
'-Subject', $PackagePublisher
) 'Developer signing certificate preparation failed'
$script:DeveloperCertificatePrepared = $true
}
function Assert-ArtifactSignatureMatchesCertificate([string] $Path) {
$cerCert = [Security.Cryptography.X509Certificates.X509Certificate2]::new($CerPath)
$signature = Get-AuthenticodeSignature -FilePath $Path
if (-not $signature.SignerCertificate) {
throw "Signed artifact does not expose a signer certificate: $Path"
}
if ($signature.SignerCertificate.Thumbprint -ne $cerCert.Thumbprint) {
throw "Signed artifact certificate thumbprint '$($signature.SignerCertificate.Thumbprint)' does not match exported certificate '$($cerCert.Thumbprint)'. Rebuild the package and keep the matching YMhutBox.cer next to it."
}
Invoke-ToolQuiet $WindowsPowerShell @(
'-NoLogo', '-NoProfile', '-ExecutionPolicy', 'Bypass',
'-File', $CertificateScript,
'-PfxPath', $PfxPath,
'-CerPath', $CerPath,
'-Password', $PfxPassword,
'-Subject', $PackagePublisher,
'-VerifyPath', $Path
) "Signed artifact certificate validation failed for $Path"
}
function Get-WindowsAppRuntimePackageExtensions {
@@ -1472,11 +1523,8 @@ pause
Set-Content -LiteralPath $cmdPath -Value $cmd -Encoding ASCII
}
New-Directory $AppDataRoot
New-Directory $NuGetRoot
New-Directory $OutputRoot
$env:APPDATA = $AppDataRoot
$env:LOCALAPPDATA = $AppDataRoot
$env:DOTNET_CLI_HOME = Join-Path $ToolStateRoot 'dotnet'
$env:NUGET_PACKAGES = $NuGetRoot
$env:DOTNET_CLI_TELEMETRY_OPTOUT = '1'
@@ -1491,6 +1539,7 @@ Write-Host " Target: $Target"
Write-Host " Version: $($versionInfo.Version) build $($versionInfo.Build) ($($versionInfo.PackageVersion))"
Ensure-MsixAssets
$tauriHostExecutable = Ensure-TauriPluginHost
Invoke-DotNet @(
'restore',
$Solution,
@@ -1514,6 +1563,12 @@ foreach ($ridProject in @($Project, $InstallerBootstrapProject)) {
}
Publish-UnpackagedApp $versionInfo
if ($tauriHostExecutable) {
$publishedTauriHost = Join-Path $PublishRoot 'tauri-host\ymhut-box-plugin-tauri-host.exe'
if (-not (Test-Path -LiteralPath $publishedTauriHost)) {
throw "The Tauri plugin host was built but was not included in the publish payload: $publishedTauriHost"
}
}
if (($Target -in @('exe', 'both')) -and -not $SkipExe) {
$exeBuilt = Build-InnoInstaller $versionInfo $signTool -AllowMissingCompiler:($Target -eq 'both')
+76
View File
@@ -0,0 +1,76 @@
param(
[Parameter(Mandatory = $true)]
[string] $PfxPath,
[Parameter(Mandatory = $true)]
[string] $CerPath,
[Parameter(Mandatory = $true)]
[string] $Password,
[Parameter(Mandatory = $true)]
[string] $Subject,
[string] $VerifyPath = ''
)
$ErrorActionPreference = 'Stop'
$securityModule = Join-Path $PSHOME 'Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1'
$pkiModule = Join-Path $PSHOME 'Modules\PKI\PKI.psd1'
Import-Module $securityModule -ErrorAction Stop
Import-Module $pkiModule -ErrorAction Stop
if ($VerifyPath) {
if (-not (Test-Path -LiteralPath $VerifyPath)) {
throw "Signed artifact was not found: $VerifyPath"
}
if (-not (Test-Path -LiteralPath $CerPath)) {
throw "Signing certificate was not found: $CerPath"
}
$certificate = [Security.Cryptography.X509Certificates.X509Certificate2]::new($CerPath)
$signature = Get-AuthenticodeSignature -FilePath $VerifyPath
if (-not $signature.SignerCertificate) {
throw "Signed artifact does not expose a signer certificate: $VerifyPath"
}
if ($signature.SignerCertificate.Thumbprint -ne $certificate.Thumbprint) {
throw "Signed artifact certificate does not match the exported developer certificate: $VerifyPath"
}
exit 0
}
$parent = Split-Path -Parent $PfxPath
if ($parent) {
New-Item -ItemType Directory -Force -Path $parent | Out-Null
}
$securePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
if (Test-Path -LiteralPath $PfxPath) {
$certificate = [Security.Cryptography.X509Certificates.X509Certificate2]::new($PfxPath, $securePassword)
$cerMatches = $false
if (Test-Path -LiteralPath $CerPath) {
$existingCer = [Security.Cryptography.X509Certificates.X509Certificate2]::new($CerPath)
$cerMatches = $existingCer.Thumbprint -eq $certificate.Thumbprint
}
if (-not $cerMatches) {
Export-Certificate -Cert $certificate -FilePath $CerPath -Force | Out-Null
}
} else {
$certificate = New-SelfSignedCertificate `
-Type CodeSigningCert `
-Subject $Subject `
-CertStoreLocation 'Cert:\CurrentUser\My' `
-KeyExportPolicy Exportable `
-KeyUsage DigitalSignature `
-FriendlyName 'YMhut Box WinUI local signing'
Export-PfxCertificate -Cert $certificate -FilePath $PfxPath -Password $securePassword | Out-Null
Export-Certificate -Cert $certificate -FilePath $CerPath -Force | Out-Null
}
foreach ($store in @('Cert:\CurrentUser\Root', 'Cert:\CurrentUser\TrustedPeople')) {
$present = Get-ChildItem -Path $store | Where-Object Thumbprint -eq $certificate.Thumbprint
if (-not $present) {
Import-Certificate -FilePath $CerPath -CertStoreLocation $store | Out-Null
}
}