448 lines
16 KiB
PowerShell
448 lines
16 KiB
PowerShell
|
|
# sign_release_artifacts.ps1
|
||
|
|
# DigiCert KeyLocker Code Signing — AzA Release-Artefakte
|
||
|
|
# Minimalversion: ExeOnly | InstallerOnly | DryRun
|
||
|
|
# Keine Integration in release.ps1 in dieser Version.
|
||
|
|
|
||
|
|
[CmdletBinding()]
|
||
|
|
param(
|
||
|
|
[Parameter(Mandatory = $true)]
|
||
|
|
[ValidateSet("ExeOnly", "InstallerOnly")]
|
||
|
|
[string]$Phase,
|
||
|
|
|
||
|
|
[string]$DistRoot = "",
|
||
|
|
[string]$InstallerPath = "",
|
||
|
|
[string]$ReportRoot = "",
|
||
|
|
[switch]$DryRun
|
||
|
|
)
|
||
|
|
|
||
|
|
$ErrorActionPreference = "Stop"
|
||
|
|
|
||
|
|
# --- Exit-Codes ---
|
||
|
|
$EXIT_OK = 0
|
||
|
|
$EXIT_ENV = 10
|
||
|
|
$EXIT_KEYPAIR = 11
|
||
|
|
$EXIT_MISSING = 12
|
||
|
|
$EXIT_UNEXPECTED = 13
|
||
|
|
$EXIT_ALREADY_SIGNED = 14
|
||
|
|
$EXIT_SIGN_FAIL = 20
|
||
|
|
$EXIT_VERIFY_FAIL = 21
|
||
|
|
$EXIT_SHA = 22
|
||
|
|
$EXIT_AUTH = 30
|
||
|
|
|
||
|
|
# --- Feste Allowlists ---
|
||
|
|
$Script:ExeAllowlist = @(
|
||
|
|
"aza_desktop.exe",
|
||
|
|
"aza_start_panel.exe",
|
||
|
|
"aza_updater.exe",
|
||
|
|
"AZA_EmpfangShell.exe",
|
||
|
|
"AZA_KontaktPanel.exe"
|
||
|
|
)
|
||
|
|
|
||
|
|
$Script:InstallerAllowlist = @(
|
||
|
|
"aza_desktop_setup.exe",
|
||
|
|
"aza_desktop_setup_TEST.exe"
|
||
|
|
)
|
||
|
|
|
||
|
|
$Script:PublisherExpected = "Praxis Lindengut AG"
|
||
|
|
|
||
|
|
function Get-ProjectRoot {
|
||
|
|
return $PSScriptRoot
|
||
|
|
}
|
||
|
|
|
||
|
|
function Find-SignTool {
|
||
|
|
$kitsRoot = "${env:ProgramFiles(x86)}\Windows Kits\10\bin"
|
||
|
|
if (-not (Test-Path $kitsRoot)) { return $null }
|
||
|
|
$candidates = Get-ChildItem -Path $kitsRoot -Recurse -Filter "signtool.exe" -ErrorAction SilentlyContinue |
|
||
|
|
Where-Object { $_.FullName -match '\\x64\\signtool\.exe$' } |
|
||
|
|
Sort-Object { [version]($_.Directory.Parent.Name) } -Descending
|
||
|
|
if ($candidates) { return $candidates[0].FullName }
|
||
|
|
return $null
|
||
|
|
}
|
||
|
|
|
||
|
|
function Find-Smctl {
|
||
|
|
$candidates = @(
|
||
|
|
"${env:ProgramFiles}\DigiCert\DigiCert One Signing Manager Tools\smctl.exe",
|
||
|
|
"${env:ProgramFiles(x86)}\DigiCert\DigiCert One Signing Manager Tools\smctl.exe"
|
||
|
|
)
|
||
|
|
foreach ($p in $candidates) {
|
||
|
|
if (Test-Path $p) { return $p }
|
||
|
|
}
|
||
|
|
$found = Get-ChildItem "${env:ProgramFiles}\DigiCert" -Recurse -Filter "smctl.exe" -ErrorAction SilentlyContinue |
|
||
|
|
Select-Object -First 1
|
||
|
|
if ($found) { return $found.FullName }
|
||
|
|
return $null
|
||
|
|
}
|
||
|
|
|
||
|
|
function Get-Sha256Hex {
|
||
|
|
param([string]$Path)
|
||
|
|
return (Get-FileHash -Algorithm SHA256 -Path $Path).Hash.ToUpperInvariant()
|
||
|
|
}
|
||
|
|
|
||
|
|
function Redact-Text {
|
||
|
|
param([string]$Text)
|
||
|
|
if (-not $Text) { return "" }
|
||
|
|
$t = $Text
|
||
|
|
$t = $t -replace '[A-Fa-f0-9]{40,64}', '[HEX-REDACTED]'
|
||
|
|
$t = $t -replace '\bkey_\d+\b', '[ALIAS-REDACTED]'
|
||
|
|
$t = $t -replace '(?i)(api[_ -]?key|password|token|secret|fingerprint|certificate_id)[^\r\n]*', '[CRED-REDACTED]'
|
||
|
|
if ($env:SM_CLIENT_CERT_FILE) {
|
||
|
|
$t = $t -replace [regex]::Escape($env:SM_CLIENT_CERT_FILE), '[CERT-PATH-REDACTED]'
|
||
|
|
}
|
||
|
|
return $t
|
||
|
|
}
|
||
|
|
|
||
|
|
function Get-SignatureSummary {
|
||
|
|
param([string]$Path)
|
||
|
|
$sig = Get-AuthenticodeSignature -FilePath $Path -ErrorAction SilentlyContinue
|
||
|
|
$status = if ($sig) { [string]$sig.Status } else { "Unknown" }
|
||
|
|
$subject = if ($sig -and $sig.SignerCertificate) { $sig.SignerCertificate.Subject } else { $null }
|
||
|
|
$hasTs = [bool]($sig -and $sig.TimeStamperCertificate)
|
||
|
|
$notAfter = if ($sig -and $sig.SignerCertificate) { $sig.SignerCertificate.NotAfter.ToString("o") } else { $null }
|
||
|
|
return [ordered]@{
|
||
|
|
status = $status
|
||
|
|
publisher_contains_expected = [bool]($subject -and ($subject -match [regex]::Escape($Script:PublisherExpected)))
|
||
|
|
timestamp_present = $hasTs
|
||
|
|
not_after = $notAfter
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function Test-Environment {
|
||
|
|
param(
|
||
|
|
[string]$SmctlPath,
|
||
|
|
[bool]$RequireKeypair,
|
||
|
|
[ref]$KeypairAliasOut
|
||
|
|
)
|
||
|
|
|
||
|
|
if (-not $SmctlPath -or -not (Test-Path $SmctlPath)) {
|
||
|
|
throw [System.InvalidOperationException]::new("smctl nicht gefunden")
|
||
|
|
}
|
||
|
|
if (-not $env:SM_HOST) {
|
||
|
|
throw [System.InvalidOperationException]::new("SM_HOST nicht gesetzt")
|
||
|
|
}
|
||
|
|
if (-not $env:SM_CLIENT_CERT_FILE) {
|
||
|
|
throw [System.InvalidOperationException]::new("SM_CLIENT_CERT_FILE nicht gesetzt")
|
||
|
|
}
|
||
|
|
if (-not (Test-Path -LiteralPath $env:SM_CLIENT_CERT_FILE)) {
|
||
|
|
throw [System.InvalidOperationException]::new("SM_CLIENT_CERT_FILE nicht erreichbar")
|
||
|
|
}
|
||
|
|
|
||
|
|
$healthRaw = & $SmctlPath healthcheck 2>&1 | Out-String
|
||
|
|
if ($healthRaw -notmatch 'Status:\s*Connected') {
|
||
|
|
throw [System.InvalidOperationException]::new("smctl healthcheck: nicht Connected")
|
||
|
|
}
|
||
|
|
if ($healthRaw -notmatch 'Can sign:\s*Yes') {
|
||
|
|
throw [System.InvalidOperationException]::new("smctl healthcheck: Can sign nicht Yes")
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($RequireKeypair) {
|
||
|
|
$kpRaw = & $SmctlPath keypair ls 2>&1 | Out-String
|
||
|
|
$onlineLines = ($kpRaw -split "`n") | Where-Object {
|
||
|
|
$_ -match 'ONLINE' -and $_ -match 'PRODUCTION'
|
||
|
|
}
|
||
|
|
if (($onlineLines | Measure-Object).Count -ne 1) {
|
||
|
|
throw [System.InvalidOperationException]::new("Keypair nicht eindeutig (ONLINE/PRODUCTION)")
|
||
|
|
}
|
||
|
|
$m = [regex]::Match(($onlineLines | Select-Object -First 1), '\bkey_\d+\b')
|
||
|
|
if (-not $m.Success) {
|
||
|
|
throw [System.InvalidOperationException]::new("Keypair-Alias konnte nicht lokal ermittelt werden")
|
||
|
|
}
|
||
|
|
$KeypairAliasOut.Value = $m.Value
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function Get-TargetFiles {
|
||
|
|
param(
|
||
|
|
[string]$PhaseName,
|
||
|
|
[string]$DistRootPath,
|
||
|
|
[string]$InstallerPathFull
|
||
|
|
)
|
||
|
|
|
||
|
|
$targets = @()
|
||
|
|
|
||
|
|
if ($PhaseName -eq "ExeOnly") {
|
||
|
|
if (-not (Test-Path -LiteralPath $DistRootPath)) {
|
||
|
|
throw [System.IO.FileNotFoundException]::new("DistRoot fehlt: $DistRootPath")
|
||
|
|
}
|
||
|
|
|
||
|
|
$rootExes = Get-ChildItem -LiteralPath $DistRootPath -File -Filter "*.exe" -ErrorAction SilentlyContinue
|
||
|
|
$rootNames = @($rootExes | ForEach-Object { $_.Name })
|
||
|
|
$allowedSet = [System.Collections.Generic.HashSet[string]]::new([string[]]$Script:ExeAllowlist, [StringComparer]::OrdinalIgnoreCase)
|
||
|
|
|
||
|
|
foreach ($name in $rootNames) {
|
||
|
|
if (-not $allowedSet.Contains($name)) {
|
||
|
|
throw [System.InvalidOperationException]::new("Unerwartete EXE in DistRoot: $name")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ($name in $Script:ExeAllowlist) {
|
||
|
|
$full = Join-Path $DistRootPath $name
|
||
|
|
if (-not (Test-Path -LiteralPath $full)) {
|
||
|
|
throw [System.IO.FileNotFoundException]::new("Allowlist-Datei fehlt: $name")
|
||
|
|
}
|
||
|
|
$targets += Get-Item -LiteralPath $full
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
if (-not (Test-Path -LiteralPath $InstallerPathFull)) {
|
||
|
|
throw [System.IO.FileNotFoundException]::new("Installer fehlt: $InstallerPathFull")
|
||
|
|
}
|
||
|
|
$instName = [IO.Path]::GetFileName($InstallerPathFull)
|
||
|
|
$allowedInstaller = $false
|
||
|
|
foreach ($allowedName in $Script:InstallerAllowlist) {
|
||
|
|
if ($instName -eq $allowedName) { $allowedInstaller = $true; break }
|
||
|
|
}
|
||
|
|
if (-not $allowedInstaller) {
|
||
|
|
throw [System.InvalidOperationException]::new("Installer-Dateiname nicht erlaubt: $instName")
|
||
|
|
}
|
||
|
|
$targets += Get-Item -LiteralPath $InstallerPathFull
|
||
|
|
}
|
||
|
|
|
||
|
|
return ,$targets
|
||
|
|
}
|
||
|
|
|
||
|
|
function Assert-NotSigned {
|
||
|
|
param([System.IO.FileInfo]$File)
|
||
|
|
$sum = Get-SignatureSummary -Path $File.FullName
|
||
|
|
if ($sum.status -eq "Valid") {
|
||
|
|
throw [System.InvalidOperationException]::new("Bereits signiert: $($File.Name)")
|
||
|
|
}
|
||
|
|
if ($sum.status -ne "NotSigned") {
|
||
|
|
throw [System.InvalidOperationException]::new("Unerwarteter Signaturstatus '$($sum.status)' fuer $($File.Name)")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function Invoke-TripleVerify {
|
||
|
|
param(
|
||
|
|
[string]$Path,
|
||
|
|
[string]$SmctlPath,
|
||
|
|
[string]$SignToolPath,
|
||
|
|
[string]$PreSignSha
|
||
|
|
)
|
||
|
|
|
||
|
|
$postSignSha = Get-Sha256Hex -Path $Path
|
||
|
|
if ($postSignSha -eq $PreSignSha) {
|
||
|
|
throw [System.InvalidOperationException]::new("SHA-Invariante verletzt: Post-Sign identisch mit Pre-Sign")
|
||
|
|
}
|
||
|
|
|
||
|
|
$auth = Get-AuthenticodeSignature -FilePath $Path
|
||
|
|
if ($auth.Status -ne "Valid") {
|
||
|
|
throw [System.InvalidOperationException]::new("Get-AuthenticodeSignature nicht Valid")
|
||
|
|
}
|
||
|
|
if (-not $auth.SignerCertificate -or ($auth.SignerCertificate.Subject -notmatch [regex]::Escape($Script:PublisherExpected))) {
|
||
|
|
throw [System.InvalidOperationException]::new("Publisher nicht wie erwartet")
|
||
|
|
}
|
||
|
|
if (-not $auth.TimeStamperCertificate) {
|
||
|
|
throw [System.InvalidOperationException]::new("Kein RFC3161-Zeitstempel")
|
||
|
|
}
|
||
|
|
|
||
|
|
$stOut = & $SignToolPath verify /pa /v $Path 2>&1 | Out-String
|
||
|
|
if ($LASTEXITCODE -ne 0 -or $stOut -notmatch 'Successfully verified') {
|
||
|
|
throw [System.InvalidOperationException]::new("SignTool verify fehlgeschlagen")
|
||
|
|
}
|
||
|
|
|
||
|
|
$oldPath = $env:Path
|
||
|
|
try {
|
||
|
|
$signToolDir = Split-Path $SignToolPath -Parent
|
||
|
|
if ($signToolDir -and ($env:Path -notlike "*$signToolDir*")) {
|
||
|
|
$env:Path = "$signToolDir;$env:Path"
|
||
|
|
}
|
||
|
|
$smOut = & $SmctlPath sign verify --input $Path 2>&1 | Out-String
|
||
|
|
if ($LASTEXITCODE -ne 0 -or $smOut -notmatch 'SUCCESSFUL') {
|
||
|
|
throw [System.InvalidOperationException]::new("SMCTL sign verify fehlgeschlagen")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
finally {
|
||
|
|
$env:Path = $oldPath
|
||
|
|
}
|
||
|
|
|
||
|
|
$afterVerifySha = Get-Sha256Hex -Path $Path
|
||
|
|
if ($afterVerifySha -ne $postSignSha) {
|
||
|
|
throw [System.InvalidOperationException]::new("SHA nach Verify geaendert")
|
||
|
|
}
|
||
|
|
|
||
|
|
return @{
|
||
|
|
sha_pre = $PreSignSha
|
||
|
|
sha_post_sign = $postSignSha
|
||
|
|
sha_after_verify = $afterVerifySha
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function Write-Reports {
|
||
|
|
param(
|
||
|
|
[hashtable]$RunMeta,
|
||
|
|
[array]$Entries,
|
||
|
|
[string]$ReportDir
|
||
|
|
)
|
||
|
|
|
||
|
|
if (-not (Test-Path $ReportDir)) {
|
||
|
|
New-Item -ItemType Directory -Path $ReportDir -Force | Out-Null
|
||
|
|
}
|
||
|
|
|
||
|
|
$jsonPath = Join-Path $ReportDir "signing_report.json"
|
||
|
|
$txtPath = Join-Path $ReportDir "signing_report.txt"
|
||
|
|
$shaPath = Join-Path $ReportDir "SHA256SUMS.txt"
|
||
|
|
|
||
|
|
$payload = [ordered]@{
|
||
|
|
generated_at = (Get-Date).ToString("o")
|
||
|
|
phase = $RunMeta.phase
|
||
|
|
dry_run = [bool]$RunMeta.dry_run
|
||
|
|
exit_code = $RunMeta.exit_code
|
||
|
|
user = $RunMeta.user
|
||
|
|
targets_count = $Entries.Count
|
||
|
|
entries = $Entries
|
||
|
|
integration_todo = @(
|
||
|
|
"release.ps1 noch nicht integriert",
|
||
|
|
"build_installer.ps1 Precheck noch nicht integriert",
|
||
|
|
"Versionierte Installer-Kopie nach Signierung aus signiertem Hauptinstaller erzeugen",
|
||
|
|
"Testkanal publish_test_update.ps1 noch nicht gebaut"
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
($payload | ConvertTo-Json -Depth 8) | Set-Content -Path $jsonPath -Encoding UTF8
|
||
|
|
|
||
|
|
$txt = @()
|
||
|
|
$txt += "AzA Code Signing Report"
|
||
|
|
$txt += "Generated: $($payload.generated_at)"
|
||
|
|
$txt += "Phase: $($RunMeta.phase)"
|
||
|
|
$txt += "DryRun: $($RunMeta.dry_run)"
|
||
|
|
$txt += "Exit: $($RunMeta.exit_code)"
|
||
|
|
$txt += ""
|
||
|
|
foreach ($e in $Entries) {
|
||
|
|
$txt += "File: $($e.relative_path)"
|
||
|
|
$txt += " Size: $($e.size_bytes)"
|
||
|
|
$txt += " LastWrite: $($e.last_write_time)"
|
||
|
|
$txt += " SHA256: $($e.sha256_pre)"
|
||
|
|
$txt += " SigBefore: $($e.signature_status_before)"
|
||
|
|
$txt += " Planned: $($e.planned_action)"
|
||
|
|
$txt += " Result: $($e.result)"
|
||
|
|
$txt += ""
|
||
|
|
}
|
||
|
|
($txt -join "`r`n") | Set-Content -Path $txtPath -Encoding UTF8
|
||
|
|
|
||
|
|
$shaLines = @()
|
||
|
|
foreach ($e in $Entries) {
|
||
|
|
$shaLines += "$($e.sha256_pre) $($e.relative_path)"
|
||
|
|
if ($e.sha256_post) {
|
||
|
|
$shaLines += "$($e.sha256_post) $($e.relative_path) (post-sign)"
|
||
|
|
}
|
||
|
|
if ($e.sha256_after_verify) {
|
||
|
|
$shaLines += "$($e.sha256_after_verify) $($e.relative_path) (after-verify)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
($shaLines -join "`r`n") + "`r`n" | Set-Content -Path $shaPath -Encoding UTF8
|
||
|
|
|
||
|
|
return @{
|
||
|
|
json = $jsonPath
|
||
|
|
txt = $txtPath
|
||
|
|
sha = $shaPath
|
||
|
|
dir = $ReportDir
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# --- Main ---
|
||
|
|
$projectRoot = Get-ProjectRoot
|
||
|
|
if (-not $DistRoot) { $DistRoot = Join-Path $projectRoot "dist\aza_desktop" }
|
||
|
|
if (-not $InstallerPath) { $InstallerPath = Join-Path $projectRoot "dist\installer\aza_desktop_setup.exe" }
|
||
|
|
if (-not $ReportRoot) { $ReportRoot = Join-Path $projectRoot "dist\signing_reports" }
|
||
|
|
|
||
|
|
$runStamp = Get-Date -Format "yyyyMMdd_HHmmss"
|
||
|
|
$reportDir = Join-Path $ReportRoot $runStamp
|
||
|
|
|
||
|
|
$smctlPath = Find-Smctl
|
||
|
|
$signToolPath = Find-SignTool
|
||
|
|
$keypairAlias = $null
|
||
|
|
$entries = @()
|
||
|
|
$exitCode = $EXIT_OK
|
||
|
|
|
||
|
|
try {
|
||
|
|
Write-Host "sign_release_artifacts.ps1 Phase=$Phase DryRun=$($DryRun.IsPresent)"
|
||
|
|
|
||
|
|
Test-Environment -SmctlPath $smctlPath -RequireKeypair:(-not $DryRun) -KeypairAliasOut ([ref]$keypairAlias) | Out-Null
|
||
|
|
|
||
|
|
$targets = Get-TargetFiles -PhaseName $Phase -DistRootPath $DistRoot -InstallerPathFull $InstallerPath
|
||
|
|
|
||
|
|
foreach ($file in $targets) {
|
||
|
|
$rel = if ($Phase -eq "ExeOnly") {
|
||
|
|
"dist/aza_desktop/$($file.Name)"
|
||
|
|
} else {
|
||
|
|
"dist/installer/$($file.Name)"
|
||
|
|
}
|
||
|
|
|
||
|
|
$shaPre = Get-Sha256Hex -Path $file.FullName
|
||
|
|
$sigBefore = Get-SignatureSummary -Path $file.FullName
|
||
|
|
|
||
|
|
Assert-NotSigned -File $file
|
||
|
|
|
||
|
|
$entry = [ordered]@{
|
||
|
|
relative_path = $rel
|
||
|
|
size_bytes = $file.Length
|
||
|
|
last_write_time = $file.LastWriteTime.ToString("o")
|
||
|
|
sha256_pre = $shaPre
|
||
|
|
signature_status_before = $sigBefore.status
|
||
|
|
planned_action = if ($DryRun) { "dry-run-check-only" } else { "smctl-sign-simple-dynamic-auth" }
|
||
|
|
dry_run = [bool]$DryRun
|
||
|
|
result = "pending"
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($DryRun) {
|
||
|
|
$entry.result = "dry-run-ok"
|
||
|
|
$entry.sha256_post = $null
|
||
|
|
$entry.sha256_after_verify = $null
|
||
|
|
$entries += $entry
|
||
|
|
Write-Host " [DryRun OK] $rel SHA256=$shaPre Status=$($sigBefore.status)"
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
# Live-Pfad (heute nicht ausfuehren via -DryRun)
|
||
|
|
$signOut = & $smctlPath sign --keypair-alias $keypairAlias --input $file.FullName --simple --dynamic-auth 2>&1 | Out-String
|
||
|
|
if ($LASTEXITCODE -ne 0 -or $signOut -match 'failed|access_denied|403|error') {
|
||
|
|
$entry.result = "sign-failed"
|
||
|
|
$entry.error = Redact-Text -Text $signOut
|
||
|
|
$entries += $entry
|
||
|
|
throw [System.InvalidOperationException]::new("Signierung fehlgeschlagen")
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
$shaInfo = Invoke-TripleVerify -Path $file.FullName -SmctlPath $smctlPath -SignToolPath $signToolPath -PreSignSha $shaPre
|
||
|
|
$entry.sha256_post = $shaInfo.sha_post_sign
|
||
|
|
$entry.sha256_after_verify = $shaInfo.sha_after_verify
|
||
|
|
$entry.result = "signed-and-verified"
|
||
|
|
$entries += $entry
|
||
|
|
Write-Host " [OK] $rel"
|
||
|
|
}
|
||
|
|
catch {
|
||
|
|
$entry.result = "verify-failed"
|
||
|
|
$entry.error = Redact-Text -Text $_.Exception.Message
|
||
|
|
$entries += $entry
|
||
|
|
throw
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch {
|
||
|
|
$msg = $_.Exception.Message
|
||
|
|
Write-Host "FEHLER: $(Redact-Text -Text $msg)" -ForegroundColor Red
|
||
|
|
|
||
|
|
if ($msg -match 'smctl nicht gefunden|SM_HOST|SM_CLIENT_CERT_FILE|healthcheck|Can sign') { $exitCode = $EXIT_ENV }
|
||
|
|
elseif ($msg -match 'Keypair nicht eindeutig|Keypair-Alias') { $exitCode = $EXIT_KEYPAIR }
|
||
|
|
elseif ($msg -match 'Allowlist-Datei fehlt|fehlt:') { $exitCode = $EXIT_MISSING }
|
||
|
|
elseif ($msg -match 'Unerwartete EXE') { $exitCode = $EXIT_UNEXPECTED }
|
||
|
|
elseif ($msg -match 'Bereits signiert') { $exitCode = $EXIT_ALREADY_SIGNED }
|
||
|
|
elseif ($msg -match 'Signierung fehlgeschlagen|access_denied|403') { $exitCode = $EXIT_SIGN_FAIL }
|
||
|
|
elseif ($msg -match 'verify fehlgeschlagen|Publisher|Zeitstempel|Get-AuthenticodeSignature') { $exitCode = $EXIT_VERIFY_FAIL }
|
||
|
|
elseif ($msg -match 'SHA-Invariante|SHA nach Verify') { $exitCode = $EXIT_SHA }
|
||
|
|
elseif ($msg -match 'Authentifizierung|dynamic-auth|auth') { $exitCode = $EXIT_AUTH }
|
||
|
|
else { $exitCode = 20 }
|
||
|
|
}
|
||
|
|
|
||
|
|
$runMeta = @{
|
||
|
|
phase = $Phase
|
||
|
|
dry_run = [bool]$DryRun
|
||
|
|
exit_code = $exitCode
|
||
|
|
user = [Environment]::UserName
|
||
|
|
}
|
||
|
|
|
||
|
|
$reportPaths = Write-Reports -RunMeta $runMeta -Entries $entries -ReportDir $reportDir
|
||
|
|
Write-Host "Report: $($reportPaths.dir)"
|
||
|
|
|
||
|
|
exit $exitCode
|