完善 Windows 发布构建与开发证书流程
This commit is contained in:
@@ -9,12 +9,15 @@ for %%A in (%*) do (
|
|||||||
if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%"
|
if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%"
|
||||||
cd /d "%ROOT%"
|
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 ========================================
|
||||||
echo YMhut Box - WinUI 3 Build
|
echo YMhut Box - WinUI 3 Build
|
||||||
echo ========================================
|
echo ========================================
|
||||||
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%"
|
set "EXITCODE=%ERRORLEVEL%"
|
||||||
if not "%EXITCODE%"=="0" (
|
if not "%EXITCODE%"=="0" (
|
||||||
echo.
|
echo.
|
||||||
|
|||||||
+91
-36
@@ -41,7 +41,9 @@ $ServerPublicRoot = Join-Path $Root 'server\update\public'
|
|||||||
$ServerDownloadRoot = Join-Path $ServerPublicRoot 'downloads'
|
$ServerDownloadRoot = Join-Path $ServerPublicRoot 'downloads'
|
||||||
$ToolStateRoot = Join-Path $Root '.cache\tool_state'
|
$ToolStateRoot = Join-Path $Root '.cache\tool_state'
|
||||||
$NuGetRoot = Join-Path $Root '.cache\nuget'
|
$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'
|
$PackageIdentityName = 'YMhut.Box'
|
||||||
$PackagePublisher = 'CN=YMhut'
|
$PackagePublisher = 'CN=YMhut'
|
||||||
@@ -50,6 +52,8 @@ $AppExecutable = 'YMhutBox.exe'
|
|||||||
$PfxPassword = 'ymhut-box-local'
|
$PfxPassword = 'ymhut-box-local'
|
||||||
$PfxPath = Join-Path $OutputRoot 'certs\YMhutBox.pfx'
|
$PfxPath = Join-Path $OutputRoot 'certs\YMhutBox.pfx'
|
||||||
$CerPath = Join-Path $OutputRoot 'YMhutBox.cer'
|
$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/' }
|
$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/' }
|
$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
|
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) {
|
function Find-WindowsSdkTool([string] $Name) {
|
||||||
$roots = @(
|
$roots = @(
|
||||||
(Join-Path $NuGetRoot 'microsoft.windows.sdk.buildtools'),
|
(Join-Path $NuGetRoot 'microsoft.windows.sdk.buildtools'),
|
||||||
@@ -448,45 +506,38 @@ function Ensure-MsixAssets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Ensure-LocalDeveloperCertificate {
|
function Ensure-LocalDeveloperCertificate {
|
||||||
New-Directory (Split-Path -Parent $PfxPath)
|
if ($script:DeveloperCertificatePrepared) {
|
||||||
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
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$secure = ConvertTo-SecureString -String $PfxPassword -AsPlainText -Force
|
if (-not (Test-Path -LiteralPath $WindowsPowerShell)) {
|
||||||
$cert = New-SelfSignedCertificate `
|
throw "Windows PowerShell was not found: $WindowsPowerShell"
|
||||||
-Type CodeSigningCert `
|
}
|
||||||
-Subject $PackagePublisher `
|
if (-not (Test-Path -LiteralPath $CertificateScript)) {
|
||||||
-CertStoreLocation 'Cert:\CurrentUser\My' `
|
throw "Developer certificate helper was not found: $CertificateScript"
|
||||||
-KeyExportPolicy Exportable `
|
}
|
||||||
-KeyUsage DigitalSignature `
|
|
||||||
-FriendlyName 'YMhut Box WinUI local signing'
|
|
||||||
|
|
||||||
Export-PfxCertificate -Cert $cert -FilePath $PfxPath -Password $secure | Out-Null
|
Invoke-ToolQuiet $WindowsPowerShell @(
|
||||||
Export-Certificate -Cert $cert -FilePath $CerPath | Out-Null
|
'-NoLogo', '-NoProfile', '-ExecutionPolicy', 'Bypass',
|
||||||
Import-Certificate -FilePath $CerPath -CertStoreLocation 'Cert:\CurrentUser\Root' | Out-Null
|
'-File', $CertificateScript,
|
||||||
Import-Certificate -FilePath $CerPath -CertStoreLocation 'Cert:\CurrentUser\TrustedPeople' | Out-Null
|
'-PfxPath', $PfxPath,
|
||||||
|
'-CerPath', $CerPath,
|
||||||
|
'-Password', $PfxPassword,
|
||||||
|
'-Subject', $PackagePublisher
|
||||||
|
) 'Developer signing certificate preparation failed'
|
||||||
|
$script:DeveloperCertificatePrepared = $true
|
||||||
}
|
}
|
||||||
|
|
||||||
function Assert-ArtifactSignatureMatchesCertificate([string] $Path) {
|
function Assert-ArtifactSignatureMatchesCertificate([string] $Path) {
|
||||||
$cerCert = [Security.Cryptography.X509Certificates.X509Certificate2]::new($CerPath)
|
Invoke-ToolQuiet $WindowsPowerShell @(
|
||||||
$signature = Get-AuthenticodeSignature -FilePath $Path
|
'-NoLogo', '-NoProfile', '-ExecutionPolicy', 'Bypass',
|
||||||
if (-not $signature.SignerCertificate) {
|
'-File', $CertificateScript,
|
||||||
throw "Signed artifact does not expose a signer certificate: $Path"
|
'-PfxPath', $PfxPath,
|
||||||
}
|
'-CerPath', $CerPath,
|
||||||
|
'-Password', $PfxPassword,
|
||||||
if ($signature.SignerCertificate.Thumbprint -ne $cerCert.Thumbprint) {
|
'-Subject', $PackagePublisher,
|
||||||
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."
|
'-VerifyPath', $Path
|
||||||
}
|
) "Signed artifact certificate validation failed for $Path"
|
||||||
}
|
}
|
||||||
|
|
||||||
function Get-WindowsAppRuntimePackageExtensions {
|
function Get-WindowsAppRuntimePackageExtensions {
|
||||||
@@ -1472,11 +1523,8 @@ pause
|
|||||||
Set-Content -LiteralPath $cmdPath -Value $cmd -Encoding ASCII
|
Set-Content -LiteralPath $cmdPath -Value $cmd -Encoding ASCII
|
||||||
}
|
}
|
||||||
|
|
||||||
New-Directory $AppDataRoot
|
|
||||||
New-Directory $NuGetRoot
|
New-Directory $NuGetRoot
|
||||||
New-Directory $OutputRoot
|
New-Directory $OutputRoot
|
||||||
$env:APPDATA = $AppDataRoot
|
|
||||||
$env:LOCALAPPDATA = $AppDataRoot
|
|
||||||
$env:DOTNET_CLI_HOME = Join-Path $ToolStateRoot 'dotnet'
|
$env:DOTNET_CLI_HOME = Join-Path $ToolStateRoot 'dotnet'
|
||||||
$env:NUGET_PACKAGES = $NuGetRoot
|
$env:NUGET_PACKAGES = $NuGetRoot
|
||||||
$env:DOTNET_CLI_TELEMETRY_OPTOUT = '1'
|
$env:DOTNET_CLI_TELEMETRY_OPTOUT = '1'
|
||||||
@@ -1491,6 +1539,7 @@ Write-Host " Target: $Target"
|
|||||||
Write-Host " Version: $($versionInfo.Version) build $($versionInfo.Build) ($($versionInfo.PackageVersion))"
|
Write-Host " Version: $($versionInfo.Version) build $($versionInfo.Build) ($($versionInfo.PackageVersion))"
|
||||||
|
|
||||||
Ensure-MsixAssets
|
Ensure-MsixAssets
|
||||||
|
$tauriHostExecutable = Ensure-TauriPluginHost
|
||||||
Invoke-DotNet @(
|
Invoke-DotNet @(
|
||||||
'restore',
|
'restore',
|
||||||
$Solution,
|
$Solution,
|
||||||
@@ -1514,6 +1563,12 @@ foreach ($ridProject in @($Project, $InstallerBootstrapProject)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Publish-UnpackagedApp $versionInfo
|
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) {
|
if (($Target -in @('exe', 'both')) -and -not $SkipExe) {
|
||||||
$exeBuilt = Build-InnoInstaller $versionInfo $signTool -AllowMissingCompiler:($Target -eq 'both')
|
$exeBuilt = Build-InnoInstaller $versionInfo $signTool -AllowMissingCompiler:($Target -eq 'both')
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user