mirror of
https://github.com/MAKS-IT-COM/maksit-postclient.git
synced 2026-09-29 19:08:09 +02:00
306 lines
12 KiB
PowerShell
306 lines
12 KiB
PowerShell
#requires -Version 7.0
|
|
#requires -PSEdition Core
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Windows installer plugin for published .NET desktop apps (Community).
|
|
|
|
.DESCRIPTION
|
|
Harvests a win-* (or sole) DotNetPublish folder into a WiX MSI, then wraps
|
|
it in a Burn bootstrapper .exe. The .exe is the GitHub asset; the MSI/WXS
|
|
stay in a staging folder and are not added to the portable zip.
|
|
`wix build -arch` follows `runtimeIdentifier` (default win-x64 → x64) so
|
|
per-machine installs go to `C:\Program Files`, not Program Files (x86).
|
|
Requires the WiX CLI (`dotnet tool install -g wix`). WiX v7: accept the
|
|
OSMF EULA (`wix eula accept wix7` or `-acceptEula wix7`) and
|
|
`wix extension add -g WixToolset.BootstrapperApplications.wixext` and
|
|
`wix extension add -g WixToolset.Util.wixext`.
|
|
Reopen the shell so `%USERPROFILE%\.dotnet\tools` is on PATH.
|
|
#>
|
|
|
|
if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) {
|
|
$srcDir = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
|
$pluginSupportModulePath = Join-Path $srcDir "modules/Engine/PluginSupport.psm1"
|
|
if (Test-Path $pluginSupportModulePath -PathType Leaf) {
|
|
Import-Module $pluginSupportModulePath -Force -Global -ErrorAction Stop
|
|
}
|
|
}
|
|
|
|
function Test-WixMissingException {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[System.Management.Automation.ErrorRecord]$ErrorRecord
|
|
)
|
|
|
|
$message = [string]$ErrorRecord.Exception.Message
|
|
if ($ErrorRecord.Exception -is [System.Management.Automation.CommandNotFoundException]) {
|
|
return $true
|
|
}
|
|
|
|
return ($message -match '(?i)(not recognized|command not found|The term .+ is not recognized|marked unavailable)')
|
|
}
|
|
|
|
function Get-WixMissingInstallMessage {
|
|
return "WiX CLI ('wix') is not on PATH. Install with: dotnet tool install -g wix. For WiX v7 also run: wix eula accept wix7; wix extension add -g WixToolset.BootstrapperApplications.wixext; wix extension add -g WixToolset.Util.wixext. Reopen the terminal so %USERPROFILE%\.dotnet\tools is on PATH."
|
|
}
|
|
|
|
function Get-WixCliVersionText {
|
|
$raw = Invoke-ExternalCommand -Name wix -ArgumentList @('--version')
|
|
return ((@($raw) | ForEach-Object { [string]$_ }) -join '').Trim()
|
|
}
|
|
|
|
function Get-WixAcceptEulaArguments {
|
|
param(
|
|
[Parameter(Mandatory = $false)]
|
|
[string]$VersionText
|
|
)
|
|
|
|
if ($VersionText -match '^7\.') {
|
|
return @('-acceptEula', 'wix7')
|
|
}
|
|
|
|
return @()
|
|
}
|
|
|
|
function Get-WixBundleExtensionName {
|
|
param(
|
|
[Parameter(Mandatory = $false)]
|
|
[string]$VersionText
|
|
)
|
|
|
|
if ($VersionText -match '^[5-9]\.') {
|
|
return 'WixToolset.BootstrapperApplications.wixext'
|
|
}
|
|
|
|
return 'WixToolset.Bal.wixext'
|
|
}
|
|
|
|
function Invoke-Plugin {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
$Settings
|
|
)
|
|
|
|
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
|
|
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Add-ReleaseAssetPaths"
|
|
Import-PluginDependency -ModuleName "ExternalCommandSupport" -RequiredCommand "Invoke-ExternalCommand"
|
|
Import-PluginDependency -ModuleName "DesktopPackSupport" -RequiredCommand "New-WixPackageXml"
|
|
|
|
$pluginSettings = $Settings
|
|
$sharedSettings = $Settings.context
|
|
$scriptDir = $sharedSettings.scriptDir
|
|
$version = [string]$sharedSettings.version
|
|
$artifactsDirectory = $sharedSettings.artifactsDirectory
|
|
|
|
$appName = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'appName')
|
|
$upgradeCodeRaw = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'upgradeCode')
|
|
if ([string]::IsNullOrWhiteSpace($appName)) {
|
|
throw "WindowsInstaller plugin requires appName."
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($upgradeCodeRaw)) {
|
|
throw "WindowsInstaller plugin requires a stable upgradeCode GUID (per product, never change it)."
|
|
}
|
|
|
|
$upgradeCode = [guid]$upgradeCodeRaw
|
|
$manufacturer = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'manufacturer' -Default 'MaksIT')
|
|
$runtimeIdentifier = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'runtimeIdentifier' -Default 'win-x64')
|
|
$wixArch = Get-WixArchitectureFromRuntimeIdentifier -RuntimeIdentifier $runtimeIdentifier
|
|
$archArgs = @('-arch', $wixArch)
|
|
$installScope = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'installScope' -Default 'perMachine')
|
|
$installFolderName = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'installFolderName')
|
|
$executableName = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'executableName')
|
|
$publishDirSetting = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'publishDir')
|
|
$iconSetting = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'iconPath')
|
|
$logoSetting = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'logoPath')
|
|
$logoSideSetting = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'logoSidePath')
|
|
$themeSetting = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'themePath')
|
|
|
|
if ([string]::IsNullOrWhiteSpace([string]$artifactsDirectory)) {
|
|
throw "WindowsInstaller plugin requires an artifacts directory in the shared context."
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $artifactsDirectory -PathType Container)) {
|
|
New-Item -ItemType Directory -Path $artifactsDirectory | Out-Null
|
|
}
|
|
|
|
$publishDirectory = Resolve-DesktopPublishDirectory `
|
|
-Context $sharedSettings `
|
|
-RuntimeIdentifier $runtimeIdentifier `
|
|
-PublishDir $publishDirSetting `
|
|
-ScriptDir $scriptDir
|
|
|
|
$executablePath = Resolve-DesktopExecutablePath `
|
|
-PublishDirectory $publishDirectory `
|
|
-ExecutableName $executableName `
|
|
-Windows
|
|
|
|
$brandDir = Join-Path $PSScriptRoot 'brand'
|
|
|
|
$resolvePackPath = {
|
|
param([string]$Setting)
|
|
if ([string]::IsNullOrWhiteSpace($Setting)) {
|
|
return $null
|
|
}
|
|
|
|
if ([System.IO.Path]::IsPathRooted($Setting)) {
|
|
return $Setting
|
|
}
|
|
|
|
return [System.IO.Path]::GetFullPath((Join-Path $scriptDir $Setting))
|
|
}
|
|
|
|
$iconPath = & $resolvePackPath $iconSetting
|
|
if (-not [string]::IsNullOrWhiteSpace($iconSetting) -and -not (Test-Path -LiteralPath $iconPath -PathType Leaf)) {
|
|
throw "WindowsInstaller iconPath not found: $iconPath"
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($iconPath)) {
|
|
$fallbackIcon = Join-Path $brandDir 'mark.ico'
|
|
if (Test-Path -LiteralPath $fallbackIcon -PathType Leaf) {
|
|
$iconPath = $fallbackIcon
|
|
}
|
|
}
|
|
|
|
$logoPath = & $resolvePackPath $logoSetting
|
|
if (-not [string]::IsNullOrWhiteSpace($logoSetting) -and -not (Test-Path -LiteralPath $logoPath -PathType Leaf)) {
|
|
throw "WindowsInstaller logoPath not found: $logoPath"
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($logoPath)) {
|
|
$fallbackLogo = Join-Path $brandDir 'logo-64.png'
|
|
if (Test-Path -LiteralPath $fallbackLogo -PathType Leaf) {
|
|
$logoPath = $fallbackLogo
|
|
}
|
|
}
|
|
|
|
$logoSidePath = & $resolvePackPath $logoSideSetting
|
|
if (-not [string]::IsNullOrWhiteSpace($logoSideSetting) -and -not (Test-Path -LiteralPath $logoSidePath -PathType Leaf)) {
|
|
throw "WindowsInstaller logoSidePath not found: $logoSidePath"
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($logoSidePath)) {
|
|
$fallbackSide = Join-Path $brandDir 'sidebar.png'
|
|
if (Test-Path -LiteralPath $fallbackSide -PathType Leaf) {
|
|
$logoSidePath = $fallbackSide
|
|
}
|
|
}
|
|
|
|
$themePath = & $resolvePackPath $themeSetting
|
|
if (-not [string]::IsNullOrWhiteSpace($themeSetting) -and -not (Test-Path -LiteralPath $themePath -PathType Leaf)) {
|
|
throw "WindowsInstaller themePath not found: $themePath"
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($themePath)) {
|
|
$fallbackTheme = Join-Path $brandDir 'HyperlinkSidebarTheme.xml'
|
|
if (Test-Path -LiteralPath $fallbackTheme -PathType Leaf) {
|
|
$themePath = $fallbackTheme
|
|
}
|
|
}
|
|
|
|
$productVersion = Get-MsiProductVersion -Version $version
|
|
$safeName = ($appName -replace '[^A-Za-z0-9._-]', '-').Trim('-')
|
|
$exeNamePattern = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'exeNamePattern' -Default '{name}-{version}.exe')
|
|
$exeFileName = $exeNamePattern.Replace('{version}', $version).Replace('{name}', $safeName)
|
|
$exePath = Join-Path $artifactsDirectory $exeFileName
|
|
|
|
$stageDir = Join-Path $artifactsDirectory '.wix-stage'
|
|
if (Test-Path -LiteralPath $stageDir) {
|
|
Remove-Item -LiteralPath $stageDir -Recurse -Force
|
|
}
|
|
New-Item -ItemType Directory -Path $stageDir | Out-Null
|
|
|
|
$wxsPath = Join-Path $stageDir ($safeName + '-' + $version + '.wxs')
|
|
$msiPath = Join-Path $stageDir ($safeName + '-' + $version + '.msi')
|
|
$bundleWxsPath = Join-Path $stageDir ($safeName + '-' + $version + '-bundle.wxs')
|
|
|
|
Write-Log -Level "STEP" -Message "Generating WiX source for '$appName' from $publishDirectory ($wixArch)"
|
|
$xml = New-WixPackageXml `
|
|
-AppName $appName `
|
|
-Manufacturer $manufacturer `
|
|
-ProductVersion $productVersion `
|
|
-UpgradeCode $upgradeCode `
|
|
-PublishDirectory $publishDirectory `
|
|
-ExecutablePath $executablePath `
|
|
-InstallScope $installScope `
|
|
-InstallFolderName $installFolderName `
|
|
-Architecture $wixArch `
|
|
-IconPath $iconPath
|
|
|
|
$xml.Save($wxsPath)
|
|
Write-Log -Level "OK" -Message " WiX source: $wxsPath"
|
|
|
|
try {
|
|
$wixVersion = Get-WixCliVersionText
|
|
}
|
|
catch {
|
|
if (Test-WixMissingException -ErrorRecord $_) {
|
|
throw (Get-WixMissingInstallMessage)
|
|
}
|
|
|
|
throw
|
|
}
|
|
|
|
$eulaArgs = @(Get-WixAcceptEulaArguments -VersionText $wixVersion)
|
|
$bundleExt = Get-WixBundleExtensionName -VersionText $wixVersion
|
|
|
|
Write-Log -Level "STEP" -Message "Building MSI with WiX ($wixArch)..."
|
|
try {
|
|
Invoke-ExternalCommand -Name wix -ArgumentList (@('build') + $eulaArgs + $archArgs + @($wxsPath, '-o', $msiPath)) | Out-Null
|
|
}
|
|
catch {
|
|
if (Test-WixMissingException -ErrorRecord $_) {
|
|
throw (Get-WixMissingInstallMessage)
|
|
}
|
|
|
|
throw
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $msiPath -PathType Leaf)) {
|
|
throw "WiX completed but MSI was not produced: $msiPath"
|
|
}
|
|
|
|
$bundleXml = New-WixBundleXml `
|
|
-AppName $appName `
|
|
-Manufacturer $manufacturer `
|
|
-ProductVersion $productVersion `
|
|
-UpgradeCode $upgradeCode `
|
|
-MsiPath $msiPath `
|
|
-IconPath $iconPath `
|
|
-LogoPath $logoPath `
|
|
-LogoSidePath $logoSidePath `
|
|
-ThemePath $themePath `
|
|
-InstallScope $installScope `
|
|
-InstallFolderName $installFolderName `
|
|
-Architecture $wixArch
|
|
[System.IO.File]::WriteAllText($bundleWxsPath, $bundleXml, [System.Text.UTF8Encoding]::new($false))
|
|
|
|
if (Test-Path -LiteralPath $exePath -PathType Leaf) {
|
|
Remove-Item -LiteralPath $exePath -Force
|
|
}
|
|
|
|
Write-Log -Level "STEP" -Message "Building Windows installer exe ($wixArch)..."
|
|
try {
|
|
Invoke-ExternalCommand -Name wix -ArgumentList (@('extension', 'add', '-g', 'WixToolset.Util.wixext') + $eulaArgs) | Out-Null
|
|
Invoke-ExternalCommand -Name wix -ArgumentList (@('build') + $eulaArgs + $archArgs + @($bundleWxsPath, '-ext', $bundleExt, '-ext', 'WixToolset.Util.wixext', '-o', $exePath)) | Out-Null
|
|
}
|
|
catch {
|
|
if (Test-WixMissingException -ErrorRecord $_) {
|
|
throw (Get-WixMissingInstallMessage)
|
|
}
|
|
|
|
throw
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $exePath -PathType Leaf)) {
|
|
throw "WiX completed but installer exe was not produced: $exePath"
|
|
}
|
|
|
|
Write-Log -Level "OK" -Message " Windows installer ready: $exePath"
|
|
Set-EngineState -Context $sharedSettings -Name 'releaseDir' -Value $artifactsDirectory
|
|
Set-EngineFact -Context $sharedSettings -Namespace 'desktop' -Name 'windowsInstallerPath' -Value $exePath -Overwrite Replace
|
|
Add-ReleaseAssetPaths -Context $sharedSettings -Path @($exePath)
|
|
}
|
|
|
|
Export-ModuleMember -Function Invoke-Plugin
|