Last active
November 7, 2025 23:55
-
-
Save graphemecluster/8c54087cfaba7cdfe6b97d15885842ee to your computer and use it in GitHub Desktop.
Script for signing a TypeDuck Windows release (since I've got a physical HSM). Supply the version as the first argument and it will download the exe from a GitHub release, sign its content and itself and upload back it. Require access to the https://github.com/TypeDuck-HK/TypeDuck-Windows repo. Download TypeDuck: https://typeduck.hk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [CmdletBinding(PositionalBinding = $false)] | |
| param( | |
| [Parameter(Mandatory, Position = 0)] | |
| [ValidatePattern("^v?\d+\.\d+\.\d+$")] | |
| [string]$Version, | |
| [Parameter()] | |
| [ValidatePattern("^\d+$")] | |
| [string]$Build = "0" | |
| ) | |
| # common parameter, must be obtained this way | |
| $VerboseFlag = $PSBoundParameters["Verbose"] -or $VerbosePreference -ne "SilentlyContinue" ? @("/v") : @() | |
| if (!(Get-Command wget -ErrorAction SilentlyContinue)) { | |
| function wget { | |
| [CmdletBinding(PositionalBinding = $false)] | |
| param( | |
| [Parameter(Mandatory, Position = 0)] | |
| [string]$Uri, | |
| [Parameter()] | |
| [string]$P = "." | |
| ) | |
| $fileName = Split-Path $Uri -Leaf | |
| Invoke-WebRequest $Uri -OutFile $P/$fileName | |
| } | |
| } | |
| function Sign { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory, ValueFromRemainingArguments, ValueFromPipeline, ValueFromPipelineByPropertyName)] | |
| [string[]]$Paths | |
| ) | |
| signtool sign @VerboseFlag /n "The Education University of Hong Kong" /tr http://timestamp.sectigo.com /td sha256 /fd sha256 @Paths | |
| if ($LASTEXITCODE) { | |
| throw "Failed to sign" | |
| } | |
| } | |
| function Verify { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory, ValueFromRemainingArguments, ValueFromPipeline, ValueFromPipelineByPropertyName)] | |
| [string[]]$Paths | |
| ) | |
| signtool verify @VerboseFlag /pa @Paths *> $null | |
| return !$LASTEXITCODE | |
| } | |
| # https://stackoverflow.com/a/77056311 | |
| $oldErrorActionPreference = $global:ErrorActionPreference | |
| $global:ErrorActionPreference = "Stop" | |
| try { | |
| if ($PSBoundParameters.ContainsKey("ErrorAction") -and $PSBoundParameters["ErrorAction"] -ne "Stop") { | |
| throw "The 'ErrorAction' common parameter must be 'Stop' if provided to avoid partial completion of the whole process" | |
| } | |
| $missingCommands = @("wget", "7z", "signtool", "makensis", "gh") | Where-Object { !(Get-Command $_ -ErrorAction SilentlyContinue) } | |
| if ($missingCommands) { | |
| $message = "Please specify the path$($missingCommands.Count -gt 1 ? 's' : '') to $($missingCommands -join ', ') in the PATH." | |
| if ($missingCommands -contains "signtool") { | |
| $message += " Hint: You should run this script inside a Developer PowerShell for Visual Studio for signtool to be defined" | |
| } | |
| throw $message | |
| } | |
| $WEASEL_VERSION = $Version -replace "^v", "" | |
| $WEASEL_BUILD = $Build | |
| $SOURCE_URL_PREFIX = "https://raw.githubusercontent.com/TypeDuck-HK/TypeDuck-Windows/refs/tags/v${WEASEL_VERSION}" | |
| # If the folder already exists, the following produces an error and the script is terminated, which prevent re-signing | |
| mkdir $PSScriptRoot/${WEASEL_VERSION} | |
| pushd $PSScriptRoot/${WEASEL_VERSION} | |
| try { | |
| gh release download v${WEASEL_VERSION} --repo TypeDuck-HK/TypeDuck-Windows | |
| $executable = Get-Item TypeDuck*.exe | |
| if ($executable.Count -gt 1) { | |
| throw "Expect only a single executable, got $($executable.Count)" | |
| } | |
| if (Verify TypeDuck*.exe) { | |
| throw "$($executable.Name) is already signed" | |
| } | |
| 7z x TypeDuck*.exe -aou -ooutput | |
| pushd output | |
| try { | |
| # TODO rime.dll should be signed in advance (right after it is built) | |
| # TODO 7z.exe, 7z.dll, curl.exe, WinSparkle.dll and the NSIS plugin DLLs are not signed. n.b. if they are signed, their original hashes will be lost. Anti-virus softwares no longer recognise them and may flag them the other way round. | |
| # TODO When we migrate to newer versions of Weasel, there will be a Win32 folder, the executables in which need to be signed too, c.f. https://github.com/rime/rime-cantonese/blob/41816434b0b2457c772eae5e7dfd85054e8f5e24/.ci/package-windows.sh | |
| # n.b. Expect case-insensitive match, be careful when running in POSIX systems | |
| Sign TypeDuck*.exe TypeDuck*.dll TypeDuck*.ime rime.dll | |
| mkdir ../resource | |
| wget ${SOURCE_URL_PREFIX}/resource/TypeDuck.ico -P ../resource | |
| wget ${SOURCE_URL_PREFIX}/resource/Installer.bmp -P ../resource | |
| wget ${SOURCE_URL_PREFIX}/output/install.nsi | |
| mkdir archives | |
| makensis /DWEASEL_VERSION=${WEASEL_VERSION} /DWEASEL_BUILD=${WEASEL_BUILD} install.nsi | |
| Sign archives/TypeDuck*.exe | |
| gh release upload v${WEASEL_VERSION} archives/TypeDuck*.exe --repo TypeDuck-HK/TypeDuck-Windows --clobber | |
| } | |
| finally { | |
| popd | |
| } | |
| } | |
| finally { | |
| popd | |
| } | |
| } | |
| finally { | |
| $global:ErrorActionPreference = $oldErrorActionPreference | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment