Add WiX configuration for Windows MSI packaging
Some checks failed
farmcontrol/farmcontrol-server/pipeline/head There was a failure building this commit
Some checks failed
farmcontrol/farmcontrol-server/pipeline/head There was a failure building this commit
- Introduced a new msi-wrapped.wxs file containing the WiX configuration for the Farm Control Server MSI installer. - Defined product properties, major upgrade settings, and installation conditions to ensure compatibility with Windows 7 and above. - Added custom actions and directory structure to facilitate the installation process. - Updated the build-windows-msi.ps1 script to improve the search for the WiX Toolset, ensuring proper tool availability for MSI generation.
This commit is contained in:
parent
e33643e709
commit
ce1aeee8d5
52
packaging/windows/msi-wrapped.wxs
Normal file
52
packaging/windows/msi-wrapped.wxs
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product
|
||||
Id="__PRODUCT_ID__"
|
||||
Name="Farm Control Server"
|
||||
Language="1033"
|
||||
Version="__VERSION__"
|
||||
Manufacturer="Tom Butcher"
|
||||
UpgradeCode="__UPGRADE_CODE__">
|
||||
<Package
|
||||
InstallerVersion="500"
|
||||
Compressed="yes"
|
||||
InstallScope="perMachine"
|
||||
Platform="x64" />
|
||||
|
||||
<Condition Message="Windows 7 and above is required"><![CDATA[Installed OR VersionNT >= 601]]></Condition>
|
||||
|
||||
<MajorUpgrade
|
||||
AllowSameVersionUpgrades="yes"
|
||||
DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
|
||||
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
|
||||
|
||||
<Property Id="DISABLEADVTSHORTCUTS" Value="1" />
|
||||
<Property Id="MSIINSTALLPERUSER" Value="1" />
|
||||
|
||||
<Binary Id="WrappedExe" SourceFile="__SETUP_EXE__" />
|
||||
|
||||
<CustomAction
|
||||
Id="RunInstaller"
|
||||
BinaryKey="WrappedExe"
|
||||
ExeCommand="/S"
|
||||
Execute="deferred"
|
||||
Impersonate="no"
|
||||
Return="check" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="TempFolder" Name="Temp">
|
||||
<Component Id="EmptyComponent" Guid="7145262D-7149-4F1A-9A69-F377E38F04DA">
|
||||
<CreateFolder />
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Feature Id="EmptyFeature" Level="0">
|
||||
<ComponentRef Id="EmptyComponent" />
|
||||
</Feature>
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="RunInstaller" After="ProcessComponents">NOT Installed</Custom>
|
||||
</InstallExecuteSequence>
|
||||
</Product>
|
||||
</Wix>
|
||||
@ -15,9 +15,6 @@ param(
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Windows Installer OpenDatabase modes (winuser.h / MSI API).
|
||||
$msiOpenDatabaseModeCreate = 3
|
||||
|
||||
function Get-MsiVersion {
|
||||
param([string]$InputVersion)
|
||||
|
||||
@ -29,272 +26,96 @@ function Get-MsiVersion {
|
||||
return ($parts[0..3] -join ".")
|
||||
}
|
||||
|
||||
function Invoke-InstallerExecute {
|
||||
param(
|
||||
[object]$Database,
|
||||
[string]$Sql
|
||||
function Find-WixToolset {
|
||||
$searchRoots = @(
|
||||
$env:WIX,
|
||||
$env:WIX_TOOLSET_PATH,
|
||||
"${env:ProgramFiles(x86)}\WiX Toolset v3.14\bin",
|
||||
"${env:ProgramFiles}\WiX Toolset v3.14\bin",
|
||||
"${env:ProgramFiles(x86)}\WiX Toolset v3.11\bin",
|
||||
"${env:ProgramFiles}\WiX Toolset v3.11\bin"
|
||||
)
|
||||
|
||||
$null = $Database.Execute($Sql)
|
||||
foreach ($root in $searchRoots) {
|
||||
if (-not $root) {
|
||||
continue
|
||||
}
|
||||
|
||||
$candle = Join-Path $root "candle.exe"
|
||||
$light = Join-Path $root "light.exe"
|
||||
if ((Test-Path $candle) -and (Test-Path $light)) {
|
||||
return @{
|
||||
Candle = $candle
|
||||
Light = $light
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$candleCommand = Get-Command candle.exe -ErrorAction SilentlyContinue
|
||||
$lightCommand = Get-Command light.exe -ErrorAction SilentlyContinue
|
||||
if ($candleCommand -and $lightCommand) {
|
||||
return @{
|
||||
Candle = $candleCommand.Source
|
||||
Light = $lightCommand.Source
|
||||
}
|
||||
}
|
||||
|
||||
throw @"
|
||||
WiX Toolset not found. Install WiX Toolset 3.11+ on the Windows build agent and ensure candle.exe and light.exe are on PATH.
|
||||
Example: choco install wixtoolset --version=3.14.0.4118
|
||||
"@
|
||||
}
|
||||
|
||||
function Invoke-InstallerViewInsert {
|
||||
param(
|
||||
[object]$Installer,
|
||||
[object]$Database,
|
||||
[string]$Sql,
|
||||
[object[]]$Values
|
||||
)
|
||||
function Escape-WixSourcePath {
|
||||
param([string]$Path)
|
||||
|
||||
$view = $Database.OpenView($Sql)
|
||||
$record = $Installer.CreateRecord($Values.Count)
|
||||
|
||||
for ($index = 0; $index -lt $Values.Count; $index++) {
|
||||
$value = $Values[$index]
|
||||
$field = $index + 1
|
||||
if ($null -eq $value) {
|
||||
$record.SetNull($field)
|
||||
} elseif ($value -is [int]) {
|
||||
$record.IntegerData($field) = $value
|
||||
} else {
|
||||
$record.StringData($field) = [string]$value
|
||||
}
|
||||
}
|
||||
|
||||
$view.Execute($record)
|
||||
$view.Close()
|
||||
return $Path.Replace("\", "\\")
|
||||
}
|
||||
|
||||
function Set-InstallerBinaryStream {
|
||||
param(
|
||||
[object]$Installer,
|
||||
[object]$Database,
|
||||
[string]$Name,
|
||||
[string]$FilePath
|
||||
)
|
||||
$rootDir = Split-Path -Parent $PSScriptRoot
|
||||
$templatePath = Join-Path $rootDir "packaging/windows/msi-wrapped.wxs"
|
||||
$workDir = Join-Path $env:TEMP "farmcontrol-server-wix"
|
||||
$wix = Find-WixToolset
|
||||
|
||||
try {
|
||||
Invoke-InstallerExecute -Database $Database -Sql "DELETE FROM `Binary` WHERE `Name` = '$Name'"
|
||||
} catch {
|
||||
# Ignore when the Binary table is empty on first insert.
|
||||
}
|
||||
|
||||
$view = $Database.OpenView("INSERT INTO `Binary` (`Name`, `Data`) VALUES (?, ?)")
|
||||
$record = $Installer.CreateRecord(2)
|
||||
$record.StringData(1) = $Name
|
||||
$record.SetStream(2, (Resolve-Path -LiteralPath $FilePath).Path)
|
||||
$view.Execute($record)
|
||||
$view.Close()
|
||||
}
|
||||
|
||||
function New-WrapperMsiDatabase {
|
||||
param(
|
||||
[string]$OutputPath,
|
||||
[string]$SetupExePath,
|
||||
[string]$MsiVersion,
|
||||
[string]$ProductCode,
|
||||
[string]$UpgradeCodeValue
|
||||
)
|
||||
|
||||
$outputPath = [System.IO.Path]::GetFullPath($OutputPath)
|
||||
$outputDir = Split-Path $outputPath -Parent
|
||||
if ($outputDir -and -not (Test-Path $outputDir)) {
|
||||
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
|
||||
}
|
||||
|
||||
if (Test-Path $outputPath) {
|
||||
Remove-Item -LiteralPath $outputPath -Force
|
||||
}
|
||||
|
||||
$installer = New-Object -ComObject WindowsInstaller.Installer
|
||||
$database = $installer.OpenDatabase($outputPath, $msiOpenDatabaseModeCreate)
|
||||
|
||||
Invoke-InstallerExecute -Database $database -Sql @"
|
||||
CREATE TABLE `Property` (
|
||||
`Property` CHAR(72) NOT NULL PRIMARY KEY,
|
||||
`Value` CHAR(0) NOT NULL LOCALIZABLE
|
||||
)
|
||||
"@
|
||||
|
||||
Invoke-InstallerExecute -Database $database -Sql @"
|
||||
CREATE TABLE `Directory` (
|
||||
`Directory` CHAR(72) NOT NULL PRIMARY KEY,
|
||||
`Directory_Parent` CHAR(72),
|
||||
`DefaultDir` CHAR(255) NOT NULL
|
||||
)
|
||||
"@
|
||||
|
||||
Invoke-InstallerExecute -Database $database -Sql @"
|
||||
CREATE TABLE `Component` (
|
||||
`Component` CHAR(72) NOT NULL PRIMARY KEY,
|
||||
`ComponentId` CHAR(38) NOT NULL,
|
||||
`Directory_` CHAR(72) NOT NULL,
|
||||
`Attributes` SHORT NOT NULL
|
||||
)
|
||||
"@
|
||||
|
||||
Invoke-InstallerExecute -Database $database -Sql @"
|
||||
CREATE TABLE `Feature` (
|
||||
`Feature` CHAR(72) NOT NULL PRIMARY KEY,
|
||||
`Feature_Parent` CHAR(72),
|
||||
`Title` CHAR(64) NOT NULL LOCALIZABLE,
|
||||
`Description` CHAR(255) LOCALIZABLE,
|
||||
`Display` SHORT,
|
||||
`Level` SHORT NOT NULL,
|
||||
`Directory_` CHAR(72)
|
||||
)
|
||||
"@
|
||||
|
||||
Invoke-InstallerExecute -Database $database -Sql @"
|
||||
CREATE TABLE `FeatureComponents` (
|
||||
`Feature_` CHAR(72) NOT NULL,
|
||||
`Component_` CHAR(72) NOT NULL PRIMARY KEY
|
||||
)
|
||||
"@
|
||||
|
||||
Invoke-InstallerExecute -Database $database -Sql @"
|
||||
CREATE TABLE `Binary` (
|
||||
`Name` CHAR(72) NOT NULL PRIMARY KEY,
|
||||
`Data` OBJECT NOT NULL
|
||||
)
|
||||
"@
|
||||
|
||||
Invoke-InstallerExecute -Database $database -Sql @"
|
||||
CREATE TABLE `CustomAction` (
|
||||
`Action` CHAR(72) NOT NULL PRIMARY KEY,
|
||||
`Type` SHORT NOT NULL,
|
||||
`Source` CHAR(72),
|
||||
`Target` CHAR(255)
|
||||
)
|
||||
"@
|
||||
|
||||
Invoke-InstallerExecute -Database $database -Sql @"
|
||||
CREATE TABLE `InstallExecuteSequence` (
|
||||
`Action` CHAR(72) NOT NULL,
|
||||
`Condition` CHAR(255),
|
||||
`Sequence` SHORT
|
||||
)
|
||||
"@
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" `
|
||||
-Values @("Manufacturer", "Tom Butcher")
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" `
|
||||
-Values @("ProductName", "Farm Control Server")
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" `
|
||||
-Values @("ProductVersion", $MsiVersion)
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" `
|
||||
-Values @("ProductCode", $ProductCode)
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" `
|
||||
-Values @("UpgradeCode", $UpgradeCodeValue)
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" `
|
||||
-Values @("ProductLanguage", "1033")
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" `
|
||||
-Values @("ALLUSERS", "1")
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" `
|
||||
-Values @("MSIINSTALLPERUSER", "1")
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)" `
|
||||
-Values @("DISABLEADVTSHORTCUTS", "1")
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) VALUES (?, ?, ?)" `
|
||||
-Values @("TARGETDIR", $null, "SourceDir")
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) VALUES (?, ?, ?)" `
|
||||
-Values @("TempFolder", "TARGETDIR", "Temp")
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Component` (`Component`, `ComponentId`, `Directory_`, `Attributes`) VALUES (?, ?, ?, ?)" `
|
||||
-Values @(
|
||||
"EmptyComponent",
|
||||
"7145262D-7149-4F1A-9A69-F377E38F04DA",
|
||||
"TempFolder",
|
||||
256
|
||||
)
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `Feature` (`Feature`, `Feature_Parent`, `Title`, `Description`, `Display`, `Level`, `Directory_`) VALUES (?, ?, ?, ?, ?, ?, ?)" `
|
||||
-Values @("EmptyFeature", $null, "Empty", $null, 0, 0, $null)
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES (?, ?)" `
|
||||
-Values @("EmptyFeature", "EmptyComponent")
|
||||
|
||||
Set-InstallerBinaryStream -Installer $installer -Database $database `
|
||||
-Name "WrappedExe" -FilePath $SetupExePath
|
||||
|
||||
# EXE from Binary, deferred, no impersonate, check return code.
|
||||
$customActionType = 2 + 64 + 1024 + 2048
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) VALUES (?, ?, ?, ?)" `
|
||||
-Values @("RunInstaller", $customActionType, "WrappedExe", "/S")
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES (?, ?, ?)" `
|
||||
-Values @("InstallValidate", $null, 1400)
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES (?, ?, ?)" `
|
||||
-Values @("InstallInitialize", $null, 1500)
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES (?, ?, ?)" `
|
||||
-Values @("ProcessComponents", $null, 1600)
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES (?, ?, ?)" `
|
||||
-Values @("RunInstaller", $null, 1601)
|
||||
|
||||
Invoke-InstallerViewInsert -Installer $installer -Database $database `
|
||||
-Sql "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES (?, ?, ?)" `
|
||||
-Values @("InstallFinalize", $null, 6600)
|
||||
|
||||
$summary = $database.GetSummaryInformation(20)
|
||||
$summary.Property(1) = ";\1033"
|
||||
$summary.Property(2) = "Farm Control Server"
|
||||
$summary.Property(3) = [Guid]::NewGuid().ToString().ToUpper()
|
||||
$summary.Property(4) = "Farm Control Server"
|
||||
$summary.Property(7) = "x64;1033"
|
||||
$summary.Property(9) = "Farm Control Server"
|
||||
$summary.Property(14) = "200"
|
||||
$summary.Property(15) = "2"
|
||||
$summary.Property(19) = "2"
|
||||
$summary.Persist()
|
||||
|
||||
$database.Commit()
|
||||
|
||||
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($summary) | Out-Null
|
||||
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($database) | Out-Null
|
||||
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($installer) | Out-Null
|
||||
if (Test-Path $workDir) {
|
||||
Remove-Item $workDir -Recurse -Force
|
||||
}
|
||||
New-Item -ItemType Directory -Path $workDir | Out-Null
|
||||
|
||||
$setupExePath = (Resolve-Path -LiteralPath $SetupExe).Path
|
||||
$outputMsiPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputMsi)
|
||||
$msiVersion = Get-MsiVersion $Version
|
||||
$outputDir = Split-Path $outputMsiPath -Parent
|
||||
if ($outputDir -and -not (Test-Path $outputDir)) {
|
||||
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
|
||||
}
|
||||
|
||||
New-WrapperMsiDatabase `
|
||||
-OutputPath $outputMsiPath `
|
||||
-SetupExePath $setupExePath `
|
||||
-MsiVersion $msiVersion `
|
||||
-ProductCode $ProductId `
|
||||
-UpgradeCodeValue $UpgradeCode
|
||||
$msiVersion = Get-MsiVersion $Version
|
||||
$wxsContent = Get-Content -LiteralPath $templatePath -Raw
|
||||
$wxsContent = $wxsContent.Replace("__PRODUCT_ID__", $ProductId)
|
||||
$wxsContent = $wxsContent.Replace("__UPGRADE_CODE__", $UpgradeCode)
|
||||
$wxsContent = $wxsContent.Replace("__VERSION__", $msiVersion)
|
||||
$wxsContent = $wxsContent.Replace("__SETUP_EXE__", (Escape-WixSourcePath $setupExePath))
|
||||
|
||||
$projectWxs = Join-Path $workDir "project.wxs"
|
||||
Set-Content -LiteralPath $projectWxs -Value $wxsContent -Encoding UTF8
|
||||
|
||||
$wixObj = Join-Path $workDir "project.wixobj"
|
||||
|
||||
Push-Location $workDir
|
||||
try {
|
||||
& $wix.Candle "-arch" "x64" $projectWxs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "candle.exe failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
& $wix.Light "-out" $outputMsiPath "-spdb" "-sw1076" $wixObj
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "light.exe failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
Remove-Item $workDir -Recurse -Force
|
||||
}
|
||||
|
||||
if (-not (Test-Path $outputMsiPath)) {
|
||||
throw "MSI installer was not created at $outputMsiPath"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user