Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 61 additions & 52 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
# Examples:
# .\build.ps1 # Build + sign for both architectures (default)
# .\build.ps1 -Pkg # Build, sign, and create .pkg with cimipkg
# .\build.ps1 -Msi # Build, sign, and create .msi with cimipkg
# .\build.ps1 -Thumbprint "ABC123..." # Build with specific certificate
# .\build.ps1 -AllowUnsigned # Development build without signing (NOT for production)
# .\build.ps1 -Architecture arm64 # Build single architecture
Expand All @@ -20,7 +20,10 @@ param(
[switch]$AllowUnsigned,
[switch]$ListCerts,
[string]$FindCertSubject,
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming the switch from -Pkg to -Msi is a breaking change for any existing automation invoking this script. Consider adding an alias (e.g., [Alias('Pkg')]) or supporting both switches (with one deprecated) to preserve backward compatibility while still steering users to -Msi.

Suggested change
[string]$FindCertSubject,
[string]$FindCertSubject,
[Alias('Pkg')]

Copilot uses AI. Check for mistakes.
[switch]$Pkg
# -Pkg retained as an alias so existing automation keeps working;
# cimipkg 2026.04.09+ produces .msi output by default.
[Alias('Pkg')]
[switch]$Msi
)

$ErrorActionPreference = 'Stop'
Expand Down Expand Up @@ -248,7 +251,7 @@ Write-Host '=== ManageUsers Build ===' -ForegroundColor Magenta
Write-Host "Version: $Version" -ForegroundColor Yellow
Write-Host "Architecture: $Architecture" -ForegroundColor Yellow
Write-Host "Code Signing: $(if ($AllowUnsigned) { 'DISABLED (dev only)' } else { 'REQUIRED' })" -ForegroundColor $(if ($AllowUnsigned) { 'Red' } else { 'Green' })
Write-Host "Package: $(if ($Pkg) { 'YES (.pkg via cimipkg)' } else { 'No' })" -ForegroundColor $(if ($Pkg) { 'Green' } else { 'Gray' })
Write-Host "Package: $(if ($Msi) { 'YES (.msi via cimipkg)' } else { 'No' })" -ForegroundColor $(if ($Msi) { 'Green' } else { 'Gray' })
if ($AllowUnsigned) {
Write-Host ''
Write-Host 'WARNING: Unsigned build - NOT suitable for production deployment' -ForegroundColor Red
Expand Down Expand Up @@ -350,10 +353,10 @@ if ($SigningCert) {
}
}

