Skip to content

Commit 701b919

Browse files
authored
MSI: add function to generate a MSP (PowerShell#6445)
add a function to generate an MSP
1 parent d301e62 commit 701b919

4 files changed

Lines changed: 202 additions & 21 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,4 @@ TestsResults*.xml
6666

6767
# Resharper settings
6868
PowerShell.sln.DotSettings.user
69+
*.msp

assets/patch-template.wxs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
3+
<Patch
4+
AllowRemoval="yes"
5+
Manufacturer="Microsoft Powershell"
6+
MoreInfoURL="https://github.com/powershell/powershell"
7+
DisplayName="Sample Patch"
8+
Description="Small Update Patch"
9+
Classification="Update"
10+
>
11+
12+
<Media Id="5000" Cabinet="RTM.cab">
13+
<PatchBaseline Id="RTM"/>
14+
</Media>
15+
16+
<PatchFamilyRef Id="SamplePatchFamily"/>
17+
</Patch>
18+
19+
<Fragment>
20+
<PatchFamily Id='SamplePatchFamily' Version='6.1.1' Supersede='yes'>
21+
<PropertyRef Id="ProductVersion"/>
22+
</PatchFamily>
23+
</Fragment>
24+
</Wix>

tools/packaging/packaging.psd1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ CompanyName="Microsoft Corporation"
55
Copyright="Copyright (c) Microsoft Corporation. All rights reserved."
66
ModuleVersion="1.0.0"
77
PowerShellVersion="5.0"
8-
CmdletsToExport=@("Start-PSPackage",'New-PSSignedBuildZip', 'New-UnifiedNugetPackage')
8+
CmdletsToExport=@()
9+
FunctionsToExport=@("Start-PSPackage",'New-PSSignedBuildZip', 'New-UnifiedNugetPackage', 'New-MSIPatch')
910
RootModule="packaging.psm1"
1011
RequiredModules = @("build")
1112
}

tools/packaging/packaging.psm1

Lines changed: 175 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2039,6 +2039,177 @@ function Get-NugetSemanticVersion
20392039
$packageSemanticVersion
20402040
}
20412041

