Skip to content

Instantly share code, notes, and snippets.

@jimmylin212
Last active September 14, 2022 15:19
Show Gist options
  • Save jimmylin212/83c71fffd4820b69db0e3c3959ecd3ae to your computer and use it in GitHub Desktop.
Save jimmylin212/83c71fffd4820b69db0e3c3959ecd3ae to your computer and use it in GitHub Desktop.
This powershell script that could generate the .wxs file based on the source directories and build the .msi file using Wix (Windows Install by XML). To use this script, please modify the definition of variables in each function.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
xmlns="http://schemas.microsoft.com/wix/2006/wi"
exclude-result-prefixes="wix">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="wix:Wix">
<xsl:copy>
<!-- The following enters the directive for adding the config.wxi include file to the dynamically generated file -->
<!--xsl:processing-instruction name="include">$(sys.CURRENTDIR)wix\config.wxi</xsl:processing-instruction-->
<xsl:apply-templates select="@*" />
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<!-- ### Adding the Win64-attribute to all Components -->
<xsl:template match="wix:Component">
<xsl:copy>
<xsl:apply-templates select="@*" />
<!-- Adding the Win64-attribute as we have a x64 application -->
<xsl:attribute name="Win64">yes</xsl:attribute>
<!-- Now take the rest of the inner tag -->
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
$gWorkingDir = Convert-Path .
$gSourceDirName = "dist"
$gSourceDirPath = Join-Path -Path $gWorkingDir -ChildPath $gSourceDirName
$gXmlFileName = 'wix_template_auto'
$gWxsFilePath = Join-Path -Path $gWorkingDir -ChildPath "$gXmlFileName.wxs"
$gWixObjFilePath = Join-Path -Path $gWorkingDir -ChildPath "$gXmlFileName.wixobj"
$gProductCode = [guid]::NewGuid().GUID.ToUpper()
$gUpgradeCode = [guid]::NewGuid().GUID.ToUpper()
$gProductVersion = "1.0.0.0"
$gNamespace = "http://schemas.microsoft.com/wix/2006/wi"
$gCompanyName = "COMPANY_NAME"
$gProductName = "PRODUCT_NAME"
$gFinalProductName = "PRODUCT_NAME"
$gExeFileName = "EXE_FILE_NAME"
$gWixDirPath = "C:\Program Files (x86)\WiX Toolset v3.11\bin"
Function AddGlobalSettingNode($xmlFilePath) {
$productAttributes = @{Id= "*"; UpgradeCode= $gUpgradeCode; Name= $gProductName; Language="1033"; Version= $gProductVersion; Manufacturer= $gCompanyName; Codepage= "1252";}
$mediaAttributes = @{Id= "1"; Cabinet= "CABINET.cab"; EmbedCab="yes"}
$packageAttributes = @{Description= "Description shows here"; Comments= "Comments shows here"; InstallerVersion= "200"; Platform= "x64"; Compressed= "yes"}
$globalSettings = @{nodeType = "Package"; nodeContent= $packageAttributes},
@{nodeType = "Media"; nodeContent= $mediaAttributes}
$xmlDoc = [System.Xml.XmlDocument](Get-Content $xmlFilePath)
## Add product Node
$productNode = $xmlDoc.CreateElement("Product", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix", $nsmgr).AppendChild($productNode)
foreach($key in $productAttributes.keys) {
$productNode.SetAttribute($key, $productAttributes[$key])
}
## Add other global setting Nodes
foreach($globalSetting in $globalsettings) {
$nodeType = $globalSetting["nodeType"]
$nodeContent = $globalSetting["nodeContent"]
$globalSettingNode = $xmlDoc.CreateElement($nodeType, $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product", $nsmgr).AppendChild($globalSettingNode)
foreach($key in $nodeContent.keys) {
$globalSettingNode.SetAttribute($key, $nodeContent[$key])
}
}
$xmlDoc.Save($xmlFilePath)
}
Function AddDirectoryNode($xmlFilePath) {
$directoryIndex = 0
$directories = @(
@(@{Id= "ProgramFiles64Folder"; Name="PFiles"}, @{Id= "ProgramFilesFolderHp"; Name=$gCompanyName}, @{Id= "INSTALLDIR"; Name=$gProductName}),
@(@{Id= "ProgramMenuFolder"}, @{Id= "ProgramMenuDirHP"; Name=$gCompanyName},@{Id= "ProgramMenuDirHpProduct"; Name=$gProductName})
)
$xmlDoc = [System.Xml.XmlDocument](Get-Content $xmlFilePath);
$directoryNode = $xmlDoc.CreateElement("Directory", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product", $nsmgr).AppendChild($directoryNode)
$directoryNode.SetAttribute("Id", "TARGETDIR")
$directoryNode.SetAttribute("Name", "SourceDir")
foreach($directory in $directories) {
foreach($subDirectory in $directory) {
$subDirectoryDst = "//wixns:Wix/wixns:Product"
$subDirectoryLevel = $directory.IndexOf($subDirectory)
for ($i = 0; $i -lt $subDirectoryLevel + 1; $i++) {
if ($i -eq 1) {
$subDirectoryDst += "/wixns:Directory[$($directoryIndex + 1)]"
}
else {
$subDirectoryDst += "/wixns:Directory"
}
}
$subDirectoryNode = $xmlDoc.CreateElement("Directory", $gNamespace)
$xmlDoc.SelectSingleNode($subDirectoryDst, $nsmgr).AppendChild($subDirectoryNode)
foreach($key in $subDirectory.keys) {
$subDirectoryNode.SetAttribute($key, $subDirectory[$key])
}
}
$directoryIndex++
}
$xmlDoc.Save($xmlFilePath)
}
Function AddDirectoryRefNode($xmlFilePath) {
$features = New-Object System.Collections.ArrayList
$directoryRefIndex = 0
$directoryRefDatas =
@{
Id= "INSTALLDIR";
components= @{nodeType= "RemoveFolder"; Id= "componentINSTALLDIR"; Directory= "INSTALLDIR"; On= "uninstall"}},
@{
Id= "ProgramMenuDirHpProduct";
components= @{nodeType="Shortcut"; Id= "SHORTCUT"; Name= $gProductName; Target= "[INSTALLDIR]$gExeFileName"; WorkingDirectory= "INSTALLDIR"},
# @{nodeType="Shortcut"; Id= "UninstallProductShortCut"; Name= "Uninstall $gProductName"; Target= "[SystemFolder]msiexec.exe"; Arguments="/x [ProductCode]";},
@{nodeType="RemoveFolder"; Id= "removeProgramMenuDirHP"; Directory= "ProgramMenuDirHP"; On= "uninstall"},
@{nodeType="RemoveFolder"; Id= "removeProgramMenuDirHpProduct"; Directory= "ProgramMenuDirHpProduct"; On= "uninstall"},
@{nodeType="RegistryValue"; Root= "HKCU"; Name= "installed"; Key= "Software\$gCompanyName\$gProductName"; Type= "integer"; Value= "1"; KeyPath= "yes"}}
$xmlDoc = [System.Xml.XmlDocument](Get-Content $xmlFilePath)
foreach($directoryRefData in $directoryRefDatas) {
$directoryRefId = $directoryRefData["Id"]
$directoryRefNode = $xmlDoc.CreateElement("DirectoryRef", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product", $nsmgr).AppendChild($directoryRefNode)
$directoryRefNode.SetAttribute("Id", $directoryRefId)
$componentId = "component$directoryRefId"
$componentNode = $xmlDoc.CreateElement("Component", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product/wixns:DirectoryRef[$($directoryRefIndex + 1)]", $nsmgr).AppendChild($componentNode)
$componentNode.SetAttribute("Id", $componentId)
$componentNode.SetAttribute("Guid", [guid]::NewGuid().GUID.ToUpper())
$componentNode.SetAttribute("Win64", "yes")
$directoryRefData.Item("components")
foreach($component in $directoryRefData["components"]) {
$subNodeType = $component["nodeType"]
$subNode = $xmlDoc.CreateElement($subNodeType, $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product/wixns:DirectoryRef[$($directoryRefIndex + 1)]/wixns:Component", $nsmgr).AppendChild($subNode)
foreach($key in $component.keys) {
if ($key -ne "nodeType") {
$subNode.SetAttribute($key, $component[$key])
}
}
}
$features.Add($componentId)
$directoryRefIndex++
}
$xmlDoc.Save($xmlFilePath)
return $features
}
Function AddFeatureNode($features, $xmlFilePath) {
$featureAttributes = @{Id= "FEATURE"; Description= "Description shows here"; Display= "expand"; Level="1"; ConfigurableDirectory="INSTALLDIR"; InstallDefault="local"; AllowAdvertise= "no"}
$xmlDoc = [System.Xml.XmlDocument](Get-Content $xmlFilePath)
$featureNode = $xmlDoc.CreateElement("Feature", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product", $nsmgr).AppendChild($featureNode)
foreach($key in $featureAttributes.keys) {
$featureNode.SetAttribute($key, $featureAttributes[$key])
}
foreach($feature in $features) {
if ($feature -is [String]) {
$componentRefNode = $xmlDoc.CreateElement("ComponentRef", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product/wixns:Feature", $nsmgr).AppendChild($componentRefNode)
$componentRefNode.SetAttribute("Id", $feature)
}
}
$componentGroupRefNode = $xmlDoc.CreateElement("ComponentGroupRef", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product/wixns:Feature", $nsmgr).AppendChild($componentGroupRefNode)
$componentGroupRefNode.SetAttribute("Id", "HarvestedComponentId")
$xmlDoc.Save($xmlFilePath)
}
Function AddUIRelatedNodes($xmlFilePath) {
$publishIndex = 1
$uiPublishDatas = @{Dialog = "WelcomeDlg"; Control = "Next"; Event = "NewDialog"; Value = "InstallDirDlg"},
@{Dialog = "InstallDirDlg"; Control = "Back"; Event = "NewDialog"; Value = "WelcomeDlg"; Order = 2}
$xmlDoc = [System.Xml.XmlDocument](Get-Content $xmlFilePath)
$uiPropertyNode = $xmlDoc.CreateElement("Property", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product", $nsmgr).AppendChild($uiPropertyNode)
$uiPropertyNode.SetAttribute("Id", "WIXUI_INSTALLDIR")
$uiPropertyNode.SetAttribute("Value", "INSTALLDIR")
$uiRefNode = $xmlDoc.CreateElement("UIRef", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product", $nsmgr).AppendChild($uiRefNode)
$uiRefNode.SetAttribute("Id", "WixUI_InstallDir")
$uiNode = $xmlDoc.CreateElement("UI", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product", $nsmgr).AppendChild($uiNode)
foreach($uiPublishData in $uiPublishDatas) {
$uiPublishNode = $xmlDoc.CreateElement("Publish", $gNamespace)
$uiPublishElement = $xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product/wixns:UI", $nsmgr).AppendChild($uiPublishNode)
$uiPublishElement.AppendChild($xmlDoc.CreateTextNode($publishIndex))
foreach($key in $uiPublishData.keys) {
$uiPublishNode.SetAttribute($key, $uiPublishData[$key])
}
$publishIndex++
}
$xmlDoc.Save($xmlFilePath)
}
Function AddUpgradeNodes($xmlFilePath) {
$upgradeVersions = @{OnlyDetect= "yes"; Minimum= $gMsiVersion; Property= "NEWERVERSIONDETECTED"; IncludeMinimum= "no"},
@{OnlyDetect= "no"; Maximum= $gMsiVersion; Property= "OLDERVERSIONBEINGUPGRADED"; IncludeMaximum= "no"},
@{OnlyDetect= "yes"; Minimum= $gMsiVersion; Maximum= $gMsiVersion; IncludeMinimum= "yes"; IncludeMaximum="yes"; Property="ANOTHERBUILDINSTALLED"}
$xmlDoc = [System.Xml.XmlDocument](Get-Content $xmlFilePath)
$upgradeNode = $xmlDoc.CreateElement("Upgrade", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product", $nsmgr).AppendChild($upgradeNode)
$upgradeNode.SetAttribute("Id", $gUpgradeCode)
foreach($upgradeVersion in $upgradeVersions) {
$upgradeVersionNode = $xmlDoc.CreateElement("UpgradeVersion", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product/wixns:Upgrade", $nsmgr).AppendChild($upgradeVersionNode)
foreach($key in $upgradeVersion.keys) {
$upgradeVersionNode.SetAttribute($key, $upgradeVersion[$key])
}
}
$installExecuteSequenceNode = $xmlDoc.CreateElement("InstallExecuteSequence", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product", $nsmgr).AppendChild($installExecuteSequenceNode)
$removeExistingProductsNode = $xmlDoc.CreateElement("RemoveExistingProducts", $gNamespace)
$xmlDoc.SelectSingleNode("//wixns:Wix/wixns:Product/wixns:InstallExecuteSequence", $nsmgr).AppendChild($removeExistingProductsNode)
$removeExistingProductsNode.SetAttribute("After", "InstallInitialize")
$xmlDoc.Save($xmlFilePath)
}
Function CreateWixSourceXML() {
# add the top layer of Wix
$xmlFilePath = $gWxsFilePath
$xmlWriter = New-Object System.XMl.XmlTextWriter($xmlFilePath, $Null)
$xmlWriter.Formatting = 'Indented'
$xmlWriter.Indentation = 1
$XmlWriter.IndentChar = "`t"
$xmlWriter.WriteStartDocument()
$xmlWriter.WriteStartElement("Wix", $gNamespace)
$xmlWriter.WriteEndElement()
$xmlWriter.WriteEndDocument()
$xmlWriter.Finalize
$xmlWriter.Flush()
$xmlWriter.Close()
$xmlDoc = [System.Xml.XmlDocument](Get-Content $xmlFilePath);
$nsmgr = new-object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$nsmgr.AddNamespace("wixns", $gNamespace)
AddGlobalSettingNode $xmlFilePath
AddDirectoryNode $xmlFilePath
$features = AddDirectoryRefNode $xmlFilePath
AddFeatureNode $features $xmlFilePath
AddUIRelatedNodes $xmlFilePath
AddUpgradeNodes $xmlFilePath
}
Function BuildWithWix () {
$harvestFileName = "harvested_result"
$harvestWsxFilePath = Join-Path -Path $gWorkingDir -ChildPath "$harvestFileName.wxs"
$harvestWixObjFilePath = Join-Path -Path $gWorkingDir -ChildPath "$harvestFileName.wixobj"
$harvestTemplateFilePath = Join-Path -Path $gWorkingDir -ChildPath "HeatTransform.xslt"
$heatExePath = Join-Path $gWixDirPath -ChildPath "heat.exe"
$candleExePath = Join-Path $gWixDirPath -ChildPath "candle.exe"
$lightExePath = Join-Path $gWixDirPath -ChildPath "light.exe"
$msiFilePath = Join-Path -Path $gWorkingDir -ChildPath "$gFinalProductName.msi".Replace(" ", "_")
Write-Host "`"$heatExePath`" dir `"$gSourceDirPath`" -o `"$harvestWsxFilePath`" -t `"$harvestTemplateFilePath`" -cg HarvestedComponentId -dr INSTALLDIR -var var.Dist -scom -frag -srd -sreg -gg"
Start-Process -Wait -NoNewWindow -FilePath "$heatExePath" -ArgumentList "dir `"$gSourceDirPath`"","-o `"$harvestWsxFilePath`"","-t `"$harvestTemplateFilePath`"","-cg HarvestedComponentId","-dr INSTALLDIR","-var var.Dist","-scom","-frag","-srd","-sreg","-gg"
Write-Host "`"$candleExePath`" `"$harvestWsxFilePath`" -dDist=`"$gSourceDirPath`""
Start-Process -Wait -NoNewWindow -FilePath "$candleExePath" -ArgumentList "`"$harvestWsxFilePath`"","-dDist=`"$gSourceDirPath`""
Write-Host "`"$candleExePath`" `"$gWxsFilePath`""
Start-Process -Wait -NoNewWindow -FilePath "$candleExePath" -ArgumentList "`"$gWxsFilePath`""
Write-Host "`"$lightExePath`" `"$gWixObjFilePath`" `"$harvestWixObjFilePath`" -o `"$msiFilePath`" -ext WixUIExtension"
Start-Process -Wait -NoNewWindow -FilePath "$lightExePath" -ArgumentList "`"$gWixObjFilePath`"", "`"$harvestWixObjFilePath`"", "-o `"$msiFilePath`"", "-ext WixUIExtension"
}
CreateWixSourceXML
BuildWithWix
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment