#Requires -Version 5.1 <# Signs the LiveWall binaries and installer with Authenticode. Why: an unsigned download is the single biggest reason antivirus engines and SmartScreen treat a small installer as suspicious. Signing does not guarantee a clean scan, but it is what the vendors ask for when they answer a false-positive report, and it fixes SmartScreen's "unknown publisher". You need a code-signing certificate: * EV certificate - immediate SmartScreen/reputation trust, hardware token, ~$300-500/year. * OV certificate - works, but reputation builds up over downloads and weeks. * Self-signed - useless for this. Do not bother: an untrusted signature is no better than none. Since June 2023 all public code-signing keys must live on FIPS 140-2 hardware, so most setups mean a token/HSM in the machine and the certificate visible in the store: .\installer\sign.ps1 -Thumbprint 1A2B3C... # cert from the Windows certificate store .\installer\sign.ps1 -Pfx .\codesign.pfx # file-based cert (asks for the password) .\installer\sign.ps1 -Thumbprint ... -WhatIf # show the commands only Everything is signed with SHA-256 and an RFC 3161 timestamp, so the signature stays valid after the certificate expires. #> param( [string]$Thumbprint, [string]$Pfx, [SecureString]$Password, [string]$TimestampUrl = 'http://timestamp.digicert.com', [string]$Signtool, [switch]$WhatIf ) $ErrorActionPreference = 'Stop' $root = $PSScriptRoot | Split-Path -Parent function Find-Signtool { if ($Signtool) { return $Signtool } $cmd = Get-Command signtool.exe -ErrorAction SilentlyContinue if ($cmd) { return $cmd.Source } $roots = @( "${env:ProgramFiles(x86)}\Windows Kits\10\bin", "$env:ProgramFiles\Windows Kits\10\bin" ) | Where-Object { Test-Path $_ } foreach ($r in $roots) { $candidate = Get-ChildItem $r -Recurse -Filter signtool.exe -ErrorAction SilentlyContinue | Where-Object { $_.FullName -match 'x64' } | Sort-Object FullName -Descending | Select-Object -First 1 if ($candidate) { return $candidate.FullName } } throw 'signtool.exe not found. Install the Windows SDK ("Windows SDK Signing Tools") or pass -Signtool .' } $signtool = Find-Signtool # Files that end up on the user's machine. LiveWall.exe and the SDK matter most: those are what an # engine inspects after the installer has run. $files = @( (Join-Path $root 'prebuilt\LiveWall.exe'), (Join-Path $root 'prebuilt\LiveWall.dll'), (Join-Path $root 'prebuilt\LiveWall.Sdk.dll'), (Join-Path $root 'prebuilt\WebView2Loader.dll') ) | Where-Object { Test-Path $_ } $installers = Get-ChildItem $root -Filter 'LiveWall-Setup-*.exe' | Where-Object { $_.Name -notlike '*-old*' } if (-not $files -and -not $installers) { throw "Nothing to sign: publish first (.\build.ps1 -Task publish) and build the installer." } if ($Pfx) { if (-not (Test-Path $Pfx)) { throw "Certificate file not found: $Pfx" } if (-not $Password) { $Password = Read-Host -AsSecureString "Password for $Pfx" } $plain = [Runtime.InteropServices.Marshal]::PtrToStringAuto( [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)) $certArgs = @('/f', (Resolve-Path $Pfx).Path, '/p', $plain) } elseif ($Thumbprint) { $certArgs = @('/sha1', ($Thumbprint -replace '\s', '')) } else { throw 'Pass -Thumbprint or -Pfx . Run with -WhatIf to preview.' } Write-Host "signtool : $signtool" Write-Host "files : $($files.Count) binaries, $($installers.Count) installer(s)" Write-Host '' foreach ($file in @($files) + @($installers.FullName)) { $signArgs = @('sign', '/fd', 'sha256', '/tr', $TimestampUrl, '/td', 'sha256') + $certArgs + @($file) if ($WhatIf) { Write-Host "would run: signtool $($signArgs -join ' ')" continue } Write-Host "signing $([IO.Path]::GetFileName($file))" & $signtool @signArgs if ($LASTEXITCODE -ne 0) { throw "signtool failed for $file (exit $LASTEXITCODE)" } } if (-not $WhatIf) { Write-Host '' Write-Host 'Verifying…' foreach ($file in @($files) + @($installers.FullName)) { & $signtool verify /pa /q $file if ($LASTEXITCODE -ne 0) { throw "Signature verification failed for $file" } } Write-Host '' Write-Host 'Signed and verified. Rebuild the hashes for your release notes:' -ForegroundColor Green foreach ($file in @($files) + @($installers.FullName)) { $hash = (Get-FileHash $file -Algorithm SHA256).Hash.ToLower() Write-Host (" {0} {1}" -f $hash, [IO.Path]::GetFileName($file)) } }