Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Powershell script to update registry information after war update #494

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@ generated
.idea/
*.orig

msi/build
venv/

153 changes: 153 additions & 0 deletions msi/build/Update-JenkinsVersion.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<#
.Synopsis
Upgrades the version information in the register from the current Jenkins war file.
.Description
The purpose of this script is to update the version of Jenkins in the registry
when the user may have upgraded the war file in place. The script probes the
registry for information about the Jenkins install (path to war, etc.) and
then grabs the version information from the war to update the values in the
registry so they match the version of the war file.

This will help with security scanners that look in the registry for versions
of software and flag things when they are too low. The information in the
registry may be very old compared to what version of the war file is
actually installed on the system.
#>


# Self-elevate the script if required
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
# We may be running under powershell.exe or pwsh.exe, make sure we relaunch the same one.
$Executable = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
# Launching with RunAs to get elevation
$CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments
Start-Process -FilePath $Executable -Verb Runas -ArgumentList $CommandLine
Exit
}
}

function New-TemporaryDirectory {
$Parent = [System.IO.Path]::GetTempPath()
do {
$Name = [System.IO.Path]::GetRandomFileName()
$Item = New-Item -Path $Parent -Name $Name -ItemType "Directory" -ErrorAction SilentlyContinue
} while (-not $Item)
return $Item.FullName
}

function Exit-Script($Message, $Fatal = $False) {
$ExitCode = 0
if($Fatal) {
Write-Error $Message
} else {
Write-Host $Message
}
Read-Host "Press ENTER to continue"
Exit $ExitCode
}

# Let's find the location of the war file...
$JenkinsDir = Get-ItemPropertyValue -Path HKLM:\Software\Jenkins\InstalledProducts\Jenkins -Name InstallLocation -ErrorAction SilentlyContinue

if (($Null -eq $JenkinsDir) -or [String]::IsNullOrWhiteSpace($JenkinsDir)) {
Exit-Script -Message "Jenkins does not seem to be installed. Please verify you have previously installed using the MSI installer" -Fatal $True
}

$WarPath = Join-Path $JenkinsDir "jenkins.war"
if(-Not (Test-Path $WarPath)) {
Exit-Script -Message "Could not find war file at location found in registry, please verify Jenkins installation" -Fatal $True
}

# Get the MANIFEST.MF file from the war file to get the version of Jenkins
$TempWorkDir = New-TemporaryDirectory
$ManifestFile = Join-Path $TempWorkDir "MANIFEST.MF"
$Zip = [IO.Compression.ZipFile]::OpenRead($WarPath)
$Zip.Entries | Where-Object { $_.Name -like "MANIFEST.MF" } | ForEach-Object { [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $ManiFestFile, $True) }
$Zip.Dispose()

$JenkinsVersion = $(Get-Content $ManiFestFile | Select-String -Pattern "^Jenkins-Version:\s*(.*)" | ForEach-Object { $_.Matches } | ForEach-Object { $_.Groups[1].Value } | Select-Object -First 1)
Remove-Item -Path $ManifestFile

# Convert the Jenkins version into what should be in the registry
$VersionItems = $JenkinsVersion.Split(".") | ForEach-Object { [int]::Parse($_) }

# Use the same encoding algorithm as the installer to encode the version into the correct format
$RegistryEncodedVersion = 0
$Major = $VersionItems[0]
if ($VersionItems.Length -le 2) {
$Minor = 0
if (($VersionItems.Length -gt 1) -and ($VersionItems[1] -gt 255)) {
$Minor = $VersionItems[1]
$RegistryEncodedVersion = $RegistryEncodedVersion -bor ((($Major -band 0xff) -shl 24) -bor 0x00ff0000 -bor (($Minor * 10) -band 0x0000ffff))
}
else {
$RegistryEncodedVersion = $RegistryEncodedVersion -bor (($Major -band 0xff) -shl 24)
}
}
else {
$Minor = $VersionItems[1]
if ($Minor -gt 255) {
$RegistryEncodedVersion = $RegistryEncodedVersion -bor ((($Major -band 0xff) -shl 24) -bor 0x00ff0000 -bor ((($Minor * 10) + $VersionItems[2]) -band 0x0000ffff))
}
else {
$RegistryEncodedVersion = $RegistryEncodedVersion -bor ((($Major -band 0xff) -shl 24) -bor (($Minor -band 0xff) -shl 16) -bor ($VersionItems[2] -band 0x0000ffff))
}
}

$ProductName = "Jenkins $JenkinsVersion"

# Find the registry key for Jenkins in the Installer\Products area and CurrentVersion\Uninstall
$JenkinsProductsRegistryKey = Get-ChildItem -Path HKLM:\SOFTWARE\Classes\Installer\Products | Where-Object { $_.GetValue("ProductName", "").StartsWith("Jenkins") }

$JenkinsUninstallRegistryKey = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall | Where-Object { $_.GetValue("DisplayName", "").StartsWith("Jenkins") }

if (($Null -eq $JenkinsProductsRegistryKey) -or ($Null -eq $JenkinsUninstallRegistryKey)) {
Exit-Script -Message "Could not find the product information for Jenkins" -Fatal $True
}

