|
| 1 | +param( |
| 2 | + [Parameter(Mandatory = $true)] |
| 3 | + [ValidatePattern("^v\d+\.\d+\.\d+$")] |
| 4 | + [string]$Tag, |
| 5 | + |
| 6 | + [Parameter(Mandatory = $true)] |
| 7 | + [string]$CertificateSubjectName, |
| 8 | + |
| 9 | + [string]$TimestampUrl = "http://timestamp.digicert.com", |
| 10 | + [string]$UnsignedZipPath, |
| 11 | + [string]$WorkspaceRoot = (Join-Path $PSScriptRoot ".."), |
| 12 | + [string]$OutputDir = (Join-Path $PSScriptRoot "../.release-local"), |
| 13 | + [switch]$NoPublish |
| 14 | +) |
| 15 | + |
| 16 | +Set-StrictMode -Version Latest |
| 17 | +$ErrorActionPreference = "Stop" |
| 18 | + |
| 19 | +function Ensure-WdkWhere { |
| 20 | + $command = Get-Command wdkwhere -ErrorAction SilentlyContinue |
| 21 | + if (!$command) { |
| 22 | + throw "wdkwhere was not found in PATH. Install it first (dotnet tool install --global Nefarius.Tools.WDKWhere)." |
| 23 | + } |
| 24 | + |
| 25 | + return $command.Source |
| 26 | +} |
| 27 | + |
| 28 | +function Resolve-UnsignedZip { |
| 29 | + param( |
| 30 | + [string]$TagValue, |
| 31 | + [string]$ExplicitZipPath, |
| 32 | + [string]$DestinationDir |
| 33 | + ) |
| 34 | + |
| 35 | + if ($ExplicitZipPath) { |
| 36 | + if (!(Test-Path $ExplicitZipPath)) { |
| 37 | + throw "Unsigned zip path not found: $ExplicitZipPath" |
| 38 | + } |
| 39 | + |
| 40 | + return (Resolve-Path $ExplicitZipPath).Path |
| 41 | + } |
| 42 | + |
| 43 | + $workflowName = "release.yml" |
| 44 | + $artifactName = "unsigned-release-bundle-$TagValue" |
| 45 | + $runRows = gh run list --workflow $workflowName --limit 100 --json databaseId,headBranch,displayTitle,status,conclusion,event | ConvertFrom-Json |
| 46 | + if (!$runRows) { |
| 47 | + throw "No workflow runs found for '$workflowName'." |
| 48 | + } |
| 49 | + |
| 50 | + $run = $runRows | |
| 51 | + Where-Object { |
| 52 | + $_.event -eq "push" -and |
| 53 | + $_.status -eq "completed" -and |
| 54 | + $_.conclusion -eq "success" -and |
| 55 | + ($_.headBranch -eq $TagValue -or $_.displayTitle -eq $TagValue) |
| 56 | + } | |
| 57 | + Select-Object -First 1 |
| 58 | + |
| 59 | + if (!$run) { |
| 60 | + throw "No successful '$workflowName' run found for tag '$TagValue'." |
| 61 | + } |
| 62 | + |
| 63 | + $downloadDir = Join-Path $DestinationDir "downloaded" |
| 64 | + New-Item -ItemType Directory -Path $downloadDir -Force | Out-Null |
| 65 | + |
| 66 | + gh run download $run.databaseId -n $artifactName -D $downloadDir | Out-Null |
| 67 | + |
| 68 | + $zip = Get-ChildItem -Path $downloadDir -Filter "Injector_x86_amd64_arm64_unsigned.zip" -File -Recurse | Select-Object -First 1 |
| 69 | + if (!$zip) { |
| 70 | + throw "Downloaded artifact '$artifactName' did not contain Injector_x86_amd64_arm64_unsigned.zip." |
| 71 | + } |
| 72 | + |
| 73 | + return $zip.FullName |
| 74 | +} |
| 75 | + |
| 76 | +function Sign-Binary { |
| 77 | + param( |
| 78 | + [string]$WdkWherePath, |
| 79 | + [string]$CertSubjectName, |
| 80 | + [string]$Timestamp, |
| 81 | + [string]$FilePath |
| 82 | + ) |
| 83 | + |
| 84 | + if (!(Test-Path $FilePath)) { |
| 85 | + throw "Expected binary missing: $FilePath" |
| 86 | + } |
| 87 | + |
| 88 | + & $WdkWherePath run signtool sign /n $CertSubjectName /a /fd SHA256 /td SHA256 /tr $Timestamp $FilePath |
| 89 | + if ($LASTEXITCODE -ne 0) { |
| 90 | + throw "signtool failed for '$FilePath' with exit code $LASTEXITCODE." |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +Push-Location $WorkspaceRoot |
| 95 | +try { |
| 96 | + # Validate GH auth early because this script relies on release + artifact APIs. |
| 97 | + gh auth status | Out-Null |
| 98 | + |
| 99 | + $wdkWhere = Ensure-WdkWhere |
| 100 | + Write-Host "Using wdkwhere: $wdkWhere" |
| 101 | + |
| 102 | + $resolvedOutputDir = Resolve-Path (New-Item -ItemType Directory -Path $OutputDir -Force) |
| 103 | + $workRoot = Join-Path $resolvedOutputDir ".work-$Tag" |
| 104 | + if (Test-Path $workRoot) { |
| 105 | + Remove-Item -Path $workRoot -Recurse -Force |
| 106 | + } |
| 107 | + New-Item -ItemType Directory -Path $workRoot -Force | Out-Null |
| 108 | + |
| 109 | + $unsignedZip = Resolve-UnsignedZip -TagValue $Tag -ExplicitZipPath $UnsignedZipPath -DestinationDir $workRoot |
| 110 | + Write-Host "Using unsigned zip: $unsignedZip" |
| 111 | + |
| 112 | + $unsignedExtract = Join-Path $workRoot "unsigned" |
| 113 | + Expand-Archive -Path $unsignedZip -DestinationPath $unsignedExtract -Force |
| 114 | + |
| 115 | + $targets = @( |
| 116 | + (Join-Path $unsignedExtract "ARM64/Injector.exe"), |
| 117 | + (Join-Path $unsignedExtract "Win32/Injector.exe"), |
| 118 | + (Join-Path $unsignedExtract "x64/Injector.exe") |
| 119 | + ) |
| 120 | + |
| 121 | + foreach ($file in $targets) { |
| 122 | + Write-Host "Signing $file" |
| 123 | + Sign-Binary -WdkWherePath $wdkWhere -CertSubjectName $CertificateSubjectName -Timestamp $TimestampUrl -FilePath $file |
| 124 | + } |
| 125 | + |
| 126 | + $finalZip = Join-Path $resolvedOutputDir "Injector_x86_amd64_arm64.zip" |
| 127 | + if (Test-Path $finalZip) { |
| 128 | + Remove-Item -Path $finalZip -Force |
| 129 | + } |
| 130 | + Compress-Archive -Path (Join-Path $unsignedExtract "*") -DestinationPath $finalZip |
| 131 | + Write-Host "Created signed zip: $finalZip" |
| 132 | + |
| 133 | + gh release view $Tag --json tagName,isDraft | Out-Null |
| 134 | + gh release upload $Tag $finalZip --clobber | Out-Null |
| 135 | + Write-Host "Uploaded asset to release '$Tag'." |
| 136 | + |
| 137 | + if (-not $NoPublish) { |
| 138 | + gh release edit $Tag --draft=false | Out-Null |
| 139 | + Write-Host "Published release '$Tag'." |
| 140 | + } |
| 141 | + else { |
| 142 | + Write-Host "Draft release left unpublished due to -NoPublish." |
| 143 | + } |
| 144 | +} |
| 145 | +finally { |
| 146 | + Pop-Location |
| 147 | +} |
0 commit comments