2042+
# Get the paths to various WiX tools
2043+
function Get-WixPath
2044+
{
2045+
## AppVeyor base image might update the version for Wix. Hence, we should
2046+
## not hard code version numbers.
2047+
$wixToolsetBinPath = "${env:ProgramFiles(x86)}\WiX Toolset *\bin"
2048+
2049+
Write-Verbose "Ensure Wix Toolset is present on the machine @ $wixToolsetBinPath"
2050+
if (-not (Test-Path $wixToolsetBinPath))
2051+
{
2052+
throw "The latest version of Wix Toolset 3.11 is required to create MSI package. Please install it from https://github.com/wixtoolset/wix3/releases"
2053+
}
2054+
2055+
## Get the latest if multiple versions exist.
2056+
$wixToolsetBinPath = (Get-ChildItem $wixToolsetBinPath).FullName | Sort-Object -Descending | Select-Object -First 1
2057+
2058+
Write-Verbose "Initialize Wix executables..."
2059+
$wixHeatExePath = Join-Path $wixToolsetBinPath "heat.exe"
2060+
$wixMeltExePath = Join-Path $wixToolsetBinPath "melt.exe"
2061+
$wixTorchExePath = Join-Path $wixToolsetBinPath "torch.exe"
2062+
$wixPyroExePath = Join-Path $wixToolsetBinPath "pyro.exe"
2063+
$wixCandleExePath = Join-Path $wixToolsetBinPath "Candle.exe"
2064+
$wixLightExePath = Join-Path $wixToolsetBinPath "Light.exe"
2065+
2066+
return [PSCustomObject] @{
2067+
WixHeatExePath = $wixHeatExePath
2068+
WixMeltExePath = $wixMeltExePath
2069+
WixTorchExePath = $wixTorchExePath
2070+
WixPyroExePath = $wixPyroExePath
2071+
WixCandleExePath = $wixCandleExePath
2072+
WixLightExePath = $wixLightExePath
2073+
}
2074+
2075+
}
2076+
2077+
<#
2078+
.Synopsis
2079+
Creates a Windows installer MSP package from two MSIs and WIXPDB files
2080+
This only works on a Windows machine due to the usage of WiX.
2081+
.EXAMPLE
2082+
# This example shows how to produce a x64 patch from 6.0.2 to a theoretical 6.0.3
2083+
cd $RootPathOfPowerShellRepo
2084+
Import-Module .\build.psm1; Import-Module .\tools\packaging\packaging.psm1
2085+
New-MSIPatch -NewVersion 6.0.1 -BaselineMsiPath .\PowerShell-6.0.2-win-x64.msi -BaselineWixPdbPath .\PowerShell-6.0.2-win-x64.wixpdb -PatchMsiPath .\PowerShell-6.0.3-win-x64.msi -PatchWixPdbPath .\PowerShell-6.0.3-win-x64.wixpdb
2086+
#>
2087+
function New-MSIPatch
2088+
{
2089+
param(
2090+
[Parameter(Mandatory, HelpMessage='The version of the fixed or patch MSI.')]
2091+
[ValidatePattern("^\d+\.\d+\.\d+$")]
2092+
[string] $NewVersion,
2093+
2094+
[Parameter(Mandatory, HelpMessage='The path to the original or baseline MSI.')]
2095+
[ValidateNotNullOrEmpty()]
2096+
[ValidateScript( {(Test-Path $_) -and $_ -like '*.msi'})]
2097+
[string] $BaselineMsiPath,
2098+
2099+
[Parameter(Mandatory, HelpMessage='The path to the WIXPDB for the original or baseline MSI.')]
2100+
[ValidateNotNullOrEmpty()]
2101+
[ValidateScript( {(Test-Path $_) -and $_ -like '*.wixpdb'})]
2102+
[string] $BaselineWixPdbPath,
2103+
2104+
[Parameter(Mandatory, HelpMessage='The path to the fixed or patch MSI.')]
2105+
[ValidateNotNullOrEmpty()]
2106+
[ValidateScript( {(Test-Path $_) -and $_ -like '*.msi'})]
2107+
[string] $PatchMsiPath,
2108+
2109+
[Parameter(Mandatory, HelpMessage='The path to the WIXPDB for the fixed or patch MSI.')]
2110+
[ValidateNotNullOrEmpty()]
2111+
[ValidateScript( {(Test-Path $_) -and $_ -like '*.wixpdb'})]
2112+
[string] $PatchWixPdbPath,
2113+
2114+
[Parameter(HelpMessage='Path to the patch template WXS. Usually you do not need to specify this')]
2115+
[ValidateNotNullOrEmpty()]
2116+
[ValidateScript( {Test-Path $_})]
2117+
[string] $PatchWxsPath = "$PSScriptRoot\..\..\assets\patch-template.wxs",
2118+
2119+
[Parameter(HelpMessage='Produce a delta patch instead of a full patch. Usually not worth it.')]
2120+
[switch] $Delta
2121+
)
2122+
2123+
$mspName = (Split-Path -Path $PatchMsiPath -Leaf).Replace('.msi','.fullpath.msp')
2124+
$mspDeltaName = (Split-Path -Path $PatchMsiPath -Leaf).Replace('.msi','.deltapatch.msp')
2125+
2126+
$wixPatchXmlPath = Join-Path $env:Temp "patch.wxs"
2127+
$wixBaselineOriginalPdbPath = Join-Path $env:Temp "baseline.original.wixpdb"
2128+
$wixBaselinePdbPath = Join-Path $env:Temp "baseline.wixpdb"
2129+
$wixBaselineBinariesPath = Join-Path $env:Temp "baseline.binaries"
2130+
$wixPatchOriginalPdbPath = Join-Path $env:Temp "patch.original.wixpdb"
2131+
$wixPatchPdbPath = Join-Path $env:Temp "patch.wixpdb"
2132+
$wixPatchBinariesPath = Join-Path $env:Temp "patch.binaries"
2133+
$wixPatchMstPath = Join-Path $env:Temp "patch.wixmst"
2134+
$wixPatchObjPath = Join-Path $env:Temp "patch.wixobj"
2135+
$wixPatchWixMspPath = Join-Path $env:Temp "patch.wixmsp"
2136+
2137+
$filesToCleanup = @(
2138+
$wixPatchXmlPath
2139+
$wixBaselinePdbPath
2140+
$wixBaselineBinariesPath
2141+
$wixPatchPdbPath
2142+
$wixPatchBinariesPath
2143+
$wixPatchMstPath
2144+
$wixPatchObjPath
2145+
$wixPatchWixMspPath
2146+
$wixPatchOriginalPdbPath
2147+
$wixBaselineOriginalPdbPath
2148+
)
2149+
2150+
# cleanup from previous builds
2151+
Remove-Item -Path $filesToCleanup -Force -Recurse -ErrorAction SilentlyContinue
2152+
2153+
# Melt changes the original, so copy before running melt
2154+
Copy-Item -Path $BaselineWixPdbPath -Destination $wixBaselineOriginalPdbPath -Force
2155+
Copy-Item -Path $PatchWixPdbPath -Destination $wixPatchOriginalPdbPath -Force
2156+
2157+
[xml] $filesAssetXml = Get-Content -Raw -Path "$PSScriptRoot\..\..\assets\files.wxs"
2158+
[xml] $patchTemplateXml = Get-Content -Raw -Path $PatchWxsPath
2159+
2160+
# Update the patch version
2161+
$patchFamilyNode = $patchTemplateXml.Wix.Fragment.PatchFamily
2162+
$patchFamilyNode.SetAttribute('Version', $NewVersion)
2163+
2164+
# get all the file components from the files.wxs
2165+
$components = $filesAssetXml.GetElementsByTagName('Component')
2166+
2167+
# add all the file components to the patch
2168+
foreach($component in $components)
2169+
{
2170+
$id = $component.Id
2171+
$componentRef = $patchTemplateXml.CreateElement('ComponentRef','http://schemas.microsoft.com/wix/2006/wi')
2172+
$idAttribute = $patchTemplateXml.CreateAttribute('Id')
2173+
$idAttribute.Value = $id
2174+
$null = $componentRef.Attributes.Append($idAttribute)
2175+
$null = $patchFamilyNode.AppendChild($componentRef)
2176+
}
2177+
2178+
# save the updated patch xml
2179+
$patchTemplateXml.Save($wixPatchXmlPath)
2180+
2181+
$wixPaths = Get-WixPath
2182+
2183+
Write-Log "Processing baseline msi..."
2184+
Start-NativeExecution -VerboseOutputOnError {& $wixPaths.wixMeltExePath -nologo $BaselineMsiPath $wixBaselinePdbPath -pdb $wixBaselineOriginalPdbPath -x $wixBaselineBinariesPath}
2185+
2186+
Write-Log "Processing patch msi..."
2187+
Start-NativeExecution -VerboseOutputOnError {& $wixPaths.wixMeltExePath -nologo $PatchMsiPath $wixPatchPdbPath -pdb $wixPatchOriginalPdbPath -x $wixPatchBinariesPath}
2188+
2189+
Write-Log "generate diff..."
2190+
Start-NativeExecution -VerboseOutputOnError {& $wixPaths.wixTorchExePath -nologo -p -xi $wixBaselinePdbPath $wixPatchPdbPath -out $wixPatchMstPath}
2191+
2192+
Write-Log "Compiling patch..."
2193+
Start-NativeExecution -VerboseOutputOnError {& $wixPaths.wixCandleExePath -nologo $wixPatchXmlPath -out $wixPatchObjPath}
2194+
2195+
Write-Log "Linking patch..."
2196+
Start-NativeExecution -VerboseOutputOnError {& $wixPaths.wixLightExePath -nologo $wixPatchObjPath -out $wixPatchWixMspPath}
2197+
2198+
if($Delta.IsPresent)
2199+
{
2200+
Write-Log "Generating delta msp..."
2201+
Start-NativeExecution -VerboseOutputOnError {& $wixPaths.wixPyroExePath -nologo $wixPatchWixMspPath -out $mspDeltaName -t RTM $wixPatchMstPath }
2202+
}
2203+
else
2204+
{
2205+
Write-Log "Generating full msp..."
2206+
Start-NativeExecution -VerboseOutputOnError {& $wixPaths.wixPyroExePath -nologo $wixPatchWixMspPath -out $mspName -t RTM $wixPatchMstPath }
2207+
}
2208+
2209+
# cleanup temporary files
2210+
Remove-Item -Path $filesToCleanup -Force -Recurse -ErrorAction SilentlyContinue
2211+
}
2212+
20422213
<#
20432214
.Synopsis
20442215
Creates a Windows installer MSI package and assumes that the binaries are already built using 'Start-PSBuild'.
@@ -2106,23 +2277,7 @@ function New-MSIPackage
21062277
[Switch] $Force
21072278
)
21082279