# Update the Installer\Products area
$RegistryPath = $JenkinsProductsRegistryKey.Name.Substring($JenkinsProductsRegistryKey.Name.IndexOf("\"))

$OldProductName = $JenkinsProductsRegistryKey.GetValue("ProductName", "")
if ($OldProductName -ne $ProductName) {
Set-ItemProperty -Path HKLM:$RegistryPath -Name "ProductName" -Type String -Value $ProductName
}

$OldVersion = $JenkinsProductsRegistryKey.GetValue("Version", 0)
if ($OldVersion -ne $RegistryEncodedVersion) {
Set-ItemProperty -Path HKLM:$RegistryPath -Name "Version" -Type DWord -Value $RegistryEncodedVersion
}

# Update the Uninstall area
$RegistryPath = $JenkinsUninstallRegistryKey.Name.Substring($JenkinsUninstallRegistryKey.Name.IndexOf("\"))
$OldDisplayName = $JenkinsUninstallRegistryKey.GetValue("DisplayName", "")
if ($OldDisplayName -ne $ProductName) {
Set-ItemProperty -Path HKLM:$RegistryPath -Name "DisplayName" -Type String -Value $ProductName
}

$OldDisplayVersion = $JenkinsUninstallRegistryKey.GetValue("DisplayVersion", "")
$DisplayVersion = "{0}.{1}.{2}" -f ($RegistryEncodedVersion -shr 24), (($RegistryEncodedVersion -shr 16) -band 0xff), ($RegistryEncodedVersion -band 0xffff)
if ($OldDisplayVersion -ne $DisplayVersion) {
Set-ItemProperty -Path HKLM:$RegistryPath -Name "DisplayVersion" -Type String -Value $DisplayVersion
}

$OldVersion = $JenkinsUninstallRegistryKey.GetValue("Version", 0)
if ($OldVersion -ne $RegistryEncodedVersion) {
Set-ItemProperty -Path HKLM:$RegistryPath -Name "Version" -Type DWord -Value $RegistryEncodedVersion
}

$OldVersionMajor = $JenkinsUninstallRegistryKey.GetValue("VersionMajor", 0)
$VersionMajor = $RegistryEncodedVersion -shr 24
if ($OldVersionMajor -ne $VersionMajor) {

Set-ItemProperty -Path HKLM:$RegistryPath -Name "VersionMajor" -Type DWord -Value $VersionMajor
}

$OldVersionMinor = $JenkinsUninstallRegistryKey.GetValue("VersionMinor", 0)
$VersionMinor = ($RegistryEncodedVersion -shr 16) -band 0xff
if ($OldVersionMinor -ne $VersionMinor) {
Set-ItemProperty -Path HKLM:$RegistryPath -Name "VersionMinor" -Type DWord -Value $VersionMinor
}

Read-Host "Press ENTER to continue"
98 changes: 59 additions & 39 deletions msi/build/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,48 @@ Param(
[String] $InstallerIco = ''
)

function Set-CodeSigningSignature {
param (
$Path,
$JenkinsVersion
)

if((-not ([System.String]::IsNullOrWhiteSpace($env:PKCS12_FILE)) -and (Test-Path $env:PKCS12_FILE)) -and (-not [System.String]::IsNullOrWhiteSpace($env:SIGN_STOREPASS))) {
Write-Host "Signing $Path"
# always disable tracing here
Set-PSDebug -Trace 0
$retries = 10
$i = $retries
# Create an array of timestamp servers that includes each of the known timestamp servers duplicated $retries times so that the list won't be exhausted during retry
# Start with digicert because we purchased the code signing certificate from digicert
$timestampservers = "http://timestamp.digicert.com", "http://rfc3161timestamp.globalsign.com/advanced", "http://timestamp.sectigo.com/", "http://timestamp.verisign.com/scripts/timstamp.dll" * $retries
for(; $i -gt 0; $i--) {
# Pop first entry from timestamp server array, use it as timestamp server for this attempt
$timestamp, $timestampservers = $timestampservers
# Submit SHA256 digest to RFC 3161 timestamp server
$p = Start-Process -Wait -PassThru -NoNewWindow -FilePath "signtool.exe" -ArgumentList "sign /v /f `"${env:PKCS12_FILE}`" /p ${env:SIGN_STOREPASS} /tr $timestamp /td SHA256 /fd SHA256 /d `"Jenkins Automation Server ${JenkinsVersion}`" /du `"https://jenkins.io`" $Path"
$p.WaitForExit()
# we will retry up to $retries times until we get a good exit code
if($p.ExitCode -eq 0) {
break
} else {
Start-Sleep -Seconds 15
}
}

if($i -le 0) {
Write-Error "signtool did not complete successfully after $retries tries"
exit -1
}

if($UseTracing) { Set-PSDebug -Trace 1 }

Write-Host "Checking the signature"
# It will print the entire certificate chain with details
signtool verify /v /pa /all $Path
}
}

if($UseTracing) { Set-PSDebug -Trace 1 }

if([String]::IsNullOrWhiteSpace($War)) {
Expand Down Expand Up @@ -73,50 +115,28 @@ if($MSBuildPath -ne '') {
$MSBuildPath = [System.IO.Path]::GetDirectoryName($MSBuildPath)
}
$env:PATH = $env:PATH + ";" + $MSBuildPath
} else {
# try to find it with vswhere
$MSBuildPath = & 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -products * -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe
if(($MSBuildPath -ne '') -and $MSBuildPath.ToLower().EndsWith('msbuild.exe')) {
$MSBuildPath = [System.IO.Path]::GetDirectoryName($MSBuildPath)
}
$env:PATH = $env:PATH + ";" + $MSBuildPath
}

# Sign the Update-JenkinsVersion.ps1 script if we have PKCS files
Copy-Item -Force -Path .\Update-JenkinsVersion.ps1 -Destination tmp
Set-CodeSigningSignature -Path .\tmp\Update-JenkinsVersion.ps1 -JenkinsVersion $JenkinsVersion

msbuild "jenkins.wixproj" /p:Stable="${isLts}" /p:WAR="${War}" /p:Configuration=Release /p:DisplayVersion=$JenkinsVersion /p:ProductName="${ProductName}" /p:ProductSummary="${ProductSummary}" /p:ProductVendor="${ProductVendor}" /p:ArtifactName="${ArtifactName}" /p:BannerBmp="${BannerBmp}" /p:DialogBmp="${DialogBmp}" /p:InstallerIco="${InstallerIco}"

Get-ChildItem .\bin\Release -Filter *.msi -Recurse |
Foreach-Object {
if((-not ([System.String]::IsNullOrWhiteSpace($env:PKCS12_FILE)) -and (Test-Path $env:PKCS12_FILE)) -and (-not [System.String]::IsNullOrWhiteSpace($env:SIGN_STOREPASS))) {
Write-Host "Signing installer: $($_.FullName)"
# always disable tracing here
Set-PSDebug -Trace 0
$retries = 10
$i = $retries
# Create an array of timestamp servers that includes each of the known timestamp servers duplicated $retries times so that the list won't be exhausted during retry
# Start with digicert because we purchased the code signing certificate from digicert
$timestampservers = "http://timestamp.digicert.com", "http://rfc3161timestamp.globalsign.com/advanced", "http://timestamp.sectigo.com/", "http://timestamp.verisign.com/scripts/timstamp.dll" * $retries
for(; $i -gt 0; $i--) {
# Pop first entry from timestamp server array, use it as timestamp server for this attempt
$timestamp, $timestampservers = $timestampservers
# Submit SHA256 digest to RFC 3161 timestamp server
$p = Start-Process -Wait -PassThru -NoNewWindow -FilePath "signtool.exe" -ArgumentList "sign /v /f `"${env:PKCS12_FILE}`" /p ${env:SIGN_STOREPASS} /tr $timestamp /td SHA256 /fd SHA256 /d `"Jenkins Automation Server ${JenkinsVersion}`" /du `"https://jenkins.io`" $($_.FullName)"
$p.WaitForExit()
# we will retry up to $retries times until we get a good exit code
if($p.ExitCode -eq 0) {
break
} else {
Start-Sleep -Seconds 15
}
}

if($i -le 0) {
Write-Error "signtool did not complete successfully after $retries tries"
exit -1
}

if($UseTracing) { Set-PSDebug -Trace 1 }

Write-Host "Checking the signature"
# It will print the entire certificate chain with details
signtool verify /v /pa /all $_.FullName
}

$sha256 = (Get-FileHash -Algorithm SHA256 -Path $_.FullName).Hash.ToString().ToLower()
Set-Content -Path "$($_.FullName).sha256" -Value "$sha256 $($_.Name)" -Force
$env:MSI_SHA256 = $sha256
}
Set-CodeSigningSignature -Path $($_.FullName) -JenkinsVersion $JenkinsVersion

$sha256 = (Get-FileHash -Algorithm SHA256 -Path $_.FullName).Hash.ToString().ToLower()
Set-Content -Path "$($_.FullName).sha256" -Value "$sha256 $($_.Name)" -Force
$env:MSI_SHA256 = $sha256
}

if ($UseTracing) { Set-PSDebug -Trace 0 }
1 change: 1 addition & 0 deletions msi/build/jenkins.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@
<File Id="JenkinsXml" Name="jenkins.xml" Source='tmp/jenkins.xml' DiskId='1' />
<File Name="jenkins.exe.config" Source='jenkins.exe.config' DiskId='1' />
<File Name="$(var.ArtifactName).war" Source='$(var.WAR)' DiskId='1' />
<File Name="Update-JenkinsVersion.ps1" Source='tmp/Update-JenkinsVersion.ps1' DiskId='1' />

<!-- Update the XML file with the values selected during install -->
<util:XmlFile Id='JenkinsXmlJenkinsData' Action='setValue' ElementPath='//service/env/@value' File='[#JenkinsXml]' Value='[JENKINS_ROOT].jenkins' Sequence="1"/>
Expand Down