powershell-wix.psm1
#Requires -Version 3.0 Function Copy-WixSourceFiles { [Cmdletbinding()] Param( [Parameter(Mandatory=$true,Position = 0)] [string] $Source, [Parameter(Mandatory=$true,Position = 1)] [string] $Destination, [Parameter(Mandatory=$false)] [string[]] $Exclude ) New-Item $Destination -ItemType directory -Force | Out-Null $objects = Get-ChildItem $Source -Force -Exclude $exclude foreach ($object in $objects) { if ($object.Attributes -contains 'Directory') { Copy-WixSourceFiles $object.Fullname (Join-path $Destination $object.Name) -Exclude $Exclude } else { Copy-Item $object.FullName $Destination } } } Function Get-WixAbsolutePath ($Path){ $Path = [System.IO.Path]::Combine( ((pwd).Path), ($Path) ); $Path = [System.IO.Path]::GetFullPath($Path); return $Path; } Function Get-WixNeutralPath ($Path){ if ($Env:OS -eq "Windows_NT") { Return $Path } # If we are not using Windows, assume we are using Wine and translate Unix paths else { return ($Path -replace "^/", "Z:\" -replace "/","\") } } Function ConvertTo-WixNeutralString ($Text) { $changes = New-Object System.Collections.Hashtable $changes.'ß' = 'ss' $changes.'Ä' = 'Ae' $changes.'ä' = 'ae' $changes.'Ü' = 'Ue' $changes.'ü' = 'ue' $changes.'Ö' = 'Oe' $changes.'ö' = 'oe' $changes.' ' = '_' $changes.'-' = '_' Foreach ($key in $changes.Keys) { $text = $text.Replace($key, $changes.$key) } $text } Function New-WixUid { function New-RandomHexByte { "{0:X2}" -f (Get-Random -Minimum 0 -Maximum 255) } New-Alias -Name nrhb -Value New-RandomHexByte ((nrhb),(nrhb),(nrhb),(nrhb), "-", (nrhb),(nrhb), "-", (nrhb),(nrhb), "-", (nrhb),(nrhb), "-", (nrhb),(nrhb),(nrhb),(nrhb),(nrhb),(nrhb)) -join '' } # .ExternalHelp powershell-wix-help.xml Function Get-WixLocalConfig { [Cmdletbinding()] Param( [Parameter(Mandatory=$false)] [string] $Path = (Get-Location).Path, [Parameter(Mandatory=$false)] [switch] $ProductShortName, [Parameter(Mandatory=$false)] [switch] $ProductName, [Parameter(Mandatory=$false)] [switch] $ProductVersion, [Parameter(Mandatory=$false)] [switch] $Manufacturer, [Parameter(Mandatory=$false)] [switch] $HelpLink, [Parameter(Mandatory=$false)] [switch] $AboutLink, [Parameter(Mandatory=$false)] [switch] $UpgradeCodeX86, [Parameter(Mandatory=$false)] [switch] $UpgradeCodeX64 ) #end Param $file = Get-WixAbsolutePath((Join-Path $Path '.wix.json')) $leaf = Split-Path $Path -Leaf $defaults = @{'ProductShortName' = $leaf; 'ProductName' = $leaf; 'ProductVersion' = '1.0.0'; 'Manufacturer' = $leaf; 'HelpLink' = "http://www.google.com/q=${leaf}"; 'AboutLink' = "http://www.google.com/q=${leaf}"; 'UpgradeCodeX86' = (New-WixUid); 'UpgradeCodeX64' = (New-WixUid)} $settings = New-Object -TypeName PSCustomObject $readSettings = New-Object -TypeName PSCustomObject $params = $PSBoundParameters.GetEnumerator()| Where-Object {($_.Key -ne 'Path')} # Make sure we have persistent upgrade codes if (Test-Path $file){ try { $readSettings = Get-Content -Raw $file | ConvertFrom-Json } catch {} } If (!$readSettings.UpgradeCodeX86 -or !$readSettings.UpgradeCodeX64){ If (!$readSettings.UpgradeCodeX86){ Add-Member -InputObject $readSettings -MemberType NoteProperty ` -Name UpgradeCodeX86 -Value (New-WixUid) } If (!$readSettings.UpgradeCode64){ Add-Member -InputObject $readSettings -MemberType NoteProperty ` -Name UpgradeCodeX64 -Value (New-WixUid) } #$readsettings $null = (New-Item -ItemType Directory -Force -Path (Split-Path $file)) $readSettings | ConvertTo-JSON | Out-File -Encoding utf8 $file } if (Test-Path $file){ try { $readSettings = Get-Content -Raw $file | ConvertFrom-Json } catch {} } foreach ($parameter in $params){ $setting = $parameter.Key.ToLower() $value = $parameter.Value if ($value){ if ($readSettings.$setting) { Add-Member -InputObject $settings -MemberType NoteProperty ` -Name $setting -Value $readSettings.$setting } elseif ($defaults.$setting) { Add-Member -InputObject $settings -MemberType NoteProperty ` -Name $setting -Value $defaults.$setting} else { Add-Member -InputObject $settings -MemberType NoteProperty ` -Name $setting -Value (Read-Host "$setting") } } } if ($params.count -eq 0){ foreach ($default in $defaults.GetEnumerator()){ $setting = $default.Name $value = $default.Value Add-Member -InputObject $settings -MemberType NoteProperty ` -Name $setting -Value $value -Force } $readSettings.PSObject.Properties | foreach-object { $setting = $_.Name $value = $_.Value Add-Member -InputObject $settings -MemberType NoteProperty ` -Name $setting -Value $value -Force } } Return $settings } #end Function Get-WixLocalConfig Export-ModuleMember -Function Get-WixLocalConfig # .ExternalHelp powershell-wix-help.xml Function Set-WixLocalConfig { [Cmdletbinding()] Param( [Parameter(Mandatory=$false)] [string] $Path = (Get-Location).Path, [Parameter(Mandatory=$false)] [switch] $Replace, [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ParameterSetName="Object")] [object] $Settings, [Parameter(Mandatory=$false, ParameterSetName="Strings")][string] $ProductShortName, [Parameter(Mandatory=$false, ParameterSetName="Strings")][string] $ProductName, [Parameter(Mandatory=$false, ParameterSetName="Strings")][string] $ProductVersion, [Parameter(Mandatory=$false, ParameterSetName="Strings")][string] $Manufacturer, [Parameter(Mandatory=$false, ParameterSetName="Strings")][string] $HelpLink, [Parameter(Mandatory=$false, ParameterSetName="Strings")][string] $AboutLink, [Parameter(Mandatory=$false, ParameterSetName="Strings")][string] $UpgradeCodeX86, [Parameter(Mandatory=$false, ParameterSetName="Strings")][string] $UpgradeCodeX64 ) #end Param $file = Get-WixAbsolutePath((Join-Path $Path '.wix.json')) If ($Settings){ $newSettings = New-Object -TypeName PSCustomObject if (!$Replace){ $readSettings = Get-WixLocalConfig -Path $Path $readSettings.PSObject.Properties | foreach-object { Add-Member -InputObject $newSettings -MemberType NoteProperty ` -Name $_.Name -Value $_.Value } } $Settings.PSObject.Properties | foreach-object { $setting = $_.Name $value = $_.Value Add-Member -InputObject $newSettings -MemberType NoteProperty ` -Name $setting -Value $value -Force } $null = (New-Item -ItemType Directory -Force -Path (Split-Path $file)) $newSettings | ConvertTo-JSON | Out-File -Encoding utf8 $file Get-WixLocalConfig -Path $Path } else { $params = $PSBoundParameters.GetEnumerator()| Where-Object {($_.Key -ne 'Path' -and $_.Key -ne 'Settings' -and $_.Key -ne 'Replace')} $Settings = New-Object -TypeName PSCustomObject foreach ($parameter in $params){ $setting = $parameter.Key $value = $parameter.Value if ($value){ Add-Member -InputObject $Settings -MemberType NoteProperty ` -Name $setting -Value $value } } Set-WixLocalConfig -Path $Path -Settings $Settings -Replace:$Replace } } #end Function Set-WixLocalConfig Export-ModuleMember -Function Set-WixLocalConfig # .ExternalHelp powershell-wix-help.xml Function Start-WixBuild { [Cmdletbinding()] Param( [Parameter(Mandatory=$false,Position=0)] [string] $Path = (Get-Location).Path, [Parameter(Mandatory=$false)] [string[]] $Exclude = @('.git','.gitignore','*.msi'), [Parameter(Mandatory=$false)] [string] $OutputFolder = (Get-Location).Path, [Parameter(Mandatory=$false)] [string] $LicenseFile = "$Path\license.rtf", [Parameter(Mandatory=$false)] [string] $IconFile = "$Path\icon.ico", [Parameter(Mandatory=$false)] [string] $BannerFile = "$Path\banner.bmp", [Parameter(Mandatory=$false)] [string] $DialogFile = "$Path\dialog.bmp", [Parameter(Mandatory=$false)] [string] $ProductShortName = (Get-WiXLocalConfig -ProductShortName -Path $Path).ProductShortName, [Parameter(Mandatory=$false)] [string] $ProductName = (Get-WiXLocalConfig -ProductName -Path $Path).ProductName, [Parameter(Mandatory=$false)] [string] $ProductVersion = (Get-WiXLocalConfig -ProductVersion -Path $Path).ProductVersion, [Parameter(Mandatory=$false)] [string] $Manufacturer = (Get-WiXLocalConfig -Manufacturer -Path $Path).Manufacturer, [Parameter(Mandatory=$false)] [string] $HelpLink = (Get-WiXLocalConfig -HelpLink -Path $Path).HelpLink, [Parameter(Mandatory=$false)] [string] $AboutLink = (Get-WiXLocalConfig -AboutLink -Path $Path).AboutLink, [Parameter(Mandatory=$false)] [string] $UpgradeCodeX86 = (Get-WiXLocalConfig -UpgradeCodeX86 -Path $Path).UpgradeCodeX86, [Parameter(Mandatory=$false)] [string] $UpgradeCodeX64 = (Get-WiXLocalConfig -UpgradeCodeX64 -Path $Path).UpgradeCodeX64, [Parameter(Mandatory=$false)] [int] $Increment = 3, [Parameter(Mandatory=$false)] [switch] $NoX86, [Parameter(Mandatory=$false)] [switch] $NoX64 ) # Increment version number if requested If ($Increment -gt 0) { $versionArray = $ProductVersion.split(".") If ($Increment -gt $versionArray.length) { $extraDigits = $Increment - $versionArray.length for ($i=0;$i -lt $extraDigits-1; $i++){ $versionArray += "0" } $versionArray += "1" } else { $versionArray[$Increment - 1] = [string]([int]($versionArray[$Increment - 1]) + 1) } $NewProductVersion = $versionArray -Join "." Set-WixLocalConfig -ProductVersion $NewProductVersion -Path $Path | Out-Null } # MSI IDs $productId = ConvertTo-WixNeutralString($ProductShortName) # Date and time $timeStamp = (Get-Date -format yyyyMMddHHmmss) # WiX paths If ($Env:OS -eq "Windows_NT") { If ((Get-ChildItem -Path 'C:\Program Files*\WiX*\' -Filter heat.exe -Recurse)){ $wixDir = Split-Path ((((Get-ChildItem -Path 'C:\Program Files (x86)\WiX*\' -Filter heat.exe -Recurse) | Select-Object FullName)[0]).FullName) $heatExe = Join-Path $wixDir "heat.exe" $candleExe = Join-Path $wixDir "candle.exe" $lightExe = Join-Path $wixDir "light.exe" } Else { Throw "Please install WiX Toolset" Return } } Else { If ((Get-ChildItem -Path ($env:PATH -split ':') -Filter heat -Recurse)){ $wixDir = Split-Path ((((Get-ChildItem -Path ($env:PATH -split ':') -Filter heat -Recurse) | Select-Object FullName)[0]).FullName) $heatExe = Join-Path $wixDir "heat" $candleExe = Join-Path $wixDir "candle" $lightExe = Join-Path $wixDir "light" } Else { Throw "Please install WiX Toolset" Return } } # Other paths $thisModuleName = ConvertTo-WixNeutralString($MyInvocation.MyCommand.ModuleName) If ($Env:OS -eq "Windows_NT") { $tmpDirGlobalRoot = Join-Path $Env:TMP $thisModuleName } Else { $tmpDirGlobalRoot = Join-Path '/tmp' $thisModuleName } $tmpDirThisRoot = Join-Path $tmpDirGlobalRoot $productId $tmpDir = Join-Path $tmpDirThisRoot $timeStamp $varName = "var." + $productId $oldMsi = Join-Path $OutputFolder ($productID + '*' + ".msi") $cabFileName = $productId + ".msi" $moduleIconFile = Join-Path $PSScriptRoot "icon.ico" $moduleBannerFile = Join-Path $PSScriptRoot "banner.bmp" $moduleDialogFile = Join-Path $PSScriptRoot "dialog.bmp" $tmpIconFile = Join-Path $tmpDir "icon.ico" $tmpBannerFile = Join-Path $tmpDir "banner.bmp" $tmpDialogFile = Join-Path $tmpDir "dialog.bmp" # MSI IDs $productId = ConvertTo-WixNeutralString($ProductShortName) # Create tmp folder if (test-path $tmpDir) { Remove-Item $tmpDir -Recurse } New-Item $tmpDir -ItemType directory | Out-Null # Copy Files to tmp dir $tmpSourceDir = Join-Path $tmpDir "files" Copy-WixSourceFiles $Path $tmpSourceDir -Exclude $Exclude # Add license if (test-path $LicenseFile) { $templateLicenseFile = Get-WixNeutralPath $LicenseFile $licenseCmd = @" <WixVariable Id="WixUILicenseRtf" Value="$templateLicenseFile"></WixVariable> "@ } # Add icon if (test-path $IconFile) { Copy-Item $IconFile $tmpIconFile } elseif (test-path $moduleIconFile){ Copy-Item $moduleIconFile $tmpIconFile } if (test-path $tmpIconFile) { $templateIconFile = Get-WixNeutralPath $tmpIconFile $iconCmd = @" <Icon Id="icon.ico" SourceFile="$templateIconFile"/> <Property Id="ARPPRODUCTICON" Value="icon.ico" /> "@ } # Add banner graphic if (test-path $BannerFile) { Copy-Item $BannerFile $tmpBannerFile } elseif (test-path $moduleBannerFile){ Copy-Item $moduleBannerFile $tmpBannerFile } if (test-path $tmpBannerFile) { $templateBannerFile = Get-WixNeutralPath $tmpBannerFile $bannerCmd = @" <WixVariable Id="WixUIBannerBmp" Value="$templateBannerFile"></WixVariable> "@ } # Add dialog graphic if (test-path $DialogFile) { Copy-Item $DialogFile $tmpDialogFile } elseif (test-path $moduleDialogFile){ Copy-Item $moduleDialogFile $tmpDialogFile } if (test-path $tmpDialogFile) { $templateDialogFile = Get-WixNeutralPath $tmpDialogFile $dialogCmd = @" <WixVariable Id="WixUIDialogBmp" Value="$templateDialogFile"></WixVariable> "@ } # Platform settings $platforms = @() $x86Settings = @{ 'arch' = 'x86'; 'sysFolder' = 'SystemFolder'; 'progfolder' = 'ProgramFilesFolder'; 'upgradeCode' = $UpgradeCodeX86; 'productName' = "${ProductName} (x86)"; 'outputMsi' = (Join-Path $OutputFolder ($productID + "_" + $ProductVersion + "_x86.msi"))} $x64Settings = @{ 'arch' = 'x64'; 'sysFolder' = 'System64Folder'; 'progfolder' = 'ProgramFiles64Folder'; 'upgradeCode' = $UpgradeCodeX64; 'productName' = "${ProductName} (x64)"; 'outputMsi' = (Join-Path $OutputFolder ($productID + "_" + $ProductVersion + "_x64.msi")) } If (!$Nox86) { $platforms += $x86Settings } If (!$Nox64) { $platforms += $x64Settings } # Remove existing MSIs # Remove-Item $oldMsi # Do the build foreach ($platform in $platforms) { $platformArch = $platform.arch $platformUpgradeCode = $platform.upgradeCode $platformSysFolder = $platform.sysFolder $platformProgFolder = $platform.progFolder $platformProductName = $platform.productName $modulesWxs = Join-Path $tmpDir "_modules${platformArch}.wxs" $productWxs = Join-Path $tmpDir ".wxs${platformArch}" $productWxs2 = Join-Path $tmpDir ".wxs${platformArch}_2" $modulesWixobj = Join-Path $tmpDir "_modules${platformArch}.wixobj" $productWixobj = Join-Path $tmpDir ".wixobj${platformArch}" $productWixobj2 = Join-Path $tmpDir ".wixobj${platformArch}_2" $productPdb = Join-Path $tmpDir ($productID + ".wizpdb${platformArch}") # Build XML $wixXml = [xml] @" <?xml version="1.0" encoding="utf-8"?> <Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'> <Product Id="*" Language="1033" Name="$platformProductName" Version="$ProductVersion" Manufacturer="$Manufacturer" UpgradeCode="$platformUpgradeCode" > <Package Id="*" Description="$platformProductName Installer" InstallPrivileges="elevated" Comments="$ProductShortName Installer" InstallerVersion="200" Compressed="yes" Platform="$platformArch"> </Package> $iconCmd <Upgrade Id="$platformUpgradeCode"> <!-- Detect any newer version of this product --> <UpgradeVersion Minimum="$ProductVersion" IncludeMinimum="no" OnlyDetect="yes" Language="1033" Property="NEWPRODUCTFOUND" /> <!-- Detect and remove any older version of this product --> <UpgradeVersion Maximum="$ProductVersion" IncludeMaximum="yes" OnlyDetect="no" Language="1033" Property="OLDPRODUCTFOUND" /> </Upgrade> <!-- Define a custom action --> <CustomAction Id="PreventDowngrading" Error="Newer version already installed." /> <InstallExecuteSequence> <!-- Prevent downgrading --> <Custom Action="PreventDowngrading" After="FindRelatedProducts"> NEWPRODUCTFOUND </Custom> <RemoveExistingProducts After="InstallFinalize" /> </InstallExecuteSequence> <InstallUISequence> <!-- Prevent downgrading --> <Custom Action="PreventDowngrading" After="FindRelatedProducts"> NEWPRODUCTFOUND </Custom> </InstallUISequence> <Media Id="1" Cabinet="$cabFileName" EmbedCab="yes"></Media> $licenseCmd $bannerCmd $dialogCmd <Directory Id="TARGETDIR" Name="SourceDir"> <Directory Id="$platformProgFolder" Name="$platformProgFolder"> <Directory Id="WindowsPowerShell" Name="WindowsPowerShell"> <Directory Id="INSTALLDIR" Name="Modules"> <Directory Id="$ProductId" Name="$ProductShortName"> <Directory Id="VERSIONDIR" Name="$ProductVersion"> </Directory> </Directory> </Directory> </Directory> <Directory Id="PowerShell" Name="PowerShell"> <Directory Id="INSTALLDIRCORE" Name="Modules"> <Directory Id="${ProductId}_core" Name="$ProductShortName"> <Directory Id="VERSIONDIRCORE" Name="$ProductVersion"> </Directory> </Directory> </Directory> </Directory> </Directory> </Directory> <Property Id="ARPHELPLINK" Value="$HelpLink"></Property> <Property Id="ARPURLINFOABOUT" Value="$AboutLink"></Property> <Feature Id="$ProductId" Title="$ProductShortName" Level="1"> <Feature Id="${ProductId}_windows" Title="${ProductShortName}_windows" Level="1" ConfigurableDirectory="INSTALLDIR"> <ComponentGroupRef Id="VERSIONDIR"> </ComponentGroupRef> </Feature> <Feature Id="${ProductId}_core" Title="${ProductShortName}_core" Level="1" ConfigurableDirectory="INSTALLDIRCORE"> <ComponentGroupRef Id="VERSIONDIRCORE"> </ComponentGroupRef> </Feature> </Feature> <UI></UI> <UIRef Id="WixUI_InstallDir"></UIRef> <Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR"></Property> </Product> </Wix> "@ # Save XML and create productWxs $wixXml.Save($modulesWxs) &$heatExe dir $tmpSourceDir -nologo -sfrag -sw5151 -ag -srd -gg -dir $productId -out $productWxs -cg VERSIONDIR -dr VERSIONDIR | Out-Null &$heatExe dir $tmpSourceDir -nologo -sfrag -sw5151 -ag -srd -gg -dir ${productId}_core -out $productWxs2 -cg VERSIONDIRCORE -dr VERSIONDIRCORE | Out-Null # Produce wixobj files &$candleexe $modulesWxs -out $modulesWixobj | Out-Null &$candleexe $productWxs -out $productWixobj | Out-Null &$candleexe $productWxs2 -out $productWixobj2 | Out-Null } foreach ($platform in $platforms) { $platformArch = $platform.arch $modulesWixobj = Join-Path $tmpDir "_modules${platformArch}.wixobj" $productWixobj = Join-Path $tmpDir ".wixobj${platformArch}" $productWixobj2 = Join-Path $tmpDir ".wixobj${platformArch}_2" $platformOutputMsi = $platform.outputMsi # Produce the MSI file &$lightexe -sval -sw1076 -spdb -ext WixUIExtension -out $platformOutputMsi $modulesWixobj $productWixobj $productWixobj2 -b $tmpSourceDir -sice:ICE91 -sice:ICE69 -sice:ICE38 -sice:ICE57 -sice:ICE64 -sice:ICE204 -sice:ICE80 | Out-Null } # Remove tmp dir Remove-Item $tmpDir -Recurse -Force } #end Start-WixBuild Export-ModuleMember -Function Start-WixBuild |