11# ============================================================================
2- # Weather Widget - MSI Package Builder (Traditional Installer )
2+ # Weather Widget - MSI Package Builder (Microsoft Store / Sideload )
33# ============================================================================
44# Usage:
5- # .\installer\build-msi.ps1 -Version "1.0.0.0"
6- # .\installer\build-msi.ps1 -Version "1.0.0.0" -CertPath "cert.pfx" -CertPassword "pass"
7- # .\installer\build-msi.ps1 -Version "1.0.0.0" -SkipSign
5+ # # Sign with certificate from Windows certificate store (by thumbprint):
6+ # .\installer\build-msi.ps1 -Version "0.0.5.0" -CertThumbprint "A9D97675327F7BF344BA308A357E91141A1DDE50"
7+ #
8+ # # Sign with a .pfx file:
9+ # .\installer\build-msi.ps1 -Version "0.0.5.0" -CertPath "cert.pfx" -CertPassword "pass"
10+ #
11+ # # Build without signing (for testing only):
12+ # .\installer\build-msi.ps1 -Version "0.0.5.0" -SkipSign
813#
914# Prerequisites:
1015# - Go 1.25+ with CGO enabled (for Fyne)
1116# - go-winres: go install github.com/tc-hib/go-winres@latest
12- # - WiX v4+: dotnet tool install --global wix
13- # - Code signing certificate (for production builds)
17+ # - WiX v4+: dotnet tool install --global wix (or WiX v7 via winget)
18+ # - signtool.exe (from Windows SDK)
19+ # - Code signing certificate (installed in cert store or as .pfx file)
1420# ============================================================================
1521
1622param (
1723 [Parameter (Mandatory = $true )]
1824 [string ]$Version ,
1925
26+ [string ]$CertThumbprint = " A9D97675327F7BF344BA308A357E91141A1DDE50" ,
2027 [string ]$CertPath = " " ,
2128 [string ]$CertPassword = " " ,
2229 [switch ]$SkipSign ,
@@ -35,6 +42,41 @@ if (-not (Test-Path $GoWinRes)) {
3542 throw " go-winres not found at $GoWinRes . Install with: go install github.com/tc-hib/go-winres@latest"
3643}
3744
45+ # Determine signing mode
46+ $SignMode = " none"
47+ if (-not $SkipSign ) {
48+ if ($CertThumbprint ) {
49+ $SignMode = " thumbprint"
50+ } elseif ($CertPath ) {
51+ $SignMode = " pfx"
52+ }
53+ }
54+
55+ # Helper function to sign a file
56+ function Sign-File {
57+ param ([string ]$FilePath , [string ]$Description )
58+
59+ if ($SignMode -eq " none" ) { return }
60+
61+ $signArgs = @ (" sign" , " /fd" , " SHA256" , " /tr" , " http://timestamp.digicert.com" , " /td" , " SHA256" )
62+
63+ if ($SignMode -eq " thumbprint" ) {
64+ $signArgs += @ (" /sha1" , $CertThumbprint , " /s" , " My" )
65+ } else {
66+ $signArgs += @ (" /f" , $CertPath )
67+ if ($CertPassword ) { $signArgs += @ (" /p" , $CertPassword ) }
68+ }
69+
70+ if ($Description ) {
71+ $signArgs += @ (" /d" , $Description )
72+ }
73+
74+ $signArgs += $FilePath
75+
76+ & signtool @signArgs
77+ if ($LASTEXITCODE -ne 0 ) { throw " Signing failed for $FilePath " }
78+ }
79+
3880Push-Location $ProjectRoot
3981
4082try {
4486 Write-Host " "
4587 Write-Host " ============================================" - ForegroundColor Cyan
4688 Write-Host " Weather Widget MSI Builder v$Version " - ForegroundColor Cyan
89+ Write-Host " Sign mode: $SignMode " - ForegroundColor Cyan
4790 Write-Host " ============================================" - ForegroundColor Cyan
4891 Write-Host " "
4992
@@ -60,6 +103,15 @@ try {
60103 # -------------------------------------------------------------------------
61104 if (-not $SkipBuild ) {
62105 Write-Host " [2/5] Generating Windows resources..." - ForegroundColor Yellow
106+
107+ # Update version in winres.json before generating
108+ $winresJson = Get-Content " .\winres\winres.json" - Raw | ConvertFrom-Json
109+ $winresJson.RT_VERSION .' #1' .' 0409' .fixed.file_version = $Version
110+ $winresJson.RT_VERSION .' #1' .' 0409' .fixed.product_version = $Version
111+ $winresJson.RT_VERSION .' #1' .' 0409' .info.' 0409' .FileVersion = $Version
112+ $winresJson.RT_VERSION .' #1' .' 0409' .info.' 0409' .ProductVersion = $Version
113+ $winresJson | ConvertTo-Json - Depth 10 | Set-Content " .\winres\winres.json"
114+
63115 & $GoWinRes make - -in .\winres\winres.json -- product- version $Version -- file- version $Version
64116 if ($LASTEXITCODE -ne 0 ) { throw " go-winres failed" }
65117 Write-Host " Done." - ForegroundColor Green
@@ -75,22 +127,21 @@ try {
75127 $ldflags = " -H windowsgui -s -w -X main.version=$Version "
76128 go build - ldflags= " $ldflags " - o " $BuildDir \weatherwidget.exe" .\cmd\weatherwidget\
77129 if ($LASTEXITCODE -ne 0 ) { throw " Go build failed" }
78- Write-Host " Done." - ForegroundColor Green
130+ Write-Host " Done. Output: $BuildDir \weatherwidget.exe " - ForegroundColor Green
79131 } else {
80132 Write-Host " [2/5] Skipping (SkipBuild)." - ForegroundColor DarkGray
81133 Write-Host " [3/5] Skipping (SkipBuild)." - ForegroundColor DarkGray
134+ if (-not (Test-Path " $BuildDir \weatherwidget.exe" )) {
135+ throw " No existing exe found at $BuildDir \weatherwidget.exe. Remove -SkipBuild flag."
136+ }
82137 }
83138
84139 # -------------------------------------------------------------------------
85140 # Step 4: Sign the executable
86141 # -------------------------------------------------------------------------
87- if (-not $SkipSign -and $CertPath ) {
142+ if ($SignMode -ne " none " ) {
88143 Write-Host " [4/5] Signing executable..." - ForegroundColor Yellow
89- $signArgs = @ (" sign" , " /fd" , " SHA256" , " /tr" , " http://timestamp.digicert.com" , " /td" , " SHA256" , " /f" , $CertPath )
90- if ($CertPassword ) { $signArgs += @ (" /p" , $CertPassword ) }
91- $signArgs += " $BuildDir \weatherwidget.exe"
92- & signtool @signArgs
93- if ($LASTEXITCODE -ne 0 ) { throw " Signing failed" }
144+ Sign- File - FilePath " $BuildDir \weatherwidget.exe" - Description " Weather Widget"
94145 Write-Host " Done." - ForegroundColor Green
95146 } else {
96147 Write-Host " [4/5] Skipping signing." - ForegroundColor DarkGray
@@ -106,29 +157,26 @@ try {
106157 if (-not $wixExe ) {
107158 $wixPaths = @ (
108159 " ${env: ProgramFiles} \WiX Toolset v7.0\bin\wix.exe" ,
109- " ${env: ProgramFiles} \WiX Toolset v4.0\bin\wix.exe" ,
110- " ${env: ProgramFiles(x86)} \WiX Toolset v3.14\bin\candle.exe"
160+ " ${env: ProgramFiles} \WiX Toolset v4.0\bin\wix.exe"
111161 )
112162 foreach ($p in $wixPaths ) {
113163 if (Test-Path $p ) { $wixExe = $p ; break }
114164 }
115165 }
116166 if (-not $wixExe ) {
117- throw " WiX not found. Install with: winget install WiXToolset.WiXCLI "
167+ throw " WiX not found. Install with: dotnet tool install --global wix "
118168 }
119169 Write-Host " Using WiX: $wixExe " - ForegroundColor DarkGray
120170
121171 & $wixExe build .\installer\Package.wxs - d BuildDir= $BuildDir - d Version= $Version - o $OutputMsi
122172 if ($LASTEXITCODE -ne 0 ) { throw " WiX build failed" }
173+ Write-Host " MSI created." - ForegroundColor Green
123174
124175 # Sign the MSI
125- if (-not $SkipSign -and $CertPath ) {
176+ if ($SignMode -ne " none " ) {
126177 Write-Host " Signing MSI..." - ForegroundColor Yellow
127- $signArgs = @ (" sign" , " /fd" , " SHA256" , " /tr" , " http://timestamp.digicert.com" , " /td" , " SHA256" , " /f" , $CertPath )
128- if ($CertPassword ) { $signArgs += @ (" /p" , $CertPassword ) }
129- $signArgs += $OutputMsi
130- & signtool @signArgs
131- if ($LASTEXITCODE -ne 0 ) { throw " MSI signing failed" }
178+ Sign- File - FilePath $OutputMsi - Description " Weather Widget Installer"
179+ Write-Host " MSI signed." - ForegroundColor Green
132180 }
133181
134182 Write-Host " "
@@ -138,6 +186,13 @@ try {
138186 Write-Host " ============================================" - ForegroundColor Green
139187 Write-Host " "
140188
189+ if ($SignMode -eq " none" ) {
190+ Write-Host " WARNING: Package is unsigned. Microsoft Store" - ForegroundColor Yellow
191+ Write-Host " requires SHA256 code signing (Policy 10.2.9)." - ForegroundColor Yellow
192+ Write-Host " Use -CertThumbprint or -CertPath to sign." - ForegroundColor Yellow
193+ Write-Host " "
194+ }
195+
141196} finally {
142197 Pop-Location
143198}
0 commit comments