2109-
## AppVeyor base image might update the version for Wix. Hence, we should
2110-
## not hard code version numbers.
2111-
$wixToolsetBinPath = "${env:ProgramFiles(x86)}\WiX Toolset *\bin"
2112-
2113-
Write-Verbose "Ensure Wix Toolset is present on the machine @ $wixToolsetBinPath"
2114-
if (-not (Test-Path $wixToolsetBinPath))
2115-
{
2116-
throw "The latest version of Wix Toolset 3.11 is required to create MSI package. Please install it from https://github.com/wixtoolset/wix3/releases"
2117-
}
2118-
2119-
## Get the latest if multiple versions exist.
2120-
$wixToolsetBinPath = (Get-ChildItem $wixToolsetBinPath).FullName | Sort-Object -Descending | Select-Object -First 1
2121-
2122-
Write-Verbose "Initialize Wix executables - Heat.exe, Candle.exe, Light.exe"
2123-
$wixHeatExePath = Join-Path $wixToolsetBinPath "Heat.exe"
2124-
$wixCandleExePath = Join-Path $wixToolsetBinPath "Candle.exe"
2125-
$wixLightExePath = Join-Path $wixToolsetBinPath "Light.exe"
2280+
$wixPaths = Get-WixPath
21262281

21272282
$ProductSemanticVersion = Get-PackageSemanticVersion -Version $ProductVersion
21282283
$isPreview = $ProductSemanticVersion -like '*-*'
@@ -2190,16 +2345,16 @@ function New-MSIPackage
21902345
}
21912346