# Package with cimipkg
if ($Pkg) {
# Package with cimipkg (new cimipkg defaults to .msi output)
if ($Msi) {
Write-Host ''
Write-Log 'Building .pkg packages with cimipkg...' 'INFO'
Write-Log 'Building .msi packages with cimipkg...' 'INFO'

$cimipkg = Get-Command cimipkg -ErrorAction SilentlyContinue
if (-not $cimipkg) {
Expand All @@ -368,55 +371,61 @@ if ($Pkg) {
$buildInfoFile = Join-Path $RootDir 'build-info.yaml'
$buildInfoTemplate = Get-Content -Path $buildInfoFile -Raw
$payloadDir = Join-Path $RootDir 'payload'
$pkgStaging = Join-Path $env:TEMP "manageusers_pkg_$(Get-Date -Format 'yyyyMMddHHmmss')"
New-Item -ItemType Directory -Path $pkgStaging -Force | Out-Null
$msiStaging = Join-Path $env:TEMP "manageusers_msi_$(Get-Date -Format 'yyyyMMddHHmmss')"
New-Item -ItemType Directory -Path $msiStaging -Force | Out-Null
Comment on lines 371 to +375
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block mutates build-info.yaml and creates staging/payload directories, but cleanup/restoration only happens at the end of the happy path. If any command throws (e.g., Set-Content/Copy-Item/Rename-Item/Move-Item), the repo can be left with a stamped build-info.yaml and leftover temp/payload folders. Wrap the packaging section in a try/finally to always restore build-info.yaml and remove $payloadDir / $msiStaging.

Copilot uses AI. Check for mistakes.

foreach ($arch in $archs) {
$sourceExe = Join-Path $OutputDir $arch 'manageusers.exe'
if (-not (Test-Path $sourceExe)) {
Write-Log "Binary not found for ${arch}: $sourceExe — skipping" 'WARN'
continue
}

# Stamp build-info.yaml with concrete architecture
$buildInfoContent = $buildInfoTemplate -replace '\$\{ARCH\}', $arch
Set-Content -Path $buildInfoFile -Value $buildInfoContent -Encoding UTF8 -NoNewline

# Stage payload — only the signed binary
if (Test-Path $payloadDir) { Remove-Item $payloadDir -Recurse -Force }
New-Item -ItemType Directory -Path $payloadDir -Force | Out-Null
Copy-Item -Path $sourceExe -Destination (Join-Path $payloadDir 'manageusers.exe') -Force
# try/finally guarantees build-info.yaml is restored and the temp payload /
# staging dirs are cleaned even if a step mid-loop throws.
try {
foreach ($arch in $archs) {
$sourceExe = Join-Path $OutputDir $arch 'manageusers.exe'
if (-not (Test-Path $sourceExe)) {
Write-Log "Binary not found for ${arch}: $sourceExe — skipping" 'WARN'
continue
}

# Run cimipkg
Write-Log "Building .pkg for $arch..." 'INFO'
& cimipkg $RootDir
if ($LASTEXITCODE -ne 0) {
Write-Log "cimipkg failed for $arch" 'ERROR'
} else {
# Rescue .pkg from build/ before next cimipkg run wipes it
# cimipkg names it ManageUsers-{version}.pkg; rename to include arch
$pkgFile = Get-ChildItem -Path $buildDir -Filter 'ManageUsers-*.pkg' -File |
Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($pkgFile) {
$archName = $pkgFile.Name -replace '^ManageUsers-', "ManageUsers-${arch}-"
$archPath = Join-Path $buildDir $archName
Rename-Item -Path $pkgFile.FullName -NewName $archName -Force
Move-Item -Path $archPath -Destination $pkgStaging -Force
$stagedFile = Get-Item (Join-Path $pkgStaging $archName)
Write-Log "Created: $archName ($([math]::Round($stagedFile.Length / 1MB, 2)) MB)" 'SUCCESS'
# Stamp build-info.yaml with concrete architecture
$buildInfoContent = $buildInfoTemplate -replace '\$\{ARCH\}', $arch
Set-Content -Path $buildInfoFile -Value $buildInfoContent -Encoding UTF8 -NoNewline

# Stage payload — only the signed binary
if (Test-Path $payloadDir) { Remove-Item $payloadDir -Recurse -Force }
New-Item -ItemType Directory -Path $payloadDir -Force | Out-Null
Copy-Item -Path $sourceExe -Destination (Join-Path $payloadDir 'manageusers.exe') -Force

# Run cimipkg — defaults to .msi in 2026.04.09+
Write-Log "Building .msi for $arch..." 'INFO'
& cimipkg $RootDir
if ($LASTEXITCODE -ne 0) {
Write-Log "cimipkg failed for $arch" 'ERROR'
} else {
# Rescue .msi from build/ before next cimipkg run wipes it
# cimipkg names it ManageUsers-{version}.msi; rename to include arch
$msiFile = Get-ChildItem -Path $buildDir -Filter 'ManageUsers-*.msi' -File |
Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($msiFile) {
$archName = $msiFile.Name -replace '^ManageUsers-', "ManageUsers-${arch}-"
$archPath = Join-Path $buildDir $archName
Rename-Item -Path $msiFile.FullName -NewName $archName -Force
Move-Item -Path $archPath -Destination $msiStaging -Force
$stagedFile = Get-Item (Join-Path $msiStaging $archName)
Write-Log "Created: $archName ($([math]::Round($stagedFile.Length / 1MB, 2)) MB)" 'SUCCESS'
}
}
}

# Clean up staged payload
# Move staged .msi files back to build/ (happy-path only; finally handles cleanup)
Get-ChildItem -Path $msiStaging -Filter '*.msi' -File -ErrorAction SilentlyContinue |
Move-Item -Destination $buildDir -Force
}
finally {
# Always restore the template and remove temp dirs, even if a step above threw.
if (Test-Path $buildInfoFile) {
Set-Content -Path $buildInfoFile -Value $buildInfoTemplate -Encoding UTF8 -NoNewline
}
Remove-Item $payloadDir -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item $msiStaging -Recurse -Force -ErrorAction SilentlyContinue
}

# Move staged .pkg files back to build/
Get-ChildItem -Path $pkgStaging -Filter '*.pkg' -File | Move-Item -Destination $buildDir -Force
Remove-Item $pkgStaging -Recurse -Force -ErrorAction SilentlyContinue

# Restore build-info.yaml template with placeholders
Set-Content -Path $buildInfoFile -Value $buildInfoTemplate -Encoding UTF8 -NoNewline
}

# Summary
Expand All @@ -430,11 +439,11 @@ foreach ($arch in $archs) {
Write-Host " $arch : $exe ($size MB, $signed)" -ForegroundColor Cyan
}
}
if ($Pkg) {
$pkgFiles = Get-ChildItem -Path (Join-Path $RootDir 'build') -Filter '*.pkg' -File -ErrorAction SilentlyContinue |
if ($Msi) {
$msiFiles = Get-ChildItem -Path (Join-Path $RootDir 'build') -Filter '*.msi' -File -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending | Select-Object -First 2
foreach ($pkgItem in $pkgFiles) {
Write-Host " pkg : $($pkgItem.FullName) ($([math]::Round($pkgItem.Length / 1MB, 2)) MB)" -ForegroundColor Green
foreach ($msiItem in $msiFiles) {
Write-Host " msi : $($msiItem.FullName) ($([math]::Round($msiItem.Length / 1MB, 2)) MB)" -ForegroundColor Green
}
}
Write-Host " Version: $Version" -ForegroundColor Gray
Expand Down