1445 lines
53 KiB
PowerShell
1445 lines
53 KiB
PowerShell
param()
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$Target = 'both'
|
|
$Configuration = 'Release'
|
|
$Platform = 'x64'
|
|
$SkipTests = $false
|
|
$SkipExe = $false
|
|
|
|
foreach ($arg in $args) {
|
|
switch -Regex ($arg) {
|
|
'^--?target=(.+)$' { $Target = $Matches[1].ToLowerInvariant(); continue }
|
|
'^--?configuration=(.+)$' { $Configuration = $Matches[1]; continue }
|
|
'^--?platform=(.+)$' { $Platform = $Matches[1]; continue }
|
|
'^--?skip-tests$' { $SkipTests = $true; continue }
|
|
'^--?skip-msix$' { if ($Target -eq 'both') { $Target = 'exe' }; continue }
|
|
'^--?skip-exe$' { $SkipExe = $true; continue }
|
|
'^--?no-pause$' { continue }
|
|
default { throw "Unknown build argument: $arg" }
|
|
}
|
|
}
|
|
|
|
if (@('publish', 'exe', 'msix', 'both') -notcontains $Target) {
|
|
throw "Invalid target '$Target'. Use --target=publish|exe|msix|both."
|
|
}
|
|
|
|
$Root = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
|
$Solution = Join-Path $Root 'YMhut.Box.Native.sln'
|
|
$Project = Join-Path $Root 'src\box-winUI\YMhut.Box.WinUI.csproj'
|
|
$ProjectAssets = Join-Path $Root 'src\box-winUI\Assets'
|
|
$NuGetConfig = Join-Path $Root 'NuGet.Config'
|
|
$PublishRoot = Join-Path $Root 'build\winui\publish'
|
|
$MsixStageRoot = Join-Path $Root 'build\winui\msix-stage'
|
|
$BuildLogRoot = Join-Path $Root 'build\winui\logs'
|
|
$LatestRoot = Join-Path $Root 'latest'
|
|
$OutputRoot = Join-Path $Root 'installer_output'
|
|
$OutputUpdateInfoRoot = Join-Path $OutputRoot 'update-info'
|
|
$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'
|
|
$LocalNuGetFeedRoot = Join-Path $NuGetRoot 'feed'
|
|
$AppDataRoot = Join-Path $ToolStateRoot 'appdata'
|
|
$HomeRoot = Join-Path $ToolStateRoot 'home'
|
|
|
|
$PackageIdentityName = 'YMhut.Box'
|
|
$PackagePublisher = 'CN=YMhut'
|
|
$AppName = 'YMhut Box'
|
|
$AppExecutable = 'YMhutBox.exe'
|
|
$PfxPassword = 'ymhut-box-local'
|
|
$PfxPath = Join-Path $OutputRoot 'certs\YMhutBox.pfx'
|
|
$CerPath = Join-Path $OutputRoot 'YMhutBox.cer'
|
|
|
|
$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/' }
|
|
|
|
function New-Directory([string] $Path) {
|
|
New-Item -ItemType Directory -Force -Path $Path | Out-Null
|
|
}
|
|
|
|
function Write-Utf8NoBomFile([string] $Path, [string] $Value) {
|
|
$parent = Split-Path -Parent $Path
|
|
if ($parent) {
|
|
New-Directory $parent
|
|
}
|
|
|
|
$encoding = New-Object System.Text.UTF8Encoding -ArgumentList $false
|
|
[IO.File]::WriteAllText($Path, $Value, $encoding)
|
|
}
|
|
|
|
function Reset-DirectoryInsideRepo([string] $Path) {
|
|
$resolvedRoot = [IO.Path]::GetFullPath($Root)
|
|
$resolvedPath = [IO.Path]::GetFullPath($Path)
|
|
if (-not $resolvedPath.StartsWith($resolvedRoot, [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "Refusing to reset a directory outside the repository: $resolvedPath"
|
|
}
|
|
|
|
if (Test-Path -LiteralPath $resolvedPath) {
|
|
Remove-Item -LiteralPath $resolvedPath -Recurse -Force
|
|
}
|
|
New-Directory $resolvedPath
|
|
}
|
|
|
|
function Remove-DirectoryInsideRepo([string] $Path) {
|
|
$resolvedRoot = [IO.Path]::GetFullPath($Root)
|
|
$resolvedPath = [IO.Path]::GetFullPath($Path)
|
|
if (-not $resolvedPath.StartsWith($resolvedRoot, [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "Refusing to remove a directory outside the repository: $resolvedPath"
|
|
}
|
|
|
|
if (Test-Path -LiteralPath $resolvedPath) {
|
|
Remove-Item -LiteralPath $resolvedPath -Recurse -Force
|
|
}
|
|
}
|
|
|
|
function Stop-ProcessesFromDirectory([string] $Directory) {
|
|
if (-not (Test-Path -LiteralPath $Directory)) {
|
|
return
|
|
}
|
|
|
|
$resolvedDirectory = [IO.Path]::GetFullPath($Directory).TrimEnd('\', '/') + [IO.Path]::DirectorySeparatorChar
|
|
$stopped = 0
|
|
foreach ($process in Get-Process -ErrorAction SilentlyContinue) {
|
|
$processPath = $null
|
|
try {
|
|
$processPath = $process.Path
|
|
} catch {
|
|
continue
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($processPath)) {
|
|
continue
|
|
}
|
|
|
|
$resolvedProcessPath = [IO.Path]::GetFullPath($processPath)
|
|
if (-not $resolvedProcessPath.StartsWith($resolvedDirectory, [StringComparison]::OrdinalIgnoreCase)) {
|
|
continue
|
|
}
|
|
|
|
Write-Host " Stopping running payload process: $($process.ProcessName) ($($process.Id))"
|
|
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
|
|
$stopped++
|
|
}
|
|
|
|
if ($stopped -gt 0) {
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
}
|
|
|
|
function Invoke-Tool([string] $FilePath, [string[]] $Arguments, [string] $FailureMessage) {
|
|
& $FilePath @Arguments
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "$FailureMessage (exit code $LASTEXITCODE)"
|
|
}
|
|
}
|
|
|
|
function Join-ProcessArguments([string[]] $Arguments) {
|
|
$quotedArguments = [Collections.Generic.List[string]]::new()
|
|
foreach ($item in $Arguments) {
|
|
$argument = [string]$item
|
|
if ($argument.Length -eq 0) {
|
|
$quotedArguments.Add('""')
|
|
continue
|
|
}
|
|
|
|
if ($argument -notmatch '[\s"]') {
|
|
$quotedArguments.Add($argument)
|
|
continue
|
|
}
|
|
|
|
$builder = [Text.StringBuilder]::new()
|
|
[void]$builder.Append('"')
|
|
$backslashes = 0
|
|
foreach ($character in $argument.ToCharArray()) {
|
|
if ($character -eq '\') {
|
|
$backslashes++
|
|
continue
|
|
}
|
|
|
|
if ($character -eq '"') {
|
|
[void]$builder.Append(('\' * (($backslashes * 2) + 1)))
|
|
[void]$builder.Append('"')
|
|
$backslashes = 0
|
|
continue
|
|
}
|
|
|
|
if ($backslashes -gt 0) {
|
|
[void]$builder.Append(('\' * $backslashes))
|
|
$backslashes = 0
|
|
}
|
|
[void]$builder.Append($character)
|
|
}
|
|
|
|
if ($backslashes -gt 0) {
|
|
[void]$builder.Append(('\' * ($backslashes * 2)))
|
|
}
|
|
[void]$builder.Append('"')
|
|
$quotedArguments.Add($builder.ToString())
|
|
}
|
|
|
|
$quotedArguments -join ' '
|
|
}
|
|
|
|
function Get-LogTail([string] $Path, [int] $Lines = 24) {
|
|
if (-not (Test-Path -LiteralPath $Path)) {
|
|
return ''
|
|
}
|
|
|
|
$text = (Get-Content -LiteralPath $Path -Tail $Lines -ErrorAction SilentlyContinue) -join [Environment]::NewLine
|
|
if ([string]::IsNullOrWhiteSpace($text)) { return '' }
|
|
$text.Trim()
|
|
}
|
|
|
|
function Invoke-ToolQuiet([string] $FilePath, [string[]] $Arguments, [string] $FailureMessage, [string] $SuccessMessage = '') {
|
|
New-Directory $BuildLogRoot
|
|
$toolName = [IO.Path]::GetFileNameWithoutExtension($FilePath)
|
|
if ([string]::IsNullOrWhiteSpace($toolName)) {
|
|
$toolName = ($FilePath -replace '[^A-Za-z0-9_.-]', '_')
|
|
}
|
|
|
|
$stamp = [DateTimeOffset]::Now.ToString('yyyyMMdd-HHmmss-fff')
|
|
$stdoutPath = Join-Path $BuildLogRoot "$toolName-$stamp.out.log"
|
|
$stderrPath = Join-Path $BuildLogRoot "$toolName-$stamp.err.log"
|
|
$process = Start-Process `
|
|
-FilePath $FilePath `
|
|
-ArgumentList (Join-ProcessArguments $Arguments) `
|
|
-NoNewWindow `
|
|
-Wait `
|
|
-PassThru `
|
|
-RedirectStandardOutput $stdoutPath `
|
|
-RedirectStandardError $stderrPath
|
|
$exitCode = $process.ExitCode
|
|
|
|
if ($exitCode -ne 0) {
|
|
$stdoutTail = Get-LogTail $stdoutPath
|
|
$stderrTail = Get-LogTail $stderrPath
|
|
if ($stdoutTail) {
|
|
Write-Host $stdoutTail
|
|
}
|
|
if ($stderrTail) {
|
|
Write-Host $stderrTail
|
|
}
|
|
throw "$FailureMessage (exit code $exitCode). Logs: $stdoutPath $stderrPath"
|
|
}
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($SuccessMessage)) {
|
|
Write-Host $SuccessMessage
|
|
}
|
|
}
|
|
|
|
function Invoke-DotNet([string[]] $Arguments) {
|
|
Invoke-Tool 'dotnet' $Arguments 'dotnet command failed'
|
|
}
|
|
|
|
function Get-VersionInfo {
|
|
$versionFile = Join-Path $Root 'version.json'
|
|
if (-not (Test-Path -LiteralPath $versionFile)) {
|
|
return [pscustomobject]@{
|
|
Version = '2.0.0'
|
|
Build = '1'
|
|
Channel = 'stable'
|
|
PackageVersion = '2.0.0.1'
|
|
}
|
|
}
|
|
|
|
$json = Get-Content -LiteralPath $versionFile -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
$version = if ($json.version) { [string]$json.version } else { '2.0.0' }
|
|
$build = if ($json.build) { ([string]$json.build -replace '[^0-9]', '') } else { '1' }
|
|
if ([string]::IsNullOrWhiteSpace($build)) { $build = '1' }
|
|
$channel = if ($json.channel) { [string]$json.channel } else { 'stable' }
|
|
|
|
$parts = @()
|
|
foreach ($part in ($version -split '\.')) {
|
|
$digits = ($part -replace '[^0-9]', '')
|
|
if ($digits) { $parts += [int]$digits }
|
|
}
|
|
while ($parts.Count -lt 3) { $parts += 0 }
|
|
$parts = $parts | Select-Object -First 3
|
|
$packageVersion = (($parts + [int]$build) | ForEach-Object { [Math]::Min($_, 65535) }) -join '.'
|
|
|
|
[pscustomobject]@{
|
|
Version = $version
|
|
Build = $build
|
|
Channel = $channel
|
|
PackageVersion = $packageVersion
|
|
}
|
|
}
|
|
|
|
function Find-Executable([string] $Name, [string[]] $Candidates = @(), [string[]] $SearchRoots = @()) {
|
|
$fromPath = Get-Command $Name -ErrorAction SilentlyContinue
|
|
if ($fromPath) { return $fromPath.Source }
|
|
|
|
foreach ($candidate in $Candidates) {
|
|
if ($candidate -and (Test-Path -LiteralPath $candidate)) {
|
|
return (Resolve-Path -LiteralPath $candidate).Path
|
|
}
|
|
}
|
|
|
|
foreach ($rootPath in ($SearchRoots | Where-Object { $_ } | Select-Object -Unique)) {
|
|
if (-not (Test-Path -LiteralPath $rootPath)) {
|
|
continue
|
|
}
|
|
|
|
$tool = Get-ChildItem -LiteralPath $rootPath -Recurse -Filter $Name -File -ErrorAction SilentlyContinue |
|
|
Sort-Object FullName -Descending |
|
|
Select-Object -First 1 -ExpandProperty FullName
|
|
if ($tool) { return $tool }
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Find-WindowsSdkTool([string] $Name) {
|
|
$roots = @(
|
|
(Join-Path $NuGetRoot 'microsoft.windows.sdk.buildtools'),
|
|
(Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\bin'),
|
|
(Join-Path $env:ProgramFiles 'Windows Kits\10\bin'),
|
|
(Join-Path ${env:ProgramFiles(x86)} 'Microsoft SDKs\ClickOnce'),
|
|
(Join-Path $env:ProgramFiles 'Microsoft SDKs\ClickOnce')
|
|
)
|
|
if ($env:WindowsSdkDir) {
|
|
$roots += Join-Path $env:WindowsSdkDir 'bin'
|
|
}
|
|
|
|
$candidates = @()
|
|
foreach ($rootPath in ($roots | Where-Object { $_ } | Select-Object -Unique)) {
|
|
if (Test-Path -LiteralPath $rootPath) {
|
|
$candidates += Get-ChildItem -LiteralPath $rootPath -Recurse -Filter $Name -File -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
if ($candidates.Count -eq 0) {
|
|
return Find-Executable $Name
|
|
}
|
|
|
|
$architecturePreference = switch ($Platform.ToLowerInvariant()) {
|
|
'arm64' { @('\arm64\', '\x64\', '\x86\') }
|
|
'x86' { @('\x86\', '\x64\', '\arm64\') }
|
|
default { @('\x64\', '\amd64\', '\x86\', '\arm64\') }
|
|
}
|
|
foreach ($architecture in $architecturePreference) {
|
|
$tool = $candidates |
|
|
Where-Object { $_.FullName.IndexOf($architecture, [StringComparison]::OrdinalIgnoreCase) -ge 0 } |
|
|
Sort-Object FullName -Descending |
|
|
Select-Object -First 1
|
|
if ($tool) {
|
|
return $tool.FullName
|
|
}
|
|
}
|
|
|
|
($candidates | Sort-Object FullName -Descending | Select-Object -First 1).FullName
|
|
}
|
|
|
|
function Find-InnoSetupCompiler {
|
|
$envCandidates = @()
|
|
if ($env:YMHUT_INNO_SETUP) {
|
|
$envCandidates += if ((Split-Path -Leaf $env:YMHUT_INNO_SETUP) -ieq 'ISCC.exe') {
|
|
$env:YMHUT_INNO_SETUP
|
|
} else {
|
|
Join-Path $env:YMHUT_INNO_SETUP 'ISCC.exe'
|
|
}
|
|
}
|
|
if ($env:INNO_SETUP_HOME) {
|
|
$envCandidates += Join-Path $env:INNO_SETUP_HOME 'ISCC.exe'
|
|
}
|
|
|
|
$candidates = @(
|
|
$envCandidates,
|
|
'E:\Inno Setup 7\ISCC.exe',
|
|
(Join-Path ${env:ProgramFiles(x86)} 'Inno Setup 7\ISCC.exe'),
|
|
(Join-Path ${env:ProgramFiles(x86)} 'Inno Setup 6\ISCC.exe'),
|
|
(Join-Path $env:ProgramFiles 'Inno Setup 7\ISCC.exe'),
|
|
(Join-Path $env:ProgramFiles 'Inno Setup 6\ISCC.exe')
|
|
) | ForEach-Object { $_ }
|
|
Find-Executable 'ISCC.exe' -Candidates $candidates
|
|
}
|
|
|
|
function Resolve-InnoLanguageFile([string] $IsccPath) {
|
|
$compilerRoot = Split-Path -Parent $IsccPath
|
|
$chineseLanguageFile = Join-Path $compilerRoot 'Languages\ChineseSimplified.isl'
|
|
if (Test-Path -LiteralPath $chineseLanguageFile) {
|
|
return (Resolve-Path -LiteralPath $chineseLanguageFile).Path
|
|
}
|
|
|
|
$repoLanguageFile = Join-Path $Root 'installer\Languages\ChineseSimplified.isl'
|
|
if (Test-Path -LiteralPath $repoLanguageFile) {
|
|
return (Resolve-Path -LiteralPath $repoLanguageFile).Path
|
|
}
|
|
|
|
Write-Warning "ChineseSimplified.isl was not found under '$compilerRoot\Languages'. Falling back to compiler:Default.isl; custom Chinese task/runtime messages remain active."
|
|
return 'compiler:Default.isl'
|
|
}
|
|
|
|
function Save-ScaledLogo {
|
|
param(
|
|
[string] $SourcePath,
|
|
[string] $DestinationPath,
|
|
[int] $Width,
|
|
[int] $Height,
|
|
[int] $Padding,
|
|
[string] $BackgroundHex = '#00FFFFFF',
|
|
[switch] $Wordmark
|
|
)
|
|
|
|
Add-Type -AssemblyName System.Drawing
|
|
$source = [Drawing.Image]::FromFile($SourcePath)
|
|
$bitmap = New-Object Drawing.Bitmap($Width, $Height, [Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
$graphics = [Drawing.Graphics]::FromImage($bitmap)
|
|
try {
|
|
$graphics.SmoothingMode = [Drawing.Drawing2D.SmoothingMode]::HighQuality
|
|
$graphics.InterpolationMode = [Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
|
|
$graphics.CompositingQuality = [Drawing.Drawing2D.CompositingQuality]::HighQuality
|
|
$graphics.TextRenderingHint = [Drawing.Text.TextRenderingHint]::ClearTypeGridFit
|
|
$graphics.Clear([Drawing.ColorTranslator]::FromHtml($BackgroundHex))
|
|
|
|
if ($Wordmark) {
|
|
$iconSize = [Math]::Min($Height - ($Padding * 2), [Math]::Min(112, $Width / 3))
|
|
$iconX = $Padding + [Math]::Max(0, ($Height - $iconSize) / 5)
|
|
$iconY = ($Height - $iconSize) / 2
|
|
$graphics.DrawImage($source, [Drawing.RectangleF]::new($iconX, $iconY, $iconSize, $iconSize))
|
|
|
|
$titleFont = New-Object Drawing.Font('Segoe UI Semibold', [Math]::Max(18, [Math]::Min(34, $Height / 6)))
|
|
$subFont = New-Object Drawing.Font('Segoe UI', [Math]::Max(10, [Math]::Min(16, $Height / 12)))
|
|
$titleBrush = New-Object Drawing.SolidBrush([Drawing.ColorTranslator]::FromHtml('#111827'))
|
|
$subBrush = New-Object Drawing.SolidBrush([Drawing.ColorTranslator]::FromHtml('#56616A'))
|
|
try {
|
|
$textX = $iconX + $iconSize + $Padding
|
|
$titleY = ($Height / 2) - ($titleFont.Size * 1.15)
|
|
$graphics.DrawString('YMhut Box', $titleFont, $titleBrush, [Drawing.PointF]::new($textX, $titleY))
|
|
$graphics.DrawString('WinUI 3 toolbox', $subFont, $subBrush, [Drawing.PointF]::new($textX + 2, $titleY + ($titleFont.Size * 1.6)))
|
|
} finally {
|
|
$titleFont.Dispose()
|
|
$subFont.Dispose()
|
|
$titleBrush.Dispose()
|
|
$subBrush.Dispose()
|
|
}
|
|
} else {
|
|
$maxWidth = $Width - ($Padding * 2)
|
|
$maxHeight = $Height - ($Padding * 2)
|
|
$scale = [Math]::Min($maxWidth / $source.Width, $maxHeight / $source.Height)
|
|
$drawWidth = $source.Width * $scale
|
|
$drawHeight = $source.Height * $scale
|
|
$drawX = ($Width - $drawWidth) / 2
|
|
$drawY = ($Height - $drawHeight) / 2
|
|
$graphics.DrawImage($source, [Drawing.RectangleF]::new($drawX, $drawY, $drawWidth, $drawHeight))
|
|
}
|
|
|
|
New-Directory (Split-Path -Parent $DestinationPath)
|
|
$bitmap.Save($DestinationPath, [Drawing.Imaging.ImageFormat]::Png)
|
|
} finally {
|
|
$graphics.Dispose()
|
|
$bitmap.Dispose()
|
|
$source.Dispose()
|
|
}
|
|
}
|
|
|
|
function Ensure-MsixAssets {
|
|
$sourceIcon = Join-Path $Root 'assets\icons\app_icon.png'
|
|
if (-not (Test-Path -LiteralPath $sourceIcon)) {
|
|
throw "MSIX source icon was not found: $sourceIcon"
|
|
}
|
|
|
|
New-Directory $ProjectAssets
|
|
Save-ScaledLogo $sourceIcon (Join-Path $ProjectAssets 'StoreLogo.png') 50 50 5 '#00FFFFFF'
|
|
Save-ScaledLogo $sourceIcon (Join-Path $ProjectAssets 'Square44x44Logo.png') 44 44 4 '#00FFFFFF'
|
|
Save-ScaledLogo $sourceIcon (Join-Path $ProjectAssets 'Square150x150Logo.png') 150 150 16 '#00FFFFFF'
|
|
Save-ScaledLogo $sourceIcon (Join-Path $ProjectAssets 'LockScreenLogo.png') 70 70 8 '#00FFFFFF'
|
|
Save-ScaledLogo $sourceIcon (Join-Path $ProjectAssets 'Wide310x150Logo.png') 310 150 24 '#F5F6F7' -Wordmark
|
|
}
|
|
|
|
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
|
|
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'
|
|
|
|
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
|
|
}
|
|
|
|
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."
|
|
}
|
|
}
|
|
|
|
function Get-WindowsAppRuntimePackageExtensions {
|
|
$projectRoot = Split-Path -Parent $Project
|
|
$objRoot = Join-Path $projectRoot 'obj'
|
|
if (-not (Test-Path -LiteralPath $objRoot)) {
|
|
return ''
|
|
}
|
|
|
|
$manifest = Get-ChildItem -LiteralPath $objRoot -Filter 'AppxManifest.xml' -Recurse -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FullName -like '*\MsixContent\AppxManifest.xml' } |
|
|
Sort-Object LastWriteTime -Descending |
|
|
Select-Object -First 1
|
|
if (-not $manifest) {
|
|
return ''
|
|
}
|
|
|
|
[xml]$xml = Get-Content -LiteralPath $manifest.FullName -Raw -Encoding UTF8
|
|
$namespace = [System.Xml.XmlNamespaceManager]::new($xml.NameTable)
|
|
$namespace.AddNamespace('m', 'http://schemas.microsoft.com/appx/manifest/foundation/windows10')
|
|
$extensions = $xml.SelectSingleNode('/m:Package/m:Extensions', $namespace)
|
|
if (-not $extensions) {
|
|
return ''
|
|
}
|
|
|
|
$extensions.OuterXml
|
|
}
|
|
|
|
function Write-AppxManifest([string] $TargetDir, [string] $PackageVersion) {
|
|
$packageExtensions = Get-WindowsAppRuntimePackageExtensions
|
|
if ([string]::IsNullOrWhiteSpace($packageExtensions)) {
|
|
throw 'Windows App SDK package extensions were not found. Build output must include MsixContent\AppxManifest.xml so WinUI activatable classes are registered in the MSIX.'
|
|
}
|
|
|
|
$manifest = @"
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<Package
|
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
|
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
|
|
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
|
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
|
IgnorableNamespaces="uap uap5 uap10 rescap">
|
|
|
|
<Identity
|
|
Name="$PackageIdentityName"
|
|
Publisher="$PackagePublisher"
|
|
Version="$PackageVersion"
|
|
ProcessorArchitecture="x64" />
|
|
|
|
<Properties>
|
|
<DisplayName>$AppName</DisplayName>
|
|
<PublisherDisplayName>YMhut</PublisherDisplayName>
|
|
<Logo>Assets\StoreLogo.png</Logo>
|
|
</Properties>
|
|
|
|
<Dependencies>
|
|
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.26100.0" />
|
|
</Dependencies>
|
|
|
|
<Resources>
|
|
<Resource Language="zh-CN" />
|
|
<Resource Language="en-US" />
|
|
</Resources>
|
|
|
|
<Applications>
|
|
<Application Id="App" Executable="$AppExecutable" EntryPoint="Windows.FullTrustApplication" uap10:RuntimeBehavior="packagedClassicApp" uap10:TrustLevel="mediumIL">
|
|
<uap:VisualElements
|
|
DisplayName="$AppName"
|
|
Description="YMhut Box - A multi-functional toolbox"
|
|
Square150x150Logo="Assets\Square150x150Logo.png"
|
|
Square44x44Logo="Assets\Square44x44Logo.png"
|
|
BackgroundColor="transparent">
|
|
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
|
</uap:VisualElements>
|
|
<Extensions>
|
|
<uap5:Extension Category="windows.startupTask">
|
|
<uap5:StartupTask TaskId="YMhutBoxStartupTask" Enabled="false" DisplayName="$AppName" />
|
|
</uap5:Extension>
|
|
</Extensions>
|
|
</Application>
|
|
</Applications>
|
|
|
|
<Capabilities>
|
|
<rescap:Capability Name="runFullTrust" />
|
|
</Capabilities>
|
|
|
|
$packageExtensions
|
|
</Package>
|
|
"@
|
|
Set-Content -LiteralPath (Join-Path $TargetDir 'AppxManifest.xml') -Value $manifest -Encoding UTF8
|
|
}
|
|
|
|
function Write-AppInstaller([object] $VersionInfo, [string] $MsixFileName) {
|
|
New-Directory $OutputUpdateInfoRoot
|
|
$appInstallerUri = $UpdateBaseUri + 'winui.appinstaller'
|
|
$msixUri = $DownloadBaseUri + $MsixFileName
|
|
$appInstallerPath = Join-Path $OutputUpdateInfoRoot 'winui.appinstaller'
|
|
|
|
$content = @"
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<AppInstaller
|
|
xmlns="http://schemas.microsoft.com/appx/appinstaller/2018"
|
|
Version="$($VersionInfo.PackageVersion)"
|
|
Uri="$appInstallerUri">
|
|
<MainPackage
|
|
Name="$PackageIdentityName"
|
|
Publisher="$PackagePublisher"
|
|
Version="$($VersionInfo.PackageVersion)"
|
|
ProcessorArchitecture="x64"
|
|
Uri="$msixUri" />
|
|
<UpdateSettings>
|
|
<OnLaunch HoursBetweenUpdateChecks="12" ShowPrompt="true" UpdateBlocksActivation="false" />
|
|
</UpdateSettings>
|
|
</AppInstaller>
|
|
"@
|
|
Set-Content -LiteralPath $appInstallerPath -Value $content -Encoding UTF8
|
|
Copy-Item -LiteralPath $appInstallerPath -Destination (Join-Path $OutputRoot 'winui.appinstaller') -Force
|
|
|
|
$localAppInstallerPath = Join-Path $OutputRoot 'winui-local.appinstaller'
|
|
$localAppInstallerUri = [Uri]::new([IO.Path]::GetFullPath($localAppInstallerPath)).AbsoluteUri
|
|
$localMsixUri = [Uri]::new([IO.Path]::GetFullPath((Join-Path $OutputRoot $MsixFileName))).AbsoluteUri
|
|
$localContent = @"
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<AppInstaller
|
|
xmlns="http://schemas.microsoft.com/appx/appinstaller/2018"
|
|
Version="$($VersionInfo.PackageVersion)"
|
|
Uri="$localAppInstallerUri">
|
|
<MainPackage
|
|
Name="$PackageIdentityName"
|
|
Publisher="$PackagePublisher"
|
|
Version="$($VersionInfo.PackageVersion)"
|
|
ProcessorArchitecture="x64"
|
|
Uri="$localMsixUri" />
|
|
<UpdateSettings>
|
|
<OnLaunch HoursBetweenUpdateChecks="0" ShowPrompt="false" UpdateBlocksActivation="false" />
|
|
</UpdateSettings>
|
|
</AppInstaller>
|
|
"@
|
|
Set-Content -LiteralPath $localAppInstallerPath -Value $localContent -Encoding UTF8
|
|
}
|
|
|
|
function Write-LayoutManifests([string] $Directory) {
|
|
$files = Get-ChildItem -LiteralPath $Directory -Recurse -File |
|
|
ForEach-Object { $_.FullName.Substring($Directory.Length).TrimStart('\', '/') -replace '\\', '/' } |
|
|
Sort-Object
|
|
|
|
$configDir = Join-Path $Directory 'config'
|
|
New-Directory $configDir
|
|
|
|
$required = @($files | Where-Object { $_ -in @('YMhutBox.exe', 'YMhutBox.dll', 'WebView2Loader.dll') })
|
|
if ($required.Count -eq 0) {
|
|
$required = @('YMhutBox.exe')
|
|
}
|
|
|
|
$manifest = [System.Collections.Generic.List[string]]::new()
|
|
if ($versionInfo) {
|
|
$manifest.Add('[Release]')
|
|
$manifest.Add("Version=$($versionInfo.Version)")
|
|
$manifest.Add("Build=$($versionInfo.Build)")
|
|
$manifest.Add("Channel=$($versionInfo.Channel)")
|
|
$manifest.Add("PackageVersion=$($versionInfo.PackageVersion)")
|
|
$manifest.Add('')
|
|
}
|
|
$manifest.Add('[RequiredFiles]')
|
|
foreach ($file in $required) {
|
|
$manifest.Add($file)
|
|
}
|
|
$manifest.Add('')
|
|
$manifest.Add('[Files]')
|
|
foreach ($file in $files) {
|
|
if ($file -ine 'config/install-manifest.ini') {
|
|
$manifest.Add($file)
|
|
}
|
|
}
|
|
|
|
Set-Content -LiteralPath (Join-Path $configDir 'install-manifest.ini') -Value $manifest -Encoding UTF8
|
|
}
|
|
|
|
function Get-FileSha256([string] $Path) {
|
|
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
}
|
|
|
|
function Sync-UpdateServerPackages {
|
|
if (-not (Test-Path -LiteralPath $ServerPublicRoot)) {
|
|
return
|
|
}
|
|
|
|
New-Directory $ServerDownloadRoot
|
|
foreach ($legacyFile in @('manifest.json', 'latest-version.json', 'package-manifest.json', 'incremental-manifest.json', 'modules.json')) {
|
|
$legacyPath = Join-Path $ServerPublicRoot $legacyFile
|
|
if (Test-Path -LiteralPath $legacyPath) {
|
|
Remove-Item -LiteralPath $legacyPath -Force
|
|
}
|
|
}
|
|
|
|
foreach ($legacyRoot in @(
|
|
(Join-Path $ServerPublicRoot 'packages'),
|
|
(Join-Path $ServerPublicRoot 'tool-packages'),
|
|
(Join-Path $ServerPublicRoot 'incremental'),
|
|
(Join-Path $ServerDownloadRoot 'incremental')
|
|
)) {
|
|
if (Test-Path -LiteralPath $legacyRoot) {
|
|
Remove-Item -LiteralPath $legacyRoot -Recurse -Force
|
|
}
|
|
}
|
|
|
|
foreach ($sourceRoot in @($OutputRoot, $OutputUpdateInfoRoot)) {
|
|
if (-not (Test-Path -LiteralPath $sourceRoot)) {
|
|
continue
|
|
}
|
|
|
|
Get-ChildItem -LiteralPath $sourceRoot -File -ErrorAction SilentlyContinue |
|
|
Where-Object {
|
|
$_.Name -notmatch '_Light\.' -and (
|
|
$_.Name -match '^YMhut_Box_(WinUI_)?Setup_.+\.(exe|msi)$' -or
|
|
$_.Name -match '^YMhutBox_.+_x64\.msix$' -or
|
|
$_.Name -ieq 'winui.appinstaller'
|
|
)
|
|
} |
|
|
ForEach-Object {
|
|
Copy-Item -LiteralPath $_.FullName -Destination (Join-Path $ServerDownloadRoot $_.Name) -Force
|
|
}
|
|
}
|
|
|
|
$updateInfoSource = Join-Path $OutputRoot 'update-info.json'
|
|
if (Test-Path -LiteralPath $updateInfoSource) {
|
|
Copy-Item -LiteralPath $updateInfoSource -Destination (Join-Path $ServerPublicRoot 'update-info.json') -Force
|
|
}
|
|
|
|
Write-Host " Server downloads: $(Join-Path $ServerPublicRoot 'downloads')"
|
|
Write-Host " Server update info: $(Join-Path $ServerPublicRoot 'update-info.json')"
|
|
}
|
|
function Write-UpdateCatalogs([object] $VersionInfo) {
|
|
$fullExeName = "YMhut_Box_WinUI_Setup_$($VersionInfo.PackageVersion).exe"
|
|
$msixName = "YMhutBox_$($VersionInfo.PackageVersion)_x64.msix"
|
|
$appInstallerName = 'winui.appinstaller'
|
|
|
|
$fullInstallerPath = Join-Path $OutputRoot $fullExeName
|
|
$msixOutputPath = Join-Path $OutputUpdateInfoRoot $msixName
|
|
if (-not (Test-Path -LiteralPath $msixOutputPath)) {
|
|
$msixOutputPath = Join-Path $OutputRoot $msixName
|
|
}
|
|
$appInstallerPath = Join-Path $OutputRoot $appInstallerName
|
|
|
|
$fullInstaller = [ordered]@{
|
|
fileName = $fullExeName
|
|
url = $DownloadBaseUri + $fullExeName
|
|
sha256 = if (Test-Path -LiteralPath $fullInstallerPath) { Get-FileSha256 $fullInstallerPath } else { '' }
|
|
size = if (Test-Path -LiteralPath $fullInstallerPath) { (Get-Item -LiteralPath $fullInstallerPath).Length } else { 0 }
|
|
version = $VersionInfo.PackageVersion
|
|
}
|
|
$msix = [ordered]@{
|
|
fileName = $msixName
|
|
url = $DownloadBaseUri + $msixName
|
|
sha256 = if (Test-Path -LiteralPath $msixOutputPath) { Get-FileSha256 $msixOutputPath } else { '' }
|
|
size = if (Test-Path -LiteralPath $msixOutputPath) { (Get-Item -LiteralPath $msixOutputPath).Length } else { 0 }
|
|
version = $VersionInfo.PackageVersion
|
|
}
|
|
$appInstaller = [ordered]@{
|
|
fileName = $appInstallerName
|
|
url = $DownloadBaseUri + $appInstallerName
|
|
sha256 = if (Test-Path -LiteralPath $appInstallerPath) { Get-FileSha256 $appInstallerPath } else { '' }
|
|
size = if (Test-Path -LiteralPath $appInstallerPath) { (Get-Item -LiteralPath $appInstallerPath).Length } else { 0 }
|
|
version = $VersionInfo.PackageVersion
|
|
}
|
|
|
|
$manifest = [ordered]@{
|
|
manifestVersion = 5
|
|
latestVersion = $VersionInfo.PackageVersion
|
|
appVersion = $VersionInfo.PackageVersion
|
|
version = $VersionInfo.Version
|
|
build = $VersionInfo.Build
|
|
channel = $VersionInfo.Channel
|
|
latest = [ordered]@{
|
|
version = $VersionInfo.PackageVersion
|
|
fullInstaller = $fullInstaller
|
|
msix = $msix
|
|
appInstaller = $appInstaller
|
|
files = [ordered]@{
|
|
fullInstaller = $fullInstaller
|
|
msix = $msix
|
|
appInstaller = $appInstaller
|
|
}
|
|
}
|
|
messages = [ordered]@{
|
|
updateInfo = 'The official update-info catalog only describes the full offline installer, MSIX, and appinstaller artifacts.'
|
|
distribution = 'The update channel publishes the full offline installer, MSIX, and appinstaller artifacts.'
|
|
}
|
|
createdAt = (Get-Date).ToUniversalTime().ToString('o')
|
|
}
|
|
|
|
foreach ($legacyFile in @('manifest.json', 'latest-version.json', 'package-manifest.json', 'incremental-manifest.json')) {
|
|
$legacyPath = Join-Path $OutputRoot $legacyFile
|
|
if (Test-Path -LiteralPath $legacyPath) {
|
|
Remove-Item -LiteralPath $legacyPath -Force
|
|
}
|
|
}
|
|
|
|
Write-Utf8NoBomFile (Join-Path $OutputRoot 'update-info.json') ($manifest | ConvertTo-Json -Depth 10)
|
|
}
|
|
function Compress-ReferenceData([string] $Directory) {
|
|
$assetsRoot = Join-Path $Directory 'Assets'
|
|
$dataRoot = Join-Path $assetsRoot 'data'
|
|
if (-not (Test-Path -LiteralPath $dataRoot)) {
|
|
return
|
|
}
|
|
|
|
$jsonFiles = @(Get-ChildItem -LiteralPath $dataRoot -Recurse -Filter '*.json' -File -ErrorAction SilentlyContinue)
|
|
if ($jsonFiles.Count -eq 0) {
|
|
return
|
|
}
|
|
|
|
Add-Type -AssemblyName System.IO.Compression
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
Add-Type -AssemblyName System.Security
|
|
$ybinPath = Join-Path $dataRoot 'ymhut-data.ybin'
|
|
if (Test-Path -LiteralPath $ybinPath) {
|
|
Remove-Item -LiteralPath $ybinPath -Force
|
|
}
|
|
|
|
$memory = [System.IO.MemoryStream]::new()
|
|
$archive = [System.IO.Compression.ZipArchive]::new($memory, [System.IO.Compression.ZipArchiveMode]::Create, $true)
|
|
try {
|
|
foreach ($file in $jsonFiles) {
|
|
$entryName = $file.FullName.Substring($assetsRoot.Length).TrimStart('\', '/') -replace '\\', '/'
|
|
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive, $file.FullName, $entryName, [System.IO.Compression.CompressionLevel]::Optimal) | Out-Null
|
|
}
|
|
} finally {
|
|
$archive.Dispose()
|
|
}
|
|
|
|
$plain = $memory.ToArray()
|
|
$memory.Dispose()
|
|
$sha = [System.Security.Cryptography.SHA256]::Create()
|
|
try {
|
|
$key = $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes('YMhut.Box.ReferenceData.YBin.v1'))
|
|
} finally {
|
|
$sha.Dispose()
|
|
}
|
|
$aes = [System.Security.Cryptography.Aes]::Create()
|
|
try {
|
|
$aes.Key = $key
|
|
$aes.GenerateIV()
|
|
$aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
|
|
$aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
|
|
$encryptor = $aes.CreateEncryptor()
|
|
try {
|
|
$cipher = $encryptor.TransformFinalBlock($plain, 0, $plain.Length)
|
|
} finally {
|
|
$encryptor.Dispose()
|
|
}
|
|
$magic = [Text.Encoding]::ASCII.GetBytes('YMHUTYBIN1')
|
|
$output = [byte[]]::new($magic.Length + $aes.IV.Length + $cipher.Length)
|
|
[Array]::Copy($magic, 0, $output, 0, $magic.Length)
|
|
[Array]::Copy($aes.IV, 0, $output, $magic.Length, $aes.IV.Length)
|
|
[Array]::Copy($cipher, 0, $output, $magic.Length + $aes.IV.Length, $cipher.Length)
|
|
[IO.File]::WriteAllBytes($ybinPath, $output)
|
|
} finally {
|
|
$aes.Dispose()
|
|
}
|
|
|
|
foreach ($file in $jsonFiles) {
|
|
Remove-Item -LiteralPath $file.FullName -Force
|
|
}
|
|
}
|
|
|
|
function Compress-SatelliteResourceFolders([string] $Directory) {
|
|
Add-Type -AssemblyName System.IO.Compression
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
$langRoot = Join-Path $Directory 'resources\lang'
|
|
New-Directory $langRoot
|
|
$culturePattern = '^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,3}$'
|
|
foreach ($folder in Get-ChildItem -LiteralPath $Directory -Directory -ErrorAction SilentlyContinue) {
|
|
if ($folder.Name -match $culturePattern -and
|
|
(Test-Path -LiteralPath (Join-Path $folder.FullName 'Microsoft.ui.xaml.dll.mui'))) {
|
|
$binPath = Join-Path $langRoot "$($folder.Name).bin"
|
|
if (Test-Path -LiteralPath $binPath) {
|
|
Remove-Item -LiteralPath $binPath -Force
|
|
}
|
|
|
|
$archive = [System.IO.Compression.ZipFile]::Open($binPath, [System.IO.Compression.ZipArchiveMode]::Create)
|
|
try {
|
|
foreach ($file in Get-ChildItem -LiteralPath $folder.FullName -Recurse -File) {
|
|
$entryName = $file.FullName.Substring($folder.FullName.Length).TrimStart('\', '/') -replace '\\', '/'
|
|
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive, $file.FullName, $entryName, [System.IO.Compression.CompressionLevel]::Optimal) | Out-Null
|
|
}
|
|
} finally {
|
|
$archive.Dispose()
|
|
}
|
|
|
|
Remove-Item -LiteralPath $folder.FullName -Recurse -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
function Expand-SatelliteResourcePackages([string] $Directory, [switch] $RemovePackages) {
|
|
Add-Type -AssemblyName System.IO.Compression
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
$langRoot = Join-Path $Directory 'resources\lang'
|
|
if (-not (Test-Path -LiteralPath $langRoot)) {
|
|
return
|
|
}
|
|
|
|
$culturePattern = '^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,3}$'
|
|
foreach ($package in Get-ChildItem -LiteralPath $langRoot -Filter '*.bin' -File -ErrorAction SilentlyContinue) {
|
|
$culture = [IO.Path]::GetFileNameWithoutExtension($package.Name)
|
|
if ($culture -notmatch $culturePattern) {
|
|
continue
|
|
}
|
|
|
|
$target = Join-Path $Directory $culture
|
|
$targetFull = [IO.Path]::GetFullPath($target)
|
|
New-Directory $targetFull
|
|
|
|
$archive = [System.IO.Compression.ZipFile]::OpenRead($package.FullName)
|
|
try {
|
|
foreach ($entry in $archive.Entries) {
|
|
$destination = [IO.Path]::GetFullPath((Join-Path $targetFull $entry.FullName))
|
|
if (-not $destination.StartsWith($targetFull, [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "Refusing to extract language package entry outside target directory: $($entry.FullName)"
|
|
}
|
|
|
|
if ($entry.FullName.EndsWith('/') -or $entry.FullName.EndsWith('\')) {
|
|
New-Directory $destination
|
|
continue
|
|
}
|
|
|
|
New-Directory (Split-Path -Parent $destination)
|
|
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $destination, $true)
|
|
}
|
|
} finally {
|
|
$archive.Dispose()
|
|
}
|
|
}
|
|
|
|
if ($RemovePackages) {
|
|
Remove-Item -LiteralPath $langRoot -Recurse -Force
|
|
}
|
|
}
|
|
|
|
function Get-CanonicalCultureName([string] $Name) {
|
|
if ($Name -match '^(?i)zh-cn$') { return 'zh-CN' }
|
|
if ($Name -match '^(?i)en-us$') { return 'en-US' }
|
|
return $Name
|
|
}
|
|
|
|
function Test-CultureDirectoryName([string] $Name) {
|
|
return $Name -match '^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,3}$'
|
|
}
|
|
|
|
function Normalize-LanguageResourceLayout([string] $Directory) {
|
|
$allowedCultures = @('zh-CN', 'en-US')
|
|
$langRoot = Join-Path $Directory 'lang'
|
|
New-Directory $langRoot
|
|
|
|
$legacyLangRoot = Join-Path $Directory 'resources\lang'
|
|
if (Test-Path -LiteralPath $legacyLangRoot) {
|
|
Remove-Item -LiteralPath $legacyLangRoot -Recurse -Force
|
|
}
|
|
|
|
foreach ($folder in Get-ChildItem -LiteralPath $Directory -Directory -ErrorAction SilentlyContinue) {
|
|
if (-not (Test-CultureDirectoryName $folder.Name)) {
|
|
continue
|
|
}
|
|
|
|
$canonical = Get-CanonicalCultureName $folder.Name
|
|
if ($allowedCultures -contains $canonical) {
|
|
$target = Join-Path $langRoot $canonical
|
|
if (Test-Path -LiteralPath $target) {
|
|
Remove-Item -LiteralPath $target -Recurse -Force
|
|
}
|
|
|
|
Move-Item -LiteralPath $folder.FullName -Destination $target -Force
|
|
continue
|
|
}
|
|
|
|
Remove-Item -LiteralPath $folder.FullName -Recurse -Force
|
|
}
|
|
|
|
foreach ($folder in Get-ChildItem -LiteralPath $langRoot -Directory -ErrorAction SilentlyContinue) {
|
|
$canonical = Get-CanonicalCultureName $folder.Name
|
|
if ($allowedCultures -notcontains $canonical) {
|
|
Remove-Item -LiteralPath $folder.FullName -Recurse -Force
|
|
continue
|
|
}
|
|
|
|
if ($folder.Name -cne $canonical) {
|
|
$target = Join-Path $langRoot $canonical
|
|
if (Test-Path -LiteralPath $target) {
|
|
Remove-Item -LiteralPath $target -Recurse -Force
|
|
}
|
|
Move-Item -LiteralPath $folder.FullName -Destination $target -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
function Restore-LangResourceLayoutForMsix([string] $Directory) {
|
|
$langRoot = Join-Path $Directory 'lang'
|
|
if (-not (Test-Path -LiteralPath $langRoot)) {
|
|
return
|
|
}
|
|
|
|
foreach ($culture in @('zh-CN', 'en-US')) {
|
|
$source = Join-Path $langRoot $culture
|
|
if (-not (Test-Path -LiteralPath $source)) {
|
|
continue
|
|
}
|
|
|
|
$target = Join-Path $Directory $culture
|
|
if (Test-Path -LiteralPath $target) {
|
|
Remove-Item -LiteralPath $target -Recurse -Force
|
|
}
|
|
Move-Item -LiteralPath $source -Destination $target -Force
|
|
}
|
|
|
|
Remove-Item -LiteralPath $langRoot -Recurse -Force
|
|
}
|
|
|
|
function Normalize-PublishLayout([string] $Directory) {
|
|
Compress-ReferenceData $Directory
|
|
Normalize-AppResourcePri $Directory
|
|
Normalize-LanguageResourceLayout $Directory
|
|
Remove-DevelopmentArtifacts $Directory
|
|
Remove-OldLayoutManifests $Directory
|
|
Remove-RootHelperDuplicates $Directory
|
|
Assert-UnpackagedLanguageResourcesInLang $Directory
|
|
Assert-NoDevelopmentArtifacts $Directory
|
|
}
|
|
|
|
function Assert-UnpackagedLanguageResourcesInLang([string] $Directory) {
|
|
$langRoot = Join-Path $Directory 'resources\lang'
|
|
$languagePackages = @()
|
|
if (Test-Path -LiteralPath $langRoot) {
|
|
$languagePackages = @(Get-ChildItem -LiteralPath $langRoot -Filter '*.bin' -File -ErrorAction SilentlyContinue)
|
|
}
|
|
|
|
if ($languagePackages.Count -gt 0) {
|
|
throw "Unpackaged EXE/latest layout must keep WinUI satellite resources expanded; found compressed resources\lang packages in $Directory"
|
|
}
|
|
|
|
if (Test-Path -LiteralPath $langRoot) {
|
|
throw "Unpackaged EXE/latest layout must not contain resources\lang; use lang\zh-CN and lang\en-US in $Directory"
|
|
}
|
|
|
|
$rootCultureFolders = @(Get-ChildItem -LiteralPath $Directory -Directory -ErrorAction SilentlyContinue |
|
|
Where-Object { Test-CultureDirectoryName $_.Name })
|
|
if ($rootCultureFolders.Count -gt 0) {
|
|
$relativeList = $rootCultureFolders | Select-Object -ExpandProperty Name
|
|
throw "Unpackaged EXE/latest layout must not contain root culture folders: $($relativeList -join ', ')"
|
|
}
|
|
|
|
$canonicalLangRoot = Join-Path $Directory 'lang'
|
|
foreach ($culture in @('zh-CN', 'en-US')) {
|
|
$cultureRoot = Join-Path $canonicalLangRoot $culture
|
|
if (-not (Test-Path -LiteralPath $cultureRoot)) {
|
|
throw "Unpackaged EXE/latest layout is missing lang\$culture in $Directory"
|
|
}
|
|
|
|
$muiFiles = @(Get-ChildItem -LiteralPath $cultureRoot -Recurse -File -Filter '*.mui' -ErrorAction SilentlyContinue)
|
|
$resourceFiles = @(Get-ChildItem -LiteralPath $cultureRoot -Recurse -File -Filter '*.resources.dll' -ErrorAction SilentlyContinue)
|
|
if (($muiFiles.Count + $resourceFiles.Count) -eq 0) {
|
|
throw "Unpackaged EXE/latest layout lang\$culture contains no satellite resource files in $Directory"
|
|
}
|
|
}
|
|
|
|
$unexpectedLangFolders = @(Get-ChildItem -LiteralPath $canonicalLangRoot -Directory -ErrorAction SilentlyContinue |
|
|
Where-Object { @('zh-CN', 'en-US') -notcontains $_.Name })
|
|
if ($unexpectedLangFolders.Count -gt 0) {
|
|
$relativeList = $unexpectedLangFolders | Select-Object -ExpandProperty Name
|
|
throw "Unpackaged EXE/latest layout contains unsupported lang cultures: $($relativeList -join ', ')"
|
|
}
|
|
}
|
|
|
|
function Ensure-RootResourcePri([string] $Directory) {
|
|
$appPriPath = Join-Path $Directory ([IO.Path]::ChangeExtension($AppExecutable, '.pri'))
|
|
if (Test-Path -LiteralPath $appPriPath) {
|
|
Copy-Item -LiteralPath $appPriPath -Destination (Join-Path $Directory 'resources.pri') -Force
|
|
}
|
|
}
|
|
|
|
function Normalize-AppResourcePri([string] $Directory) {
|
|
$appPriPath = Join-Path $Directory ([IO.Path]::ChangeExtension($AppExecutable, '.pri'))
|
|
if (-not (Test-Path -LiteralPath $appPriPath)) {
|
|
return
|
|
}
|
|
|
|
$resourcePriPath = Join-Path $Directory 'resources.pri'
|
|
if (Test-Path -LiteralPath $resourcePriPath) {
|
|
Remove-Item -LiteralPath $resourcePriPath -Force
|
|
}
|
|
Move-Item -LiteralPath $appPriPath -Destination $resourcePriPath -Force
|
|
}
|
|
|
|
function Remove-DevelopmentArtifacts([string] $Directory) {
|
|
$patterns = @('*.pdb', '*.winmd', '*.ilk', '*.iobj', '*.ipdb', 'workloads*.json')
|
|
foreach ($pattern in $patterns) {
|
|
Get-ChildItem -LiteralPath $Directory -Recurse -File -Filter $pattern -ErrorAction SilentlyContinue |
|
|
ForEach-Object { Remove-Item -LiteralPath $_.FullName -Force }
|
|
}
|
|
}
|
|
|
|
function Remove-OldLayoutManifests([string] $Directory) {
|
|
$oldFiles = @(
|
|
'installer-layout.txt',
|
|
'config\installer-layout.dat',
|
|
'config\installer-required-files.dat',
|
|
'Assets\data\reference-data.dat',
|
|
'release-manifest.json',
|
|
'version.json',
|
|
'Assets\home-globe\textures\solar\solar-texture-manifest.json'
|
|
)
|
|
foreach ($relative in $oldFiles) {
|
|
$path = Join-Path $Directory $relative
|
|
if (Test-Path -LiteralPath $path) {
|
|
Remove-Item -LiteralPath $path -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
function Assert-NoDevelopmentArtifacts([string] $Directory) {
|
|
$blocked = @()
|
|
foreach ($pattern in @('*.pdb', '*.winmd', '*.ilk', '*.iobj', '*.ipdb', 'workloads*.json')) {
|
|
$blocked += @(Get-ChildItem -LiteralPath $Directory -Recurse -File -Filter $pattern -ErrorAction SilentlyContinue)
|
|
}
|
|
|
|
$appPriPath = Join-Path $Directory ([IO.Path]::ChangeExtension($AppExecutable, '.pri'))
|
|
if (Test-Path -LiteralPath $appPriPath) {
|
|
$blocked += @(Get-Item -LiteralPath $appPriPath)
|
|
}
|
|
|
|
foreach ($relative in @(
|
|
'installer-layout.txt',
|
|
'config\installer-layout.dat',
|
|
'config\installer-required-files.dat',
|
|
'Assets\data\reference-data.dat',
|
|
'release-manifest.json',
|
|
'version.json',
|
|
'Assets\home-globe\textures\solar\solar-texture-manifest.json'
|
|
)) {
|
|
$path = Join-Path $Directory $relative
|
|
if (Test-Path -LiteralPath $path) {
|
|
$blocked += @(Get-Item -LiteralPath $path)
|
|
}
|
|
}
|
|
|
|
if ($blocked.Count -gt 0) {
|
|
$relativeList = $blocked |
|
|
Select-Object -ExpandProperty FullName -Unique |
|
|
ForEach-Object { $_.Substring($Directory.Length).TrimStart('\', '/') -replace '\\', '/' }
|
|
throw "Publish layout contains development artifacts: $($relativeList -join ', ')"
|
|
}
|
|
}
|
|
|
|
function Remove-RootHelperDuplicates([string] $Directory) {
|
|
$helperFolders = @(
|
|
@{ Root = 'worker'; Prefix = 'YMhut.Box.Worker' },
|
|
@{ Root = 'plugin-host'; Prefix = 'YMhut.Box.PluginHost' },
|
|
@{ Root = 'download-host'; Prefix = 'YMhut.Box.DownloadHost' }
|
|
)
|
|
|
|
foreach ($helper in $helperFolders) {
|
|
$folder = Join-Path $Directory $helper.Root
|
|
if (-not (Test-Path -LiteralPath $folder)) {
|
|
continue
|
|
}
|
|
|
|
foreach ($extension in @('.exe', '.dll', '.deps.json', '.runtimeconfig.json')) {
|
|
$path = Join-Path $Directory "$($helper.Prefix)$extension"
|
|
if (Test-Path -LiteralPath $path) {
|
|
Remove-Item -LiteralPath $path -Force
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function Copy-PublishToLatest {
|
|
Stop-ProcessesFromDirectory $LatestRoot
|
|
Reset-DirectoryInsideRepo $LatestRoot
|
|
Copy-Item -Path (Join-Path $PublishRoot '*') -Destination $LatestRoot -Recurse -Force
|
|
Normalize-PublishLayout $LatestRoot
|
|
Write-LayoutManifests $LatestRoot
|
|
}
|
|
|
|
function Publish-UnpackagedApp([object] $VersionInfo) {
|
|
Reset-DirectoryInsideRepo $PublishRoot
|
|
Invoke-DotNet @(
|
|
'publish', $Project,
|
|
'-c', $Configuration,
|
|
'-r', 'win-x64',
|
|
'--self-contained', 'true',
|
|
'-p:WindowsPackageType=None',
|
|
'-p:PublishSingleFile=false',
|
|
"-p:AssemblyVersion=$($VersionInfo.PackageVersion)",
|
|
"-p:FileVersion=$($VersionInfo.PackageVersion)",
|
|
"-p:InformationalVersion=$($VersionInfo.PackageVersion)",
|
|
'-o', $PublishRoot
|
|
)
|
|
Normalize-PublishLayout $PublishRoot
|
|
Write-LayoutManifests $PublishRoot
|
|
Copy-PublishToLatest
|
|
}
|
|
|
|
function Build-InnoInstaller([object] $VersionInfo, [string] $SignTool, [switch] $AllowMissingCompiler, [string] $PayloadDirectory = $LatestRoot) {
|
|
$iscc = Find-InnoSetupCompiler
|
|
if (-not $iscc) {
|
|
if ($AllowMissingCompiler) {
|
|
Write-Warning 'Inno Setup compiler (ISCC.exe) was not found. Skipping EXE installer generation for this run.'
|
|
return $false
|
|
}
|
|
|
|
throw 'Inno Setup compiler (ISCC.exe) was not found. Install Inno Setup 7, then rerun build.bat --target=exe or build.bat --target=both.'
|
|
}
|
|
|
|
$iss = Join-Path $Root 'installer\ymhut_box_winui.iss'
|
|
$chineseMessagesFile = Resolve-InnoLanguageFile $iscc
|
|
Invoke-Tool $iscc @(
|
|
"/DMyAppVersion=$($VersionInfo.PackageVersion)",
|
|
"/DMyAppBuild=$($VersionInfo.Build)",
|
|
"/DMyAppChannel=$($VersionInfo.Channel)",
|
|
"/DChineseMessagesFile=$chineseMessagesFile",
|
|
"/DPayloadDir=$PayloadDirectory",
|
|
$iss
|
|
) 'Inno Setup build failed'
|
|
|
|
$setupPath = Join-Path $OutputRoot "YMhut_Box_WinUI_Setup_$($VersionInfo.PackageVersion).exe"
|
|
if ((Test-Path -LiteralPath $setupPath) -and $SignTool) {
|
|
Sign-Artifact $SignTool $setupPath
|
|
}
|
|
|
|
return $true
|
|
}
|
|
function Sign-Artifact([string] $SignTool, [string] $Path) {
|
|
Ensure-LocalDeveloperCertificate
|
|
Invoke-ToolQuiet $SignTool @('sign', '/fd', 'SHA256', '/f', $PfxPath, '/p', $PfxPassword, $Path) "Signing failed for $Path" " Signed: $Path"
|
|
Assert-ArtifactSignatureMatchesCertificate $Path
|
|
}
|
|
|
|
function Clear-PeCertificateDirectory([string] $Path) {
|
|
if (-not (Test-Path -LiteralPath $Path)) {
|
|
return $false
|
|
}
|
|
|
|
$bytes = [IO.File]::ReadAllBytes($Path)
|
|
if ($bytes.Length -lt 0x200 -or
|
|
$bytes[0] -ne 0x4D -or
|
|
$bytes[1] -ne 0x5A) {
|
|
return $false
|
|
}
|
|
|
|
$peOffset = [BitConverter]::ToInt32($bytes, 0x3C)
|
|
if ($peOffset -lt 0 -or $peOffset + 0x18 -ge $bytes.Length) {
|
|
return $false
|
|
}
|
|
|
|
if ($bytes[$peOffset] -ne 0x50 -or
|
|
$bytes[$peOffset + 1] -ne 0x45 -or
|
|
$bytes[$peOffset + 2] -ne 0 -or
|
|
$bytes[$peOffset + 3] -ne 0) {
|
|
return $false
|
|
}
|
|
|
|
$optionalHeaderSize = [BitConverter]::ToUInt16($bytes, $peOffset + 20)
|
|
$optionalHeaderOffset = $peOffset + 24
|
|
if ($optionalHeaderOffset + $optionalHeaderSize -gt $bytes.Length) {
|
|
return $false
|
|
}
|
|
|
|
$optionalMagic = [BitConverter]::ToUInt16($bytes, $optionalHeaderOffset)
|
|
$dataDirectoryOffset = switch ($optionalMagic) {
|
|
0x10B { $optionalHeaderOffset + 96 }
|
|
0x20B { $optionalHeaderOffset + 112 }
|
|
default { return $false }
|
|
}
|
|
|
|
$certificateDirectoryOffset = $dataDirectoryOffset + (4 * 8)
|
|
if ($certificateDirectoryOffset + 8 -gt $optionalHeaderOffset + $optionalHeaderSize) {
|
|
return $false
|
|
}
|
|
|
|
$certificateTableOffset = [BitConverter]::ToUInt32($bytes, $certificateDirectoryOffset)
|
|
$certificateTableSize = [BitConverter]::ToUInt32($bytes, $certificateDirectoryOffset + 4)
|
|
if ($certificateTableOffset -eq 0 -and $certificateTableSize -eq 0) {
|
|
return $false
|
|
}
|
|
|
|
for ($i = 0; $i -lt 8; $i++) {
|
|
$bytes[$certificateDirectoryOffset + $i] = 0
|
|
}
|
|
[IO.File]::WriteAllBytes($Path, $bytes)
|
|
return $true
|
|
}
|
|
|
|
function Repair-MsixStageThirdPartyPayloads {
|
|
$knownInvalidPe = Get-ChildItem -LiteralPath (Join-Path $MsixStageRoot 'Tools') -Recurse -Filter 'xiangqi.exe' -File -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FullName.IndexOf('\XIANGQI\', [StringComparison]::OrdinalIgnoreCase) -ge 0 } |
|
|
Select-Object -First 1
|
|
if ($knownInvalidPe -and (Clear-PeCertificateDirectory $knownInvalidPe.FullName)) {
|
|
Write-Host ' MSIX payload normalized: Tools\...\XIANGQI\xiangqi.exe'
|
|
}
|
|
}
|
|
|
|
function Build-MsixPackage([object] $VersionInfo, [string] $MakeAppx, [string] $SignTool) {
|
|
if (-not $MakeAppx) { throw 'Windows SDK MakeAppx.exe was not found. Install Windows 10/11 SDK.' }
|
|
if (-not $SignTool) { throw 'Windows SDK SignTool.exe was not found. Install Windows 10/11 SDK.' }
|
|
|
|
$msixFileName = "YMhutBox_$($VersionInfo.PackageVersion)_x64.msix"
|
|
$msixPath = Join-Path $OutputRoot $msixFileName
|
|
|
|
Reset-DirectoryInsideRepo $MsixStageRoot
|
|
Copy-Item -Path (Join-Path $PublishRoot '*') -Destination $MsixStageRoot -Recurse -Force
|
|
Expand-SatelliteResourcePackages $MsixStageRoot -RemovePackages
|
|
Restore-LangResourceLayoutForMsix $MsixStageRoot
|
|
Write-AppxManifest $MsixStageRoot $VersionInfo.PackageVersion
|
|
|
|
$appPriPath = Join-Path $MsixStageRoot ([IO.Path]::ChangeExtension($AppExecutable, '.pri'))
|
|
if (Test-Path -LiteralPath $appPriPath) {
|
|
Copy-Item -LiteralPath $appPriPath -Destination (Join-Path $MsixStageRoot 'resources.pri') -Force
|
|
Remove-Item -LiteralPath $appPriPath -Force
|
|
}
|
|
|
|
$stageAssets = Join-Path $MsixStageRoot 'Assets'
|
|
New-Directory $stageAssets
|
|
Copy-Item -LiteralPath (Join-Path $ProjectAssets '*') -Destination $stageAssets -Recurse -Force
|
|
|
|
Repair-MsixStageThirdPartyPayloads
|
|
Invoke-ToolQuiet $MakeAppx @('pack', '/d', $MsixStageRoot, '/p', $msixPath, '/o') 'MakeAppx packaging failed' " MSIX package: $msixPath"
|
|
Sign-Artifact $SignTool $msixPath
|
|
|
|
New-Directory $OutputUpdateInfoRoot
|
|
Copy-Item -LiteralPath $msixPath -Destination (Join-Path $OutputUpdateInfoRoot $msixFileName) -Force
|
|
Write-AppInstaller $VersionInfo $msixFileName
|
|
Write-MsixInstallHelpers $msixFileName
|
|
}
|
|
|
|
function Write-MsixInstallHelpers([string] $MsixFileName) {
|
|
$psPath = Join-Path $OutputRoot 'Install-YMhutBoxMsix.ps1'
|
|
$cmdPath = Join-Path $OutputRoot 'Install-YMhutBoxMsix.cmd'
|
|
$ps = @"
|
|
`$ErrorActionPreference = 'Stop'
|
|
`$root = Split-Path -Parent `$MyInvocation.MyCommand.Path
|
|
`$certPath = Join-Path `$root 'YMhutBox.cer'
|
|
`$msixPath = Join-Path `$root '$MsixFileName'
|
|
|
|
function Test-Administrator {
|
|
`$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
`$principal = [Security.Principal.WindowsPrincipal]::new(`$identity)
|
|
return `$principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath `$certPath)) {
|
|
throw "Certificate file was not found: `$certPath"
|
|
}
|
|
if (-not (Test-Path -LiteralPath `$msixPath)) {
|
|
throw "MSIX package was not found: `$msixPath"
|
|
}
|
|
|
|
`$cert = [Security.Cryptography.X509Certificates.X509Certificate2]::new(`$certPath)
|
|
`$signature = Get-AuthenticodeSignature -FilePath `$msixPath
|
|
if (-not `$signature.SignerCertificate) {
|
|
throw "MSIX signer certificate could not be read: `$msixPath"
|
|
}
|
|
if (`$signature.SignerCertificate.Thumbprint -ne `$cert.Thumbprint) {
|
|
throw "The MSIX package is signed with `$(`$signature.SignerCertificate.Thumbprint), but YMhutBox.cer is `$(`$cert.Thumbprint). Rebuild the MSIX and install the matching certificate."
|
|
}
|
|
|
|
if (-not (Test-Administrator)) {
|
|
Write-Host 'Administrator permission is required once to trust the local MSIX signing certificate.'
|
|
`$arguments = "-NoProfile -ExecutionPolicy Bypass -File ```"`$PSCommandPath```""
|
|
Start-Process -FilePath powershell.exe -ArgumentList `$arguments -Verb RunAs
|
|
exit
|
|
}
|
|
|
|
Write-Host 'Trusting YMhut Box local signing certificate for this computer...'
|
|
foreach (`$store in @('Cert:\LocalMachine\Root', 'Cert:\LocalMachine\TrustedPeople', 'Cert:\CurrentUser\Root', 'Cert:\CurrentUser\TrustedPeople')) {
|
|
Import-Certificate -FilePath `$certPath -CertStoreLocation `$store | Out-Null
|
|
if (-not (Get-ChildItem -Path `$store | Where-Object Thumbprint -eq `$cert.Thumbprint)) {
|
|
throw "Certificate was not found in `$store after import. Run this script from an elevated PowerShell window."
|
|
}
|
|
}
|
|
|
|
Write-Host 'Installing YMhut Box MSIX package...'
|
|
Add-AppxPackage -Path `$msixPath -ForceUpdateFromAnyVersion
|
|
Write-Host 'Done.'
|
|
"@
|
|
Set-Content -LiteralPath $psPath -Value $ps -Encoding UTF8
|
|
|
|
$cmd = @"
|
|
@echo off
|
|
setlocal
|
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0Install-YMhutBoxMsix.ps1"
|
|
if errorlevel 1 (
|
|
echo.
|
|
echo YMhut Box MSIX installation failed.
|
|
pause
|
|
exit /b 1
|
|
)
|
|
echo.
|
|
echo YMhut Box MSIX installation completed.
|
|
pause
|
|
"@
|
|
Set-Content -LiteralPath $cmdPath -Value $cmd -Encoding ASCII
|
|
}
|
|
|
|
New-Directory $AppDataRoot
|
|
New-Directory $HomeRoot
|
|
New-Directory $NuGetRoot
|
|
New-Directory $LocalNuGetFeedRoot
|
|
New-Directory $OutputRoot
|
|
$env:APPDATA = $AppDataRoot
|
|
$env:LOCALAPPDATA = $AppDataRoot
|
|
$env:HOME = $HomeRoot
|
|
$env:DOTNET_CLI_HOME = Join-Path $ToolStateRoot 'dotnet'
|
|
$env:NUGET_PACKAGES = $NuGetRoot
|
|
$env:DOTNET_CLI_TELEMETRY_OPTOUT = '1'
|
|
New-Directory $env:DOTNET_CLI_HOME
|
|
|
|
$versionInfo = Get-VersionInfo
|
|
$makeAppx = Find-WindowsSdkTool 'MakeAppx.exe'
|
|
$signTool = Find-WindowsSdkTool 'SignTool.exe'
|
|
|
|
Write-Host "YMhut Box WinUI build"
|
|
Write-Host " Target: $Target"
|
|
Write-Host " Version: $($versionInfo.Version) build $($versionInfo.Build) ($($versionInfo.PackageVersion))"
|
|
|
|
Ensure-MsixAssets
|
|
Invoke-DotNet @('restore', $Solution, '--configfile', $NuGetConfig, '--ignore-failed-sources')
|
|
if (-not $SkipTests) {
|
|
Invoke-DotNet @('test', $Solution, '-c', $Configuration, '--no-restore')
|
|
}
|
|
|
|
Publish-UnpackagedApp $versionInfo
|
|
|
|
if (($Target -in @('exe', 'both')) -and -not $SkipExe) {
|
|
$exeBuilt = Build-InnoInstaller $versionInfo $signTool -AllowMissingCompiler:($Target -eq 'both')
|
|
if (-not $exeBuilt -and $Target -eq 'exe') {
|
|
throw 'EXE installer generation was requested but Inno Setup compiler was not found.'
|
|
}
|
|
}
|
|
if ($Target -in @('msix', 'both')) {
|
|
Build-MsixPackage $versionInfo $makeAppx $signTool
|
|
}
|
|
|
|
Write-UpdateCatalogs $versionInfo
|
|
Sync-UpdateServerPackages
|
|
|
|
Write-Host ''
|
|
Write-Host 'Build complete.'
|
|
Write-Host " Publish payload: $PublishRoot"
|
|
Write-Host " Latest mirror: $LatestRoot"
|
|
Write-Host " Update info JSON:$(Join-Path $OutputRoot 'update-info.json')"
|
|
Write-Host " Server downloads:$ServerDownloadRoot"
|
|
Write-Host " Server update info: $(Join-Path $ServerPublicRoot 'update-info.json')"
|
|
Write-Host " Output: $OutputRoot"
|