21922347
Write-Log "verifying no new files have been added or removed..."
2193-
Start-NativeExecution -VerboseOutputOnError { & $wixHeatExePath dir $ProductSourcePath -dr $productDirectoryName -cg $productDirectoryName -gg -sfrag -srd -scom -sreg -out $wixFragmentPath -var env.ProductSourcePath -v}
2348+
Start-NativeExecution -VerboseOutputOnError { & $wixPaths.wixHeatExePath dir $ProductSourcePath -dr $productDirectoryName -cg $productDirectoryName -gg -sfrag -srd -scom -sreg -out $wixFragmentPath -var env.ProductSourcePath -v}
21942349
Test-FileWxs -FilesWxsPath $FilesWxsPath -HeatFilesWxsPath $wixFragmentPath
21952350

21962351
Write-Log "running candle..."
2197-
Start-NativeExecution -VerboseOutputOnError { & $wixCandleExePath "$ProductWxsPath" "$FilesWxsPath" -out (Join-Path "$env:Temp" "\\") -ext WixUIExtension -ext WixUtilExtension -arch $ProductTargetArchitecture -v}
2352+
Start-NativeExecution -VerboseOutputOnError { & $wixPaths.wixCandleExePath "$ProductWxsPath" "$FilesWxsPath" -out (Join-Path "$env:Temp" "\\") -ext WixUIExtension -ext WixUtilExtension -arch $ProductTargetArchitecture -v}
21982353

21992354
Write-Log "running light..."
22002355
# suppress ICE61, because we allow same version upgrades
22012356
# suppress ICE57, this suppresses an error caused by our shortcut not being installed per user
2202-
Start-NativeExecution -VerboseOutputOnError {& $wixLightExePath -sice:ICE61 -sice:ICE57 -out $msiLocationPath -pdbout $msiPdbLocationPath $wixObjProductPath $wixObjFragmentPath -ext WixUIExtension -ext WixUtilExtension -dWixUILicenseRtf="$LicenseFilePath"}
2357+
Start-NativeExecution -VerboseOutputOnError {& $wixPaths.wixLightExePath -sice:ICE61 -sice:ICE57 -out $msiLocationPath -pdbout $msiPdbLocationPath $wixObjProductPath $wixObjFragmentPath -ext WixUIExtension -ext WixUtilExtension -dWixUILicenseRtf="$LicenseFilePath"}
22032358

22042359
Remove-Item -ErrorAction SilentlyContinue $wixFragmentPath -Force
22052360
Remove-Item -ErrorAction SilentlyContinue $wixObjProductPath -Force

0 commit comments

Comments
 (0)