summaryrefslogtreecommitdiff
path: root/Tools/msi/bundle
diff options
context:
space:
mode:
authorZachary Ware <zachary.ware@gmail.com>2015-04-13 16:44:05 -0500
committerZachary Ware <zachary.ware@gmail.com>2015-04-13 16:44:05 -0500
commit8cc6d42d475c53396afaf972f03bee07ee4739bd (patch)
treeff70de39fc13210d28c609aa90ff93d35a9db359 /Tools/msi/bundle
parent187d9878e860b66d78bfd4c8d538130c72cc724a (diff)
parent81833b5e59153c758f894c851ad1ec4e7942c4b5 (diff)
downloadcpython-8cc6d42d475c53396afaf972f03bee07ee4739bd.tar.gz
Closes #23730: merge with 3.4
Diffstat (limited to 'Tools/msi/bundle')
-rw-r--r--Tools/msi/bundle/Default.thm123
-rw-r--r--Tools/msi/bundle/Default.wxl108
-rw-r--r--Tools/msi/bundle/SideBar.pngbin0 -> 19170 bytes
-rw-r--r--Tools/msi/bundle/bootstrap/LICENSE.txt25
-rw-r--r--Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp2746
-rw-r--r--Tools/msi/bundle/bootstrap/pch.cpp1
-rw-r--r--Tools/msi/bundle/bootstrap/pch.h59
-rw-r--r--Tools/msi/bundle/bootstrap/pythonba.cpp76
-rw-r--r--Tools/msi/bundle/bootstrap/pythonba.def18
-rw-r--r--Tools/msi/bundle/bootstrap/pythonba.sln22
-rw-r--r--Tools/msi/bundle/bootstrap/pythonba.vcxproj69
-rw-r--r--Tools/msi/bundle/bootstrap/resource.h25
-rw-r--r--Tools/msi/bundle/bundle.icobin0 -> 19790 bytes
-rw-r--r--Tools/msi/bundle/bundle.targets102
-rw-r--r--Tools/msi/bundle/bundle.wxl5
-rw-r--r--Tools/msi/bundle/bundle.wxs80
-rw-r--r--Tools/msi/bundle/full.wixproj21
-rw-r--r--Tools/msi/bundle/packagegroups/core.wxs56
-rw-r--r--Tools/msi/bundle/packagegroups/crt.wxs68
-rw-r--r--Tools/msi/bundle/packagegroups/dev.wxs40
-rw-r--r--Tools/msi/bundle/packagegroups/doc.wxs24
-rw-r--r--Tools/msi/bundle/packagegroups/exe.wxs56
-rw-r--r--Tools/msi/bundle/packagegroups/launcher.wxs23
-rw-r--r--Tools/msi/bundle/packagegroups/lib.wxs56
-rw-r--r--Tools/msi/bundle/packagegroups/postinstall.wxs62
-rw-r--r--Tools/msi/bundle/packagegroups/tcltk.wxs62
-rw-r--r--Tools/msi/bundle/packagegroups/test.wxs56
-rw-r--r--Tools/msi/bundle/packagegroups/tools.wxs24
-rw-r--r--Tools/msi/bundle/releaselocal.wixproj21
-rw-r--r--Tools/msi/bundle/releaseweb.wixproj21
-rw-r--r--Tools/msi/bundle/snapshot.wixproj21
31 files changed, 4070 insertions, 0 deletions
diff --git a/Tools/msi/bundle/Default.thm b/Tools/msi/bundle/Default.thm
new file mode 100644
index 0000000000..d165e2b515
--- /dev/null
+++ b/Tools/msi/bundle/Default.thm
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Theme xmlns="http://wixtoolset.org/schemas/thmutil/2010">
+ <Window Width="640" Height="382" HexStyle="100a0000" FontId="0">#(loc.Caption)</Window>
+ <Font Id="0" Height="-12" Weight="500" Foreground="000000" Background="ffffff">Segoe UI</Font>
+ <Font Id="1" Height="-24" Weight="500" Foreground="000000" Background="ffffff">Segoe UI</Font>
+ <Font Id="2" Height="-22" Weight="500" Foreground="666666" Background="ffffff">Segoe UI</Font>
+ <Font Id="3" Height="-12" Weight="500" Foreground="000000" Background="ffffff">Segoe UI</Font>
+ <Font Id="4" Height="-12" Weight="500" Foreground="ff0000" Background="ffffff" Underline="yes">Segoe UI</Font>
+ <Font Id="5" Height="-12" Weight="500" Foreground="666666" Background="ffffff">Segoe UI</Font>
+
+ <Page Name="Help">
+ <Text X="185" Y="11" Width="-11" Height="32" FontId="1" DisablePrefix="yes">#(loc.HelpHeader)</Text>
+ <Image X="0" Y="0" Width="162" Height="352" ImageFile="SideBar.png"/>
+
+ <Text X="185" Y="50" Width="-11" Height="-35" FontId="3" DisablePrefix="yes">#(loc.HelpText)</Text>
+ <Button Name="SuccessCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.CloseButton)</Button>
+ </Page>
+ <Page Name="Install">
+ <Text X="185" Y="11" Width="-11" Height="32" FontId="1" DisablePrefix="yes">#(loc.InstallHeader)</Text>
+ <Image X="0" Y="0" Width="162" Height="352" ImageFile="SideBar.png"/>
+
+ <Text X="185" Y="50" Width="-11" Height="50" FontId="3" TabStop="yes">#(loc.InstallMessage)</Text>
+
+ <Button Name="InstallAllUsersButton" X="175" Y="101" Width="-11" Height="59" TabStop="yes" FontId="3" HexStyle="0xF">#(loc.InstallAllUsersButton)</Button>
+ <Button Name="InstallJustForMeButton" X="175" Y="171" Width="-11" Height="59" TabStop="yes" FontId="3" HexStyle="0xE">#(loc.InstallJustForMeButton)</Button>
+ <Button Name="InstallCustomButton" X="175" Y="241" Width="-11" Height="59" TabStop="yes" FontId="3" HexStyle="0xE">#(loc.InstallCustomButton)</Button>
+
+ <Checkbox Name="PrependPath" X="185" Y="-13" Width="-100" Height="20" TabStop="yes" FontId="3">#(loc.ShortPrependPathLabel)</Checkbox>
+
+ <Button Name="InstallCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.CancelButton)</Button>
+ </Page>
+ <Page Name="SimpleInstall">
+ <Text X="185" Y="11" Width="-11" Height="32" FontId="1" DisablePrefix="yes">#(loc.InstallHeader)</Text>
+ <Image X="0" Y="0" Width="162" Height="352" ImageFile="SideBar.png"/>
+
+ <Button Name="InstallSimpleButton" X="175" Y="101" Width="-11" Height="59" TabStop="yes" FontId="3" HideWhenDisabled="yes" HexStyle="0xF">#(loc.InstallSimpleButton)</Button>
+
+ <Button Name="InstallCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.CancelButton)</Button>
+ </Page>
+ <Page Name="Custom1">
+ <Text X="185" Y="11" Width="-11" Height="32" FontId="1" DisablePrefix="yes">#(loc.Custom1Header)</Text>
+ <Image X="0" Y="0" Width="162" Height="352" ImageFile="SideBar.png"/>
+
+ <Checkbox Name="Include_doc" X="185" Y="51" Width="-11" Height="20" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.Include_docLabel)</Checkbox>
+ <Text X="205" Y="71" Width="-11" Height="20" TabStop="no" FontId="5">#(loc.Include_docHelpLabel)</Text>
+
+ <Checkbox Name="Include_pip" X="185" Y="96" Width="-11" Height="20" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.Include_pipLabel)</Checkbox>
+ <Text X="205" Y="116" Width="-11" Height="20" TabStop="no" FontId="5">#(loc.Include_pipHelpLabel)</Text>
+
+ <Checkbox Name="Include_tcltk" X="185" Y="141" Width="-11" Height="20" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.Include_tcltkLabel)</Checkbox>
+ <Text X="205" Y="161" Width="-11" Height="20" TabStop="no" FontId="5">#(loc.Include_tcltkHelpLabel)</Text>
+
+ <Checkbox Name="Include_test" X="185" Y="186" Width="-11" Height="20" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.Include_testLabel)</Checkbox>
+ <Text X="205" Y="206" Width="-11" Height="20" TabStop="no" FontId="5">#(loc.Include_testHelpLabel)</Text>
+
+ <Checkbox Name="Include_launcher" X="185" Y="231" Width="-11" Height="20" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.Include_launcherLabel)</Checkbox>
+ <Text X="205" Y="251" Width="-11" Height="20" TabStop="no" FontId="5">#(loc.Include_launcherHelpLabel)</Text>
+
+ <Button Name="Custom1BackButton" X="185" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.CustomBackButton)</Button>
+ <Button Name="CustomNextButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.CustomNextButton)</Button>
+ <Button Name="Custom1CancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.CancelButton)</Button>
+ </Page>
+ <Page Name="Custom2">
+ <Text X="185" Y="11" Width="-11" Height="32" FontId="1" DisablePrefix="yes">#(loc.Custom2Header)</Text>
+ <Image X="0" Y="0" Width="162" Height="352" ImageFile="SideBar.png"/>
+
+ <Checkbox Name="AssociateFiles" X="185" Y="51" Width="-11" Height="20" TabStop="yes" FontId="3" HideWhenDisabled="no">#(loc.AssociateFilesLabel)</Checkbox>
+ <Checkbox Name="PrependPath" X="185" Y="76" Width="-11" Height="20" TabStop="yes" FontId="3" HideWhenDisabled="no">#(loc.PrependPathLabel)</Checkbox>
+ <Checkbox Name="InstallAllUsers" X="185" Y="101" Width="-11" Height="20" TabStop="yes" FontId="3" HideWhenDisabled="no">#(loc.InstallAllUsersLabel)</Checkbox>
+ <Checkbox Name="CompileAll" X="185" Y="126" Width="-11" Height="20" TabStop="yes" FontId="3" HideWhenDisabled="no">#(loc.PrecompileLabel)</Checkbox>
+ <Checkbox Name="Include_symbols" X="185" Y="151" Width="-11" Height="20" TabStop="yes" FontId="3" HideWhenDisabled="no">#(loc.Include_symbolsLabel)</Checkbox>
+ <Checkbox Name="Include_debug" X="185" Y="176" Width="-11" Height="20" TabStop="yes" FontId="3" HideWhenDisabled="no">#(loc.Include_debugLabel)</Checkbox>
+
+ <Text X="185" Y="221" Width="-11" Height="17" FontId="3">#(loc.CustomLocationLabel)</Text>
+ <Editbox Name="TargetDir" X="185" Y="237" Width="-91" Height="21" TabStop="yes" FontId="3" FileSystemAutoComplete="yes" />
+ <Button Name="CustomBrowseButton" X="-11" Y="236" Width="75" Height="23" TabStop="yes" FontId="3">#(loc.CustomBrowseButton)</Button>
+ <Text X="185" Y="261" Width="-91" Height="35" FontId="5">#(loc.CustomLocationHelpLabel)</Text>
+
+ <Button Name="Custom2BackButton" X="185" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.CustomBackButton)</Button>
+ <Button Name="CustomInstallButton" X="-91" Y="-11" Width="95" Height="23" TabStop="yes" FontId="0">#(loc.CustomInstallButton)</Button>
+ <Button Name="Custom2CancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.CancelButton)</Button>
+ </Page>
+ <Page Name="Progress">
+ <Text X="185" Y="11" Width="-11" Height="32" FontId="1" DisablePrefix="yes">#(loc.ProgressHeader)</Text>
+ <Image X="0" Y="0" Width="162" Height="352" ImageFile="SideBar.png"/>
+
+ <Text X="185" Y="111" Width="70" Height="17" FontId="3" DisablePrefix="yes">#(loc.ProgressLabel)</Text>
+ <Text Name="OverallProgressPackageText" X="185" Y="141" Width="-11" Height="17" FontId="3" DisablePrefix="yes">#(loc.OverallProgressPackageText)</Text>
+ <Progressbar Name="OverallCalculatedProgressbar" X="185" Y="163" Width="-11" Height="20" />
+ <Button Name="ProgressCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.CancelButton)</Button>
+ </Page>
+ <Page Name="Modify">
+ <Text X="185" Y="11" Width="-11" Height="32" FontId="1" DisablePrefix="yes">#(loc.ModifyHeader)</Text>
+ <Image X="0" Y="0" Width="162" Height="352" ImageFile="SideBar.png"/>
+
+ <Button Name="ModifyButton" X="175" Y="101" Width="-11" Height="59" TabStop="yes" FontId="3" HexStyle="0xF">#(loc.ModifyModifyButton)</Button>
+ <Button Name="RepairButton" X="175" Y="171" Width="-11" Height="59" TabStop="yes" FontId="3" HexStyle="0xE">#(loc.ModifyRepairButton)</Button>
+ <Button Name="UninstallButton" X="175" Y="241" Width="-11" Height="59" TabStop="yes" FontId="3" HexStyle="0xE">#(loc.ModifyUninstallButton)</Button>
+
+ <Button Name="ModifyCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.CancelButton)</Button>
+ </Page>
+ <Page Name="Success">
+ <Text X="185" Y="11" Width="-11" Height="32" FontId="1" DisablePrefix="yes">#(loc.SuccessHeader)</Text>
+ <Image X="0" Y="0" Width="162" Height="352" ImageFile="SideBar.png"/>
+
+ <Hypertext Name="SuccessText" X="205" Y="71" Width="-31" Height="100" FontId="3" DisablePrefix="yes"></Hypertext>
+
+ <Text Name="SuccessRestartText" X="185" Y="-100" Width="-11" Height="34" FontId="3" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.SuccessRestartText)</Text>
+ <Button Name="LaunchButton" X="185" Y="-50" Width="-11" Height="59" TabStop="yes" FontId="3" HexStyle="0xF" HideWhenDisabled="yes">#(loc.SuccessLaunchButton)</Button>
+ <Button Name="SuccessRestartButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.SuccessRestartButton)</Button>
+ <Button Name="SuccessCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.CloseButton)</Button>
+ </Page>
+ <Page Name="Failure">
+ <Text X="185" Y="11" Width="-11" Height="32" FontId="1" DisablePrefix="yes">#(loc.FailureHeader)</Text>
+ <Image X="0" Y="0" Width="162" Height="352" ImageFile="SideBar.png"/>
+
+ <Hypertext Name="FailureLogFileLink" X="185" Y="71" Width="-11" Height="60" FontId="3" TabStop="yes" HideWhenDisabled="yes">#(loc.FailureHyperlinkLogText)</Hypertext>
+ <Hypertext Name="FailureMessageText" X="185" Y="-100" Width="-11" Height="60" FontId="3" TabStop="yes" HideWhenDisabled="yes"></Hypertext>
+ <Text Name="FailureRestartText" X="185" Y="-40" Width="-11" Height="34" FontId="3" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureRestartText)</Text>
+ <Button Name="FailureRestartButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.FailureRestartButton)</Button>
+ <Button Name="FailureCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.CloseButton)</Button>
+ </Page>
+</Theme> \ No newline at end of file
diff --git a/Tools/msi/bundle/Default.wxl b/Tools/msi/bundle/Default.wxl
new file mode 100644
index 0000000000..43057e7d60
--- /dev/null
+++ b/Tools/msi/bundle/Default.wxl
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<WixLocalization Culture="en-us" Language="1033" xmlns="http://schemas.microsoft.com/wix/2006/localization">
+ <String Id="Caption">[WixBundleName] Setup</String>
+ <String Id="Title">[WixBundleName]</String>
+ <String Id="Installing">Installing</String>
+ <String Id="Installation">Setup</String>
+ <String Id="Modifying">Updating</String>
+ <String Id="Modification">Modify</String>
+ <String Id="Repairing">Repairing</String>
+ <String Id="Repair">Repair</String>
+ <String Id="Uninstalling">Removing</String>
+ <String Id="Uninstallation">Uninstall</String>
+
+ <String Id="ElevateForCRTInstall">You will be prompted for Administrator privileges to install a C Runtime Library update (KB2999226).
+
+
+Continue?</String>
+
+ <String Id="CancelButton">&amp;Cancel</String>
+ <String Id="CloseButton">&amp;Close</String>
+ <String Id="InstallHeader">Install [WixBundleName]</String>
+ <String Id="InstallMessage">Select to location to install Python, or choose Customize to enable or disable features.</String>
+ <String Id="InstallVersion">Version [WixBundleVersion]</String>
+ <String Id="ConfirmCancelMessage">Are you sure you want to cancel?</String>
+ <String Id="ExecuteUpgradeRelatedBundleMessage">Previous version</String>
+ <String Id="HelpHeader">Setup Help</String>
+ <String Id="HelpText">/uninstall
+ Uninstalls Python without prompting for confirmation.
+
+/layout [\[]directory[\]]
+ Downloads all components for offline installation.
+
+/passive
+ Displays progress without requiring user interaction.
+
+/quiet
+ Performs the requested action without displaying any UI.
+
+/log [\[]filename[\]]
+ Logs to a specific file. By default, log files are created in %TEMP%.</String>
+ <String Id="InstallLicenseLinkText">[WixBundleName] &lt;a href="#"&gt;license terms&lt;/a&gt;.</String>
+ <String Id="InstallAcceptCheckbox">I &amp;agree to the license terms and conditions</String>
+ <String Id="InstallAllUsersButton">Install for &amp;All Users</String>
+ <String Id="InstallAllUsersButtonNote">[DefaultAllUsersTargetDir]</String>
+ <String Id="InstallJustForMeButton">Install &amp;Just for Me</String>
+ <String Id="InstallJustForMeButtonNote">[DefaultJustForMeTargetDir]</String>
+ <String Id="InstallCustomButton">C&amp;ustomize installation</String>
+ <String Id="InstallCustomButtonNote">Choose location and features</String>
+ <String Id="InstallSimpleButton">&amp;Install</String>
+ <String Id="InstallSimpleButtonNote">Uses setting preselected by your administrator</String>
+ <String Id="Custom1Header">Optional Features</String>
+ <String Id="Custom2Header">Advanced Options</String>
+ <String Id="CustomLocationLabel">Customize install location</String>
+ <String Id="CustomLocationHelpLabel">If not installing as administrator, you will require write permissions for this location.</String>
+ <String Id="CustomInstallButton">&amp;Install</String>
+ <String Id="CustomNextButton">&amp;Next</String>
+ <String Id="CustomBackButton">&amp;Back</String>
+ <String Id="CustomBrowseButton">B&amp;rowse</String>
+ <String Id="Include_docLabel">&amp;Documentation</String>
+ <String Id="Include_docHelpLabel">Installs the Python documentation file.</String>
+ <String Id="Include_pipLabel">&amp;pip</String>
+ <String Id="Include_pipHelpLabel">Installs pip, which can download and install other Python packages.</String>
+ <String Id="Include_tcltkLabel">tcl/tk and &amp;IDLE</String>
+ <String Id="Include_tcltkHelpLabel">Installs tkinter and the IDLE development environment.</String>
+ <String Id="Include_testLabel">Python &amp;test suite</String>
+ <String Id="Include_testHelpLabel">Installs the standard library test suite.</String>
+ <String Id="Include_launcherLabel">py &amp;launcher</String>
+ <String Id="Include_launcherHelpLabel">Installs the global 'py' launcher to make it easier to start Python.</String>
+
+ <String Id="AssociateFilesLabel">Associate &amp;files with Python (requires the py launcher)</String>
+ <String Id="PrependPathLabel">Add Python to &amp;environment variables</String>
+ <String Id="ShortPrependPathLabel">Add &amp;Python [ShortVersion] to PATH</String>
+ <String Id="InstallAllUsersLabel">Install as &amp;Administrator</String>
+ <String Id="PrecompileLabel">&amp;Precompile standard library</String>
+ <String Id="Include_symbolsLabel">Install debugging &amp;symbols</String>
+ <String Id="Include_debugLabel">Install debu&amp;g binaries (requires VS 2015 or later)</String>
+
+ <String Id="ProgressHeader">[ActionLikeInstallation] Progress</String>
+ <String Id="ProgressLabel">[ActionLikeInstalling]:</String>
+ <String Id="OverallProgressPackageText">Initializing...</String>
+ <String Id="ModifyHeader">Modify Setup</String>
+ <String Id="ModifyModifyButton">&amp;Modify</String>
+ <String Id="ModifyButtonNote">Add or remove individual features.</String>
+ <String Id="ModifyRepairButton">&amp;Repair</String>
+ <String Id="RepairButtonNote">Ensure all current features are correctly installed.</String>
+ <String Id="ModifyUninstallButton">&amp;Uninstall</String>
+ <String Id="UninstallButtonNote">Remove the entire [WixBundleName] installation.</String>
+ <String Id="SuccessHeader">[ActionLikeInstallation] was successful</String>
+ <String Id="SuccessLaunchButton">&amp;Launch</String>
+ <String Id="SuccessRestartText">You may need to restart your computer to finish updating files.</String>
+ <String Id="SuccessRestartButton">&amp;Restart</String>
+ <String Id="SuccessInstallMessage">Special thanks to Mark Hammond, without whose years of freely shared Windows expertise, Python for Windows would still be Python for DOS.
+
+New to Python? Start with the &lt;a href="https://docs.python.org/[ShortVersion]/tutorial/index.html"&gt;online tutorial&lt;/a&gt; and &lt;a href="https://docs.python.org/[ShortVersion]/index.html"&gt;documentation&lt;/a&gt;.
+
+See &lt;a href="https://docs.python.org/[ShortVersion]/whatsnew/[ShortVersion].html"&gt;what's new&lt;/a&gt; in this release.</String>
+ <String Id="SuccessModifyMessage">Thank you for using [WixBundleName].</String>
+ <String Id="SuccessRepairMessage">Thank you for using [WixBundleName].
+
+Feel free to email &lt;a href="mailto:python-list@python.org"&gt;python-list@python.org&lt;/a&gt; if you continue to encounter issues.</String>
+ <String Id="SuccessRemoveMessage">Thank you for using [WixBundleName].
+
+Feel free to email &lt;a href="mailto:python-list@python.org"&gt;python-list@python.org&lt;/a&gt; if you encountered problems.</String>
+ <String Id="FailureHeader">Setup failed</String>
+ <String Id="FailureHyperlinkLogText">One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the &lt;a href="#"&gt;log file&lt;/a&gt;.</String>
+ <String Id="FailureRestartText">You must restart your computer to complete the rollback of the software.</String>
+ <String Id="FailureRestartButton">&amp;Restart</String>
+</WixLocalization>
diff --git a/Tools/msi/bundle/SideBar.png b/Tools/msi/bundle/SideBar.png
new file mode 100644
index 0000000000..9c18fff33a
--- /dev/null
+++ b/Tools/msi/bundle/SideBar.png
Binary files differ
diff --git a/Tools/msi/bundle/bootstrap/LICENSE.txt b/Tools/msi/bundle/bootstrap/LICENSE.txt
new file mode 100644
index 0000000000..5791a7e81d
--- /dev/null
+++ b/Tools/msi/bundle/bootstrap/LICENSE.txt
@@ -0,0 +1,25 @@
+This license applies to the bootstrapper application that is embedded within the installer. It has no impact on the licensing for the rest of the installer or Python itself, as no code covered by this license exists in any other part of the product.
+
+---
+
+Microsoft Reciprocal License (MS-RL)
+
+This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
+
+1. Definitions
+ The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
+ A "contribution" is the original software, or any additions or changes to the software.
+ A "contributor" is any person that distributes its contribution under this license.
+ "Licensed patents" are a contributor's patent claims that read directly on its contribution.
+
+2. Grant of Rights
+ (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
+ (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
+
+3. Conditions and Limitations
+ (A) Reciprocal Grants- For any file you distribute that contains code from the software (in source code or binary format), you must provide recipients the source code to that file along with a copy of this license, which license will govern that file. You may license other files that are entirely your own work and do not contain code from the software under any terms you choose.
+ (B) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
+ (C) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
+ (D) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
+ (E) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
+ (F) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
diff --git a/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp b/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp
new file mode 100644
index 0000000000..bc40846735
--- /dev/null
+++ b/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp
@@ -0,0 +1,2746 @@
+//-------------------------------------------------------------------------------------------------
+// <copyright file="WixStandardBootstrapperApplication.cpp" company="Outercurve Foundation">
+// Copyright (c) 2004, Outercurve Foundation.
+// This software is released under Microsoft Reciprocal License (MS-RL).
+// The license and further copyright text can be found in the file
+// LICENSE.TXT at the root directory of the distribution.
+// </copyright>
+//-------------------------------------------------------------------------------------------------
+
+
+#include "pch.h"
+
+static const LPCWSTR PYBA_WINDOW_CLASS = L"PythonBA";
+static const LPCWSTR PYBA_VARIABLE_LAUNCH_TARGET_PATH = L"LaunchTarget";
+static const LPCWSTR PYBA_VARIABLE_LAUNCH_TARGET_ELEVATED_ID = L"LaunchTargetElevatedId";
+static const LPCWSTR PYBA_VARIABLE_LAUNCH_ARGUMENTS = L"LaunchArguments";
+static const LPCWSTR PYBA_VARIABLE_LAUNCH_HIDDEN = L"LaunchHidden";
+static const DWORD PYBA_ACQUIRE_PERCENTAGE = 30;
+static const LPCWSTR PYBA_VARIABLE_BUNDLE_FILE_VERSION = L"WixBundleFileVersion";
+
+enum PYBA_STATE {
+ PYBA_STATE_INITIALIZING,
+ PYBA_STATE_INITIALIZED,
+ PYBA_STATE_HELP,
+ PYBA_STATE_DETECTING,
+ PYBA_STATE_DETECTED,
+ PYBA_STATE_PLANNING,
+ PYBA_STATE_PLANNED,
+ PYBA_STATE_APPLYING,
+ PYBA_STATE_CACHING,
+ PYBA_STATE_CACHED,
+ PYBA_STATE_EXECUTING,
+ PYBA_STATE_EXECUTED,
+ PYBA_STATE_APPLIED,
+ PYBA_STATE_FAILED,
+};
+
+static const int WM_PYBA_SHOW_HELP = WM_APP + 100;
+static const int WM_PYBA_DETECT_PACKAGES = WM_APP + 101;
+static const int WM_PYBA_PLAN_PACKAGES = WM_APP + 102;
+static const int WM_PYBA_APPLY_PACKAGES = WM_APP + 103;
+static const int WM_PYBA_CHANGE_STATE = WM_APP + 104;
+static const int WM_PYBA_SHOW_FAILURE = WM_APP + 105;
+
+// This enum must be kept in the same order as the PAGE_NAMES array.
+enum PAGE {
+ PAGE_LOADING,
+ PAGE_HELP,
+ PAGE_INSTALL,
+ PAGE_SIMPLE_INSTALL,
+ PAGE_CUSTOM1,
+ PAGE_CUSTOM2,
+ PAGE_MODIFY,
+ PAGE_PROGRESS,
+ PAGE_PROGRESS_PASSIVE,
+ PAGE_SUCCESS,
+ PAGE_FAILURE,
+ COUNT_PAGE,
+};
+
+// This array must be kept in the same order as the PAGE enum.
+static LPCWSTR PAGE_NAMES[] = {
+ L"Loading",
+ L"Help",
+ L"Install",
+ L"SimpleInstall",
+ L"Custom1",
+ L"Custom2",
+ L"Modify",
+ L"Progress",
+ L"ProgressPassive",
+ L"Success",
+ L"Failure",
+};
+
+enum CONTROL_ID {
+ // Non-paged controls
+ ID_CLOSE_BUTTON = THEME_FIRST_ASSIGN_CONTROL_ID,
+ ID_MINIMIZE_BUTTON,
+
+ // Welcome page
+ ID_INSTALL_ALL_USERS_BUTTON,
+ ID_INSTALL_JUST_FOR_ME_BUTTON,
+ ID_INSTALL_CUSTOM_BUTTON,
+ ID_INSTALL_SIMPLE_BUTTON,
+ ID_INSTALL_CANCEL_BUTTON,
+
+ // Customize Page
+ ID_TARGETDIR_EDITBOX,
+ ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX,
+ ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX,
+ ID_CUSTOM_BROWSE_BUTTON,
+ ID_CUSTOM_INSTALL_BUTTON,
+ ID_CUSTOM_NEXT_BUTTON,
+ ID_CUSTOM1_BACK_BUTTON,
+ ID_CUSTOM2_BACK_BUTTON,
+ ID_CUSTOM1_CANCEL_BUTTON,
+ ID_CUSTOM2_CANCEL_BUTTON,
+
+ // Modify page
+ ID_MODIFY_BUTTON,
+ ID_REPAIR_BUTTON,
+ ID_UNINSTALL_BUTTON,
+ ID_MODIFY_CANCEL_BUTTON,
+
+ // Progress page
+ ID_CACHE_PROGRESS_PACKAGE_TEXT,
+ ID_CACHE_PROGRESS_BAR,
+ ID_CACHE_PROGRESS_TEXT,
+
+ ID_EXECUTE_PROGRESS_PACKAGE_TEXT,
+ ID_EXECUTE_PROGRESS_BAR,
+ ID_EXECUTE_PROGRESS_TEXT,
+ ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT,
+
+ ID_OVERALL_PROGRESS_PACKAGE_TEXT,
+ ID_OVERALL_PROGRESS_BAR,
+ ID_OVERALL_CALCULATED_PROGRESS_BAR,
+ ID_OVERALL_PROGRESS_TEXT,
+
+ ID_PROGRESS_CANCEL_BUTTON,
+
+ // Success page
+ ID_LAUNCH_BUTTON,
+ ID_SUCCESS_TEXT,
+ ID_SUCCESS_RESTART_TEXT,
+ ID_SUCCESS_RESTART_BUTTON,
+ ID_SUCCESS_CANCEL_BUTTON,
+
+ // Failure page
+ ID_FAILURE_LOGFILE_LINK,
+ ID_FAILURE_MESSAGE_TEXT,
+ ID_FAILURE_RESTART_TEXT,
+ ID_FAILURE_RESTART_BUTTON,
+ ID_FAILURE_CANCEL_BUTTON
+};
+
+static THEME_ASSIGN_CONTROL_ID CONTROL_ID_NAMES[] = {
+ { ID_CLOSE_BUTTON, L"CloseButton" },
+ { ID_MINIMIZE_BUTTON, L"MinimizeButton" },
+
+ { ID_INSTALL_ALL_USERS_BUTTON, L"InstallAllUsersButton" },
+ { ID_INSTALL_JUST_FOR_ME_BUTTON, L"InstallJustForMeButton" },
+ { ID_INSTALL_CUSTOM_BUTTON, L"InstallCustomButton" },
+ { ID_INSTALL_SIMPLE_BUTTON, L"InstallSimpleButton" },
+ { ID_INSTALL_CANCEL_BUTTON, L"InstallCancelButton" },
+
+ { ID_TARGETDIR_EDITBOX, L"TargetDir" },
+ { ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, L"AssociateFiles" },
+ { ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX, L"InstallAllUsers" },
+ { ID_CUSTOM_BROWSE_BUTTON, L"CustomBrowseButton" },
+ { ID_CUSTOM_INSTALL_BUTTON, L"CustomInstallButton" },
+ { ID_CUSTOM_NEXT_BUTTON, L"CustomNextButton" },
+ { ID_CUSTOM1_BACK_BUTTON, L"Custom1BackButton" },
+ { ID_CUSTOM2_BACK_BUTTON, L"Custom2BackButton" },
+ { ID_CUSTOM1_CANCEL_BUTTON, L"Custom1CancelButton" },
+ { ID_CUSTOM2_CANCEL_BUTTON, L"Custom2CancelButton" },
+
+ { ID_MODIFY_BUTTON, L"ModifyButton" },
+ { ID_REPAIR_BUTTON, L"RepairButton" },
+ { ID_UNINSTALL_BUTTON, L"UninstallButton" },
+ { ID_MODIFY_CANCEL_BUTTON, L"ModifyCancelButton" },
+
+ { ID_CACHE_PROGRESS_PACKAGE_TEXT, L"CacheProgressPackageText" },
+ { ID_CACHE_PROGRESS_BAR, L"CacheProgressbar" },
+ { ID_CACHE_PROGRESS_TEXT, L"CacheProgressText" },
+ { ID_EXECUTE_PROGRESS_PACKAGE_TEXT, L"ExecuteProgressPackageText" },
+ { ID_EXECUTE_PROGRESS_BAR, L"ExecuteProgressbar" },
+ { ID_EXECUTE_PROGRESS_TEXT, L"ExecuteProgressText" },
+ { ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, L"ExecuteProgressActionDataText" },
+ { ID_OVERALL_PROGRESS_PACKAGE_TEXT, L"OverallProgressPackageText" },
+ { ID_OVERALL_PROGRESS_BAR, L"OverallProgressbar" },
+ { ID_OVERALL_CALCULATED_PROGRESS_BAR, L"OverallCalculatedProgressbar" },
+ { ID_OVERALL_PROGRESS_TEXT, L"OverallProgressText" },
+ { ID_PROGRESS_CANCEL_BUTTON, L"ProgressCancelButton" },
+
+ { ID_LAUNCH_BUTTON, L"LaunchButton" },
+ { ID_SUCCESS_TEXT, L"SuccessText" },
+ { ID_SUCCESS_RESTART_TEXT, L"SuccessRestartText" },
+ { ID_SUCCESS_RESTART_BUTTON, L"SuccessRestartButton" },
+ { ID_SUCCESS_CANCEL_BUTTON, L"SuccessCancelButton" },
+
+ { ID_FAILURE_LOGFILE_LINK, L"FailureLogFileLink" },
+ { ID_FAILURE_MESSAGE_TEXT, L"FailureMessageText" },
+ { ID_FAILURE_RESTART_TEXT, L"FailureRestartText" },
+ { ID_FAILURE_RESTART_BUTTON, L"FailureRestartButton" },
+ { ID_FAILURE_CANCEL_BUTTON, L"FailureCancelButton" },
+};
+
+class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication {
+ void ShowPage(DWORD newPageId) {
+ // Process each control for special handling in the new page.
+ ProcessPageControls(ThemeGetPage(_theme, newPageId));
+
+ // Enable disable controls per-page.
+ if (_pageIds[PAGE_INSTALL] == newPageId || _pageIds[PAGE_SIMPLE_INSTALL] == newPageId) {
+ InstallPage_Show();
+ } else if (_pageIds[PAGE_CUSTOM1] == newPageId) {
+ Custom1Page_Show();
+ } else if (_pageIds[PAGE_CUSTOM2] == newPageId) {
+ Custom2Page_Show();
+ } else if (_pageIds[PAGE_MODIFY] == newPageId) {
+ ModifyPage_Show();
+ } else if (_pageIds[PAGE_SUCCESS] == newPageId) {
+ SuccessPage_Show();
+ } else if (_pageIds[PAGE_FAILURE] == newPageId) {
+ FailurePage_Show();
+ }
+
+ // Prevent repainting while switching page to avoid ugly flickering
+ _suppressPaint = TRUE;
+ ThemeShowPage(_theme, newPageId, SW_SHOW);
+ ThemeShowPage(_theme, _visiblePageId, SW_HIDE);
+ _suppressPaint = FALSE;
+ InvalidateRect(_theme->hwndParent, nullptr, TRUE);
+ _visiblePageId = newPageId;
+
+ // On the install page set the focus to the install button or
+ // the next enabled control if install is disabled
+ if (_pageIds[PAGE_INSTALL] == newPageId) {
+ ThemeSetFocus(_theme, ID_INSTALL_ALL_USERS_BUTTON);
+ } else if (_pageIds[PAGE_SIMPLE_INSTALL] == newPageId) {
+ ThemeSetFocus(_theme, ID_INSTALL_SIMPLE_BUTTON);
+ }
+ }
+
+ //
+ // Handles control clicks
+ //
+ void OnCommand(CONTROL_ID id) {
+ LPWSTR defaultDir = nullptr;
+ LPWSTR targetDir = nullptr;
+ LONGLONG elevated, crtInstalled;
+ BOOL checked;
+ WCHAR wzPath[MAX_PATH] = { };
+ BROWSEINFOW browseInfo = { };
+ PIDLIST_ABSOLUTE pidl = nullptr;
+ HRESULT hr = S_OK;
+
+ switch(id) {
+ case ID_CLOSE_BUTTON:
+ OnClickCloseButton();
+ break;
+
+ // Install commands
+ case ID_INSTALL_SIMPLE_BUTTON:
+ hr = BalGetStringVariable(L"TargetDir", &targetDir);
+ if (FAILED(hr) || !targetDir || !*targetDir) {
+ LONGLONG installAll;
+ if (SUCCEEDED(BalGetNumericVariable(L"InstallAllUsers", &installAll)) && installAll) {
+ hr = BalGetStringVariable(L"DefaultAllUsersTargetDir", &defaultDir);
+ BalExitOnFailure(hr, "Failed to get the default all users install directory");
+ } else {
+ hr = BalGetStringVariable(L"DefaultJustForMeTargetDir", &defaultDir);
+ BalExitOnFailure(hr, "Failed to get the default per-user install directory");
+ }
+
+ if (!defaultDir || !*defaultDir) {
+ BalLogError(E_INVALIDARG, "Default install directory is blank");
+ }
+
+ hr = BalFormatString(defaultDir, &targetDir);
+ BalExitOnFailure1(hr, "Failed to format '%ls'", defaultDir);
+
+ hr = _engine->SetVariableString(L"TargetDir", targetDir);
+ ReleaseStr(targetDir);
+ BalExitOnFailure(hr, "Failed to set install target directory");
+ } else {
+ ReleaseStr(targetDir);
+ }
+
+ OnPlan(BOOTSTRAPPER_ACTION_INSTALL);
+ break;
+
+ case ID_INSTALL_ALL_USERS_BUTTON:
+ SavePageSettings();
+
+ hr = _engine->SetVariableNumeric(L"InstallAllUsers", 1);
+ ExitOnFailure(hr, L"Failed to set install scope");
+
+ hr = _engine->SetVariableNumeric(L"CompileAll", 1);
+ ExitOnFailure(hr, L"Failed to set compile all setting");
+
+ hr = BalGetStringVariable(L"DefaultAllUsersTargetDir", &defaultDir);
+ BalExitOnFailure(hr, "Failed to get the default all users install directory");
+
+ if (!defaultDir || !*defaultDir) {
+ BalLogError(E_INVALIDARG, "Default install directory is blank");
+ }
+
+ hr = BalFormatString(defaultDir, &targetDir);
+ BalExitOnFailure1(hr, "Failed to format '%ls'", defaultDir);
+
+ hr = _engine->SetVariableString(L"TargetDir", targetDir);
+ ReleaseStr(targetDir);
+ BalExitOnFailure(hr, "Failed to set install target directory");
+
+ OnPlan(BOOTSTRAPPER_ACTION_INSTALL);
+ break;
+
+ case ID_INSTALL_JUST_FOR_ME_BUTTON:
+ SavePageSettings();
+
+ hr = _engine->SetVariableNumeric(L"InstallAllUsers", 0);
+ ExitOnFailure(hr, L"Failed to set install scope");
+
+ hr = BalGetStringVariable(L"DefaultJustForMeTargetDir", &defaultDir);
+ BalExitOnFailure(hr, "Failed to get the default per-user install directory");
+
+ if (!defaultDir || !*defaultDir) {
+ BalLogError(E_INVALIDARG, "Default install directory is blank");
+ }
+
+ hr = BalFormatString(defaultDir, &targetDir);
+ BalExitOnFailure1(hr, "Failed to format '%ls'", defaultDir);
+
+ hr = _engine->SetVariableString(L"TargetDir", targetDir);
+ ReleaseStr(targetDir);
+ BalExitOnFailure(hr, "Failed to set install target directory");
+
+ if (!QueryElevateForCrtInstall()) {
+ break;
+ }
+
+ OnPlan(BOOTSTRAPPER_ACTION_INSTALL);
+ break;
+
+ case ID_CUSTOM1_BACK_BUTTON:
+ SavePageSettings();
+ if (_modifying) {
+ GoToPage(PAGE_MODIFY);
+ } else {
+ GoToPage(PAGE_INSTALL);
+ }
+ break;
+
+ case ID_INSTALL_CUSTOM_BUTTON: __fallthrough;
+ case ID_CUSTOM2_BACK_BUTTON:
+ SavePageSettings();
+ GoToPage(PAGE_CUSTOM1);
+ break;
+
+ case ID_CUSTOM_NEXT_BUTTON:
+ SavePageSettings();
+ GoToPage(PAGE_CUSTOM2);
+ break;
+
+ case ID_CUSTOM_INSTALL_BUTTON:
+ SavePageSettings();
+
+ hr = BalGetStringVariable(L"TargetDir", &targetDir);
+ if (SUCCEEDED(hr)) {
+ // TODO: Check whether directory exists and contains another installation
+ ReleaseStr(targetDir);
+ }
+
+ checked = ThemeIsControlChecked(_theme, ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX);
+ if (!checked && !QueryElevateForCrtInstall()) {
+ break;
+ }
+
+ OnPlan(_command.action);
+ break;
+
+ case ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX:
+ hr = BalGetNumericVariable(L"WixBundleElevated", &elevated);
+ checked = ThemeIsControlChecked(_theme, ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX);
+ ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, checked && (FAILED(hr) || !elevated));
+ ThemeGetTextControl(_theme, ID_TARGETDIR_EDITBOX, &targetDir);
+ if (targetDir) {
+ // Check the current value against the default to see
+ // if we should switch it automatically.
+ hr = BalGetStringVariable(
+ checked ? L"DefaultJustForMeTargetDir" : L"DefaultAllUsersTargetDir",
+ &defaultDir
+ );
+
+ if (SUCCEEDED(hr) && defaultDir) {
+ LPWSTR formatted = nullptr;
+ if (defaultDir[0] && SUCCEEDED(BalFormatString(defaultDir, &formatted))) {
+ if (wcscmp(formatted, targetDir) == 0) {
+ ReleaseStr(defaultDir);
+ defaultDir = nullptr;
+ ReleaseStr(formatted);
+ formatted = nullptr;
+
+ hr = BalGetStringVariable(
+ checked ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir",
+ &defaultDir
+ );
+ if (SUCCEEDED(hr) && defaultDir && defaultDir[0] && SUCCEEDED(BalFormatString(defaultDir, &formatted))) {
+ ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, formatted);
+ ReleaseStr(formatted);
+ }
+ } else {
+ ReleaseStr(formatted);
+ }
+ }
+
+ ReleaseStr(defaultDir);
+ }
+ }
+ break;
+
+ case ID_CUSTOM_BROWSE_BUTTON:
+ browseInfo.hwndOwner = _hWnd;
+ browseInfo.pszDisplayName = wzPath;
+ browseInfo.lpszTitle = _theme->sczCaption;
+ browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
+ pidl = ::SHBrowseForFolderW(&browseInfo);
+ if (pidl && ::SHGetPathFromIDListW(pidl, wzPath)) {
+ ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, wzPath);
+ }
+
+ if (pidl) {
+ ::CoTaskMemFree(pidl);
+ }
+ break;
+
+ // Modify commands
+ case ID_MODIFY_BUTTON:
+ // Some variables cannot be modified
+ _engine->SetVariableString(L"InstallAllUsersState", L"disable");
+ _engine->SetVariableString(L"TargetDirState", L"disable");
+ _engine->SetVariableString(L"CustomBrowseButtonState", L"disable");
+ _modifying = TRUE;
+ GoToPage(PAGE_CUSTOM1);
+ break;
+
+ case ID_REPAIR_BUTTON:
+ OnPlan(BOOTSTRAPPER_ACTION_REPAIR);
+ break;
+
+ case ID_UNINSTALL_BUTTON:
+ OnPlan(BOOTSTRAPPER_ACTION_UNINSTALL);
+ break;
+ }
+
+ LExit:
+ return;
+ }
+
+ void InstallPage_Show() {
+ // Ensure the All Users install button has a UAC shield
+ LONGLONG elevated, installAll;
+
+ if (FAILED(BalGetNumericVariable(L"WixBundleElevated", &elevated))) {
+ elevated = 0;
+ }
+
+ ThemeControlElevates(_theme, ID_INSTALL_ALL_USERS_BUTTON, !elevated);
+
+ if (SUCCEEDED(BalGetNumericVariable(L"InstallAllUsers", &installAll)) && installAll) {
+ ThemeControlElevates(_theme, ID_INSTALL_SIMPLE_BUTTON, !elevated);
+ }
+ }
+
+ void Custom1Page_Show() {
+ }
+
+ void Custom2Page_Show() {
+ HRESULT hr;
+ LONGLONG installAll, elevated, includeLauncher;
+
+ if (FAILED(BalGetNumericVariable(L"WixBundleElevated", &elevated))) {
+ elevated = 0;
+ }
+ if (SUCCEEDED(BalGetNumericVariable(L"InstallAllUsers", &installAll))) {
+ ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, installAll && !elevated);
+ } else {
+ installAll = 0;
+ }
+
+ if (SUCCEEDED(BalGetNumericVariable(L"Include_launcher", &includeLauncher)) && includeLauncher) {
+ ThemeControlEnable(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, TRUE);
+ } else {
+ ThemeSendControlMessage(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, BM_SETCHECK, BST_UNCHECKED, 0);
+ ThemeControlEnable(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, FALSE);
+ }
+
+ LPWSTR targetDir = nullptr;
+ hr = BalGetStringVariable(L"TargetDir", &targetDir);
+ if (SUCCEEDED(hr) && targetDir && targetDir[0]) {
+ ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, targetDir);
+ StrFree(targetDir);
+ } else if (SUCCEEDED(hr)) {
+ StrFree(targetDir);
+ targetDir = nullptr;
+
+ LPWSTR defaultTargetDir = nullptr;
+ hr = BalGetStringVariable(L"DefaultCustomTargetDir", &defaultTargetDir);
+ if (SUCCEEDED(hr) && defaultTargetDir && !defaultTargetDir[0]) {
+ StrFree(defaultTargetDir);
+ defaultTargetDir = nullptr;
+
+ hr = BalGetStringVariable(
+ installAll ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir",
+ &defaultTargetDir
+ );
+ }
+ if (SUCCEEDED(hr) && defaultTargetDir) {
+ if (defaultTargetDir[0] && SUCCEEDED(BalFormatString(defaultTargetDir, &targetDir))) {
+ ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, targetDir);
+ StrFree(targetDir);
+ }
+ StrFree(defaultTargetDir);
+ }
+ }
+ }
+
+ void ModifyPage_Show() {
+ ThemeControlEnable(_theme, ID_REPAIR_BUTTON, !_suppressRepair);
+ }
+
+ void SuccessPage_Show() {
+ // on the "Success" page, check if the restart or launch button should be enabled.
+ BOOL showRestartButton = FALSE;
+ BOOL launchTargetExists = FALSE;
+ LOC_STRING *successText = nullptr;
+ HRESULT hr = S_OK;
+
+ if (_restartRequired) {
+ if (BOOTSTRAPPER_RESTART_PROMPT == _command.restart) {
+ showRestartButton = TRUE;
+ }
+ } else if (ThemeControlExists(_theme, ID_LAUNCH_BUTTON)) {
+ launchTargetExists = BalStringVariableExists(PYBA_VARIABLE_LAUNCH_TARGET_PATH);
+ }
+
+ switch (_plannedAction) {
+ case BOOTSTRAPPER_ACTION_INSTALL:
+ hr = LocGetString(_wixLoc, L"#(loc.SuccessInstallMessage)", &successText);
+ break;
+ case BOOTSTRAPPER_ACTION_MODIFY:
+ hr = LocGetString(_wixLoc, L"#(loc.SuccessModifyMessage)", &successText);
+ break;
+ case BOOTSTRAPPER_ACTION_REPAIR:
+ hr = LocGetString(_wixLoc, L"#(loc.SuccessRepairMessage)", &successText);
+ break;
+ case BOOTSTRAPPER_ACTION_UNINSTALL:
+ hr = LocGetString(_wixLoc, L"#(loc.SuccessRemoveMessage)", &successText);
+ break;
+ }
+
+ if (successText) {
+ LPWSTR formattedString = nullptr;
+ BalFormatString(successText->wzText, &formattedString);
+ if (formattedString) {
+ ThemeSetTextControl(_theme, ID_SUCCESS_TEXT, formattedString);
+ StrFree(formattedString);
+ }
+ }
+
+ ThemeControlEnable(_theme, ID_LAUNCH_BUTTON, launchTargetExists && BOOTSTRAPPER_ACTION_UNINSTALL < _plannedAction);
+ ThemeControlEnable(_theme, ID_SUCCESS_RESTART_TEXT, showRestartButton);
+ ThemeControlEnable(_theme, ID_SUCCESS_RESTART_BUTTON, showRestartButton);
+ }
+
+ void FailurePage_Show() {
+ // on the "Failure" page, show error message and check if the restart button should be enabled.
+
+ // if there is a log file variable then we'll assume the log file exists.
+ BOOL showLogLink = (_bundle.sczLogVariable && *_bundle.sczLogVariable);
+ BOOL showErrorMessage = FALSE;
+ BOOL showRestartButton = FALSE;
+
+ if (FAILED(_hrFinal)) {
+ LPWSTR unformattedText = nullptr;
+ LPWSTR text = nullptr;
+
+ // If we know the failure message, use that.
+ if (_failedMessage && *_failedMessage) {
+ StrAllocString(&unformattedText, _failedMessage, 0);
+ } else {
+ // try to get the error message from the error code.
+ StrAllocFromError(&unformattedText, _hrFinal, nullptr);
+ if (!unformattedText || !*unformattedText) {
+ StrAllocFromError(&unformattedText, E_FAIL, nullptr);
+ }
+ }
+
+ if (E_WIXSTDBA_CONDITION_FAILED == _hrFinal) {
+ if (unformattedText) {
+ StrAllocString(&text, unformattedText, 0);
+ }
+ } else {
+ StrAllocFormatted(&text, L"0x%08x - %ls", _hrFinal, unformattedText);
+ }
+
+ if (text) {
+ ThemeSetTextControl(_theme, ID_FAILURE_MESSAGE_TEXT, text);
+ showErrorMessage = TRUE;
+ }
+
+ ReleaseStr(text);
+ ReleaseStr(unformattedText);
+ }
+
+ if (_restartRequired && BOOTSTRAPPER_RESTART_PROMPT == _command.restart) {
+ showRestartButton = TRUE;
+ }
+
+ ThemeControlEnable(_theme, ID_FAILURE_LOGFILE_LINK, showLogLink);
+ ThemeControlEnable(_theme, ID_FAILURE_MESSAGE_TEXT, showErrorMessage);
+ ThemeControlEnable(_theme, ID_FAILURE_RESTART_TEXT, showRestartButton);
+ ThemeControlEnable(_theme, ID_FAILURE_RESTART_BUTTON, showRestartButton);
+ }
+
+
+public: // IBootstrapperApplication
+ virtual STDMETHODIMP OnStartup() {
+ HRESULT hr = S_OK;
+ DWORD dwUIThreadId = 0;
+
+ // create UI thread
+ _hUiThread = ::CreateThread(nullptr, 0, UiThreadProc, this, 0, &dwUIThreadId);
+ if (!_hUiThread) {
+ ExitWithLastError(hr, "Failed to create UI thread.");
+ }
+
+ LExit:
+ return hr;
+ }
+
+
+ virtual STDMETHODIMP_(int) OnShutdown() {
+ int nResult = IDNOACTION;
+
+ // wait for UI thread to terminate
+ if (_hUiThread) {
+ ::WaitForSingleObject(_hUiThread, INFINITE);
+ ReleaseHandle(_hUiThread);
+ }
+
+ // If a restart was required.
+ if (_restartRequired && _allowRestart) {
+ nResult = IDRESTART;
+ }
+
+ return nResult;
+ }
+
+
+ virtual STDMETHODIMP_(int) OnDetectRelatedBundle(
+ __in LPCWSTR wzBundleId,
+ __in BOOTSTRAPPER_RELATION_TYPE relationType,
+ __in LPCWSTR /*wzBundleTag*/,
+ __in BOOL fPerMachine,
+ __in DWORD64 /*dw64Version*/,
+ __in BOOTSTRAPPER_RELATED_OPERATION operation
+ ) {
+ BalInfoAddRelatedBundleAsPackage(&_bundle.packages, wzBundleId, relationType, fPerMachine);
+
+ // Remember when our bundle would cause a downgrade.
+ if (BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE == operation) {
+ _downgrading = TRUE;
+ }
+
+ return CheckCanceled() ? IDCANCEL : IDOK;
+ }
+
+
+ virtual STDMETHODIMP_(void) OnDetectPackageComplete(
+ __in LPCWSTR wzPackageId,
+ __in HRESULT /*hrStatus*/,
+ __in BOOTSTRAPPER_PACKAGE_STATE state
+ ) { }
+
+
+ virtual STDMETHODIMP_(void) OnDetectComplete(__in HRESULT hrStatus) {
+ if (SUCCEEDED(hrStatus) && _baFunction) {
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running detect complete BA function");
+ _baFunction->OnDetectComplete();
+ }
+
+ if (SUCCEEDED(hrStatus)) {
+ hrStatus = EvaluateConditions();
+ }
+
+ SetState(PYBA_STATE_DETECTED, hrStatus);
+
+ // If we're not interacting with the user or we're doing a layout or we're just after a force restart
+ // then automatically start planning.
+ if (BOOTSTRAPPER_DISPLAY_FULL > _command.display ||
+ BOOTSTRAPPER_ACTION_LAYOUT == _command.action ||
+ BOOTSTRAPPER_ACTION_UNINSTALL == _command.action ||
+ BOOTSTRAPPER_RESUME_TYPE_REBOOT == _command.resumeType) {
+ if (SUCCEEDED(hrStatus)) {
+ ::PostMessageW(_hWnd, WM_PYBA_PLAN_PACKAGES, 0, _command.action);
+ }
+ }
+ }
+
+
+ virtual STDMETHODIMP_(int) OnPlanRelatedBundle(
+ __in_z LPCWSTR /*wzBundleId*/,
+ __inout_z BOOTSTRAPPER_REQUEST_STATE* pRequestedState
+ ) {
+ return CheckCanceled() ? IDCANCEL : IDOK;
+ }
+
+
+ virtual STDMETHODIMP_(int) OnPlanPackageBegin(
+ __in_z LPCWSTR wzPackageId,
+ __inout BOOTSTRAPPER_REQUEST_STATE *pRequestState
+ ) {
+ HRESULT hr = S_OK;
+ BAL_INFO_PACKAGE* pPackage = nullptr;
+
+ if (_nextPackageAfterRestart) {
+ // After restart we need to finish the dependency registration for our package so allow the package
+ // to go present.
+ if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, _nextPackageAfterRestart, -1)) {
+ // Do not allow a repair because that could put us in a perpetual restart loop.
+ if (BOOTSTRAPPER_REQUEST_STATE_REPAIR == *pRequestState) {
+ *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT;
+ }
+
+ ReleaseNullStr(_nextPackageAfterRestart); // no more skipping now.
+ } else {
+ // not the matching package, so skip it.
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Skipping package: %ls, after restart because it was applied before the restart.", wzPackageId);
+
+ *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE;
+ }
+ } else if ((_plannedAction == BOOTSTRAPPER_ACTION_INSTALL || _plannedAction == BOOTSTRAPPER_ACTION_MODIFY) &&
+ SUCCEEDED(BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage))) {
+ BOOL f = FALSE;
+ if (SUCCEEDED(_engine->EvaluateCondition(pPackage->sczInstallCondition, &f)) && f) {
+ *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT;
+ }
+ }
+
+ return CheckCanceled() ? IDCANCEL : IDOK;
+ }
+
+ virtual STDMETHODIMP_(int) OnPlanMsiFeature(
+ __in_z LPCWSTR wzPackageId,
+ __in_z LPCWSTR wzFeatureId,
+ __inout BOOTSTRAPPER_FEATURE_STATE* pRequestedState
+ ) {
+ LONGLONG install;
+
+ if (wcscmp(wzFeatureId, L"AssociateFiles") == 0) {
+ if (SUCCEEDED(_engine->GetVariableNumeric(L"AssociateFiles", &install)) && install) {
+ *pRequestedState = BOOTSTRAPPER_FEATURE_STATE_LOCAL;
+ } else {
+ *pRequestedState = BOOTSTRAPPER_FEATURE_STATE_ABSENT;
+ }
+ } else {
+ *pRequestedState = BOOTSTRAPPER_FEATURE_STATE_LOCAL;
+ }
+ return CheckCanceled() ? IDCANCEL : IDNOACTION;
+ }
+
+ virtual STDMETHODIMP_(void) OnPlanComplete(__in HRESULT hrStatus) {
+ if (SUCCEEDED(hrStatus) && _baFunction) {
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running plan complete BA function");
+ _baFunction->OnPlanComplete();
+ }
+
+ SetState(PYBA_STATE_PLANNED, hrStatus);
+
+ if (SUCCEEDED(hrStatus)) {
+ ::PostMessageW(_hWnd, WM_PYBA_APPLY_PACKAGES, 0, 0);
+ }
+
+ _startedExecution = FALSE;
+ _calculatedCacheProgress = 0;
+ _calculatedExecuteProgress = 0;
+ }
+
+
+ virtual STDMETHODIMP_(int) OnCachePackageBegin(
+ __in_z LPCWSTR wzPackageId,
+ __in DWORD cCachePayloads,
+ __in DWORD64 dw64PackageCacheSize
+ ) {
+ if (wzPackageId && *wzPackageId) {
+ BAL_INFO_PACKAGE* pPackage = nullptr;
+ HRESULT hr = BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage);
+ LPCWSTR wz = (SUCCEEDED(hr) && pPackage->sczDisplayName) ? pPackage->sczDisplayName : wzPackageId;
+
+ ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_PACKAGE_TEXT, wz);
+
+ // If something started executing, leave it in the overall progress text.
+ if (!_startedExecution) {
+ ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, wz);
+ }
+ }
+
+ return __super::OnCachePackageBegin(wzPackageId, cCachePayloads, dw64PackageCacheSize);
+ }
+
+
+ virtual STDMETHODIMP_(int) OnCacheAcquireProgress(
+ __in_z LPCWSTR wzPackageOrContainerId,
+ __in_z_opt LPCWSTR wzPayloadId,
+ __in DWORD64 dw64Progress,
+ __in DWORD64 dw64Total,
+ __in DWORD dwOverallPercentage
+ ) {
+ WCHAR wzProgress[5] = { };
+
+#ifdef DEBUG
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnCacheAcquireProgress() - container/package: %ls, payload: %ls, progress: %I64u, total: %I64u, overall progress: %u%%", wzPackageOrContainerId, wzPayloadId, dw64Progress, dw64Total, dwOverallPercentage);
+#endif
+
+ ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallPercentage);
+ ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_TEXT, wzProgress);
+
+ ThemeSetProgressControl(_theme, ID_CACHE_PROGRESS_BAR, dwOverallPercentage);
+
+ _calculatedCacheProgress = dwOverallPercentage * PYBA_ACQUIRE_PERCENTAGE / 100;
+ ThemeSetProgressControl(_theme, ID_OVERALL_CALCULATED_PROGRESS_BAR, _calculatedCacheProgress + _calculatedExecuteProgress);
+
+ SetTaskbarButtonProgress(_calculatedCacheProgress + _calculatedExecuteProgress);
+
+ return __super::OnCacheAcquireProgress(wzPackageOrContainerId, wzPayloadId, dw64Progress, dw64Total, dwOverallPercentage);
+ }
+
+
+ virtual STDMETHODIMP_(int) OnCacheAcquireComplete(
+ __in_z LPCWSTR wzPackageOrContainerId,
+ __in_z_opt LPCWSTR wzPayloadId,
+ __in HRESULT hrStatus,
+ __in int nRecommendation
+ ) {
+ SetProgressState(hrStatus);
+ return __super::OnCacheAcquireComplete(wzPackageOrContainerId, wzPayloadId, hrStatus, nRecommendation);
+ }
+
+
+ virtual STDMETHODIMP_(int) OnCacheVerifyComplete(
+ __in_z LPCWSTR wzPackageId,
+ __in_z LPCWSTR wzPayloadId,
+ __in HRESULT hrStatus,
+ __in int nRecommendation
+ ) {
+ SetProgressState(hrStatus);
+ return __super::OnCacheVerifyComplete(wzPackageId, wzPayloadId, hrStatus, nRecommendation);
+ }
+
+
+ virtual STDMETHODIMP_(void) OnCacheComplete(__in HRESULT /*hrStatus*/) {
+ ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_PACKAGE_TEXT, L"");
+ SetState(PYBA_STATE_CACHED, S_OK); // we always return success here and let OnApplyComplete() deal with the error.
+ }
+
+
+ virtual STDMETHODIMP_(int) OnError(
+ __in BOOTSTRAPPER_ERROR_TYPE errorType,
+ __in LPCWSTR wzPackageId,
+ __in DWORD dwCode,
+ __in_z LPCWSTR wzError,
+ __in DWORD dwUIHint,
+ __in DWORD /*cData*/,
+ __in_ecount_z_opt(cData) LPCWSTR* /*rgwzData*/,
+ __in int nRecommendation
+ ) {
+ int nResult = nRecommendation;
+ LPWSTR sczError = nullptr;
+
+ if (BOOTSTRAPPER_DISPLAY_EMBEDDED == _command.display) {
+ HRESULT hr = _engine->SendEmbeddedError(dwCode, wzError, dwUIHint, &nResult);
+ if (FAILED(hr)) {
+ nResult = IDERROR;
+ }
+ } else if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) {
+ // If this is an authentication failure, let the engine try to handle it for us.
+ if (BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_SERVER == errorType || BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_PROXY == errorType) {
+ nResult = IDTRYAGAIN;
+ } else // show a generic error message box.
+ {
+ BalRetryErrorOccurred(wzPackageId, dwCode);
+
+ if (!_showingInternalUIThisPackage) {
+ // If no error message was provided, use the error code to try and get an error message.
+ if (!wzError || !*wzError || BOOTSTRAPPER_ERROR_TYPE_WINDOWS_INSTALLER != errorType) {
+ HRESULT hr = StrAllocFromError(&sczError, dwCode, nullptr);
+ if (FAILED(hr) || !sczError || !*sczError) {
+ StrAllocFormatted(&sczError, L"0x%x", dwCode);
+ }
+ }
+
+ nResult = ::MessageBoxW(_hWnd, sczError ? sczError : wzError, _theme->sczCaption, dwUIHint);
+ }
+ }
+
+ SetProgressState(HRESULT_FROM_WIN32(dwCode));
+ } else {
+ // just take note of the error code and let things continue.
+ BalRetryErrorOccurred(wzPackageId, dwCode);
+ }
+
+ ReleaseStr(sczError);
+ return nResult;
+ }
+
+
+ virtual STDMETHODIMP_(int) OnExecuteMsiMessage(
+ __in_z LPCWSTR wzPackageId,
+ __in INSTALLMESSAGE mt,
+ __in UINT uiFlags,
+ __in_z LPCWSTR wzMessage,
+ __in DWORD cData,
+ __in_ecount_z_opt(cData) LPCWSTR* rgwzData,
+ __in int nRecommendation
+ ) {
+#ifdef DEBUG
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnExecuteMsiMessage() - package: %ls, message: %ls", wzPackageId, wzMessage);
+#endif
+ if (BOOTSTRAPPER_DISPLAY_FULL == _command.display && (INSTALLMESSAGE_WARNING == mt || INSTALLMESSAGE_USER == mt)) {
+ int nResult = ::MessageBoxW(_hWnd, wzMessage, _theme->sczCaption, uiFlags);
+ return nResult;
+ }
+
+ if (INSTALLMESSAGE_ACTIONSTART == mt) {
+ ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, wzMessage);
+ }
+
+ return __super::OnExecuteMsiMessage(wzPackageId, mt, uiFlags, wzMessage, cData, rgwzData, nRecommendation);
+ }
+
+
+ virtual STDMETHODIMP_(int) OnProgress(__in DWORD dwProgressPercentage, __in DWORD dwOverallProgressPercentage) {
+ WCHAR wzProgress[5] = { };
+
+#ifdef DEBUG
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnProgress() - progress: %u%%, overall progress: %u%%", dwProgressPercentage, dwOverallProgressPercentage);
+#endif
+
+ ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallProgressPercentage);
+ ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_TEXT, wzProgress);
+
+ ThemeSetProgressControl(_theme, ID_OVERALL_PROGRESS_BAR, dwOverallProgressPercentage);
+ SetTaskbarButtonProgress(dwOverallProgressPercentage);
+
+ return __super::OnProgress(dwProgressPercentage, dwOverallProgressPercentage);
+ }
+
+
+ virtual STDMETHODIMP_(int) OnExecutePackageBegin(__in_z LPCWSTR wzPackageId, __in BOOL fExecute) {
+ LPWSTR sczFormattedString = nullptr;
+
+ _startedExecution = TRUE;
+
+ if (wzPackageId && *wzPackageId) {
+ BAL_INFO_PACKAGE* pPackage = nullptr;
+ BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage);
+
+ LPCWSTR wz = wzPackageId;
+ if (pPackage) {
+ LOC_STRING* pLocString = nullptr;
+
+ switch (pPackage->type) {
+ case BAL_INFO_PACKAGE_TYPE_BUNDLE_ADDON:
+ LocGetString(_wixLoc, L"#(loc.ExecuteAddonRelatedBundleMessage)", &pLocString);
+ break;
+
+ case BAL_INFO_PACKAGE_TYPE_BUNDLE_PATCH:
+ LocGetString(_wixLoc, L"#(loc.ExecutePatchRelatedBundleMessage)", &pLocString);
+ break;
+
+ case BAL_INFO_PACKAGE_TYPE_BUNDLE_UPGRADE:
+ LocGetString(_wixLoc, L"#(loc.ExecuteUpgradeRelatedBundleMessage)", &pLocString);
+ break;
+ }
+
+ if (pLocString) {
+ // If the wix developer is showing a hidden variable in the UI, then obviously they don't care about keeping it safe
+ // so don't go down the rabbit hole of making sure that this is securely freed.
+ BalFormatString(pLocString->wzText, &sczFormattedString);
+ }
+
+ wz = sczFormattedString ? sczFormattedString : pPackage->sczDisplayName ? pPackage->sczDisplayName : wzPackageId;
+ }
+
+ _showingInternalUIThisPackage = pPackage && pPackage->fDisplayInternalUI;
+
+ ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_PACKAGE_TEXT, wz);
+ ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, wz);
+ } else {
+ _showingInternalUIThisPackage = FALSE;
+ }
+
+ ReleaseStr(sczFormattedString);
+ return __super::OnExecutePackageBegin(wzPackageId, fExecute);
+ }
+
+
+ virtual int __stdcall OnExecuteProgress(
+ __in_z LPCWSTR wzPackageId,
+ __in DWORD dwProgressPercentage,
+ __in DWORD dwOverallProgressPercentage
+ ) {
+ WCHAR wzProgress[8] = { };
+
+#ifdef DEBUG
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnExecuteProgress() - package: %ls, progress: %u%%, overall progress: %u%%", wzPackageId, dwProgressPercentage, dwOverallProgressPercentage);
+#endif
+
+ ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallProgressPercentage);
+ ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_TEXT, wzProgress);
+
+ ThemeSetProgressControl(_theme, ID_EXECUTE_PROGRESS_BAR, dwOverallProgressPercentage);
+
+ _calculatedExecuteProgress = dwOverallProgressPercentage * (100 - PYBA_ACQUIRE_PERCENTAGE) / 100;
+ ThemeSetProgressControl(_theme, ID_OVERALL_CALCULATED_PROGRESS_BAR, _calculatedCacheProgress + _calculatedExecuteProgress);
+
+ SetTaskbarButtonProgress(_calculatedCacheProgress + _calculatedExecuteProgress);
+
+ return __super::OnExecuteProgress(wzPackageId, dwProgressPercentage, dwOverallProgressPercentage);
+ }
+
+
+ virtual STDMETHODIMP_(int) OnExecutePackageComplete(
+ __in_z LPCWSTR wzPackageId,
+ __in HRESULT hrExitCode,
+ __in BOOTSTRAPPER_APPLY_RESTART restart,
+ __in int nRecommendation
+ ) {
+ SetProgressState(hrExitCode);
+
+ if (_wcsnicmp(wzPackageId, L"path_", 5) == 0 && SUCCEEDED(hrExitCode)) {
+ SendMessageTimeoutW(
+ HWND_BROADCAST,
+ WM_SETTINGCHANGE,
+ 0,
+ reinterpret_cast<LPARAM>(L"Environment"),
+ SMTO_ABORTIFHUNG,
+ 1000,
+ nullptr
+ );
+ }
+
+ int nResult = __super::OnExecutePackageComplete(wzPackageId, hrExitCode, restart, nRecommendation);
+
+ return nResult;
+ }
+
+
+ virtual STDMETHODIMP_(void) OnExecuteComplete(__in HRESULT hrStatus) {
+ ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_PACKAGE_TEXT, L"");
+ ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, L"");
+ ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, L"");
+ ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, FALSE); // no more cancel.
+
+ SetState(PYBA_STATE_EXECUTED, S_OK); // we always return success here and let OnApplyComplete() deal with the error.
+ SetProgressState(hrStatus);
+ }
+
+
+ virtual STDMETHODIMP_(int) OnResolveSource(
+ __in_z LPCWSTR wzPackageOrContainerId,
+ __in_z_opt LPCWSTR wzPayloadId,
+ __in_z LPCWSTR wzLocalSource,
+ __in_z_opt LPCWSTR wzDownloadSource
+ ) {
+ int nResult = IDERROR; // assume we won't resolve source and that is unexpected.
+
+ if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) {
+ if (wzDownloadSource) {
+ nResult = IDDOWNLOAD;
+ } else {
+ // prompt to change the source location.
+ OPENFILENAMEW ofn = { };
+ WCHAR wzFile[MAX_PATH] = { };
+
+ ::StringCchCopyW(wzFile, countof(wzFile), wzLocalSource);
+
+ ofn.lStructSize = sizeof(ofn);
+ ofn.hwndOwner = _hWnd;
+ ofn.lpstrFile = wzFile;
+ ofn.nMaxFile = countof(wzFile);
+ ofn.lpstrFilter = L"All Files\0*.*\0";
+ ofn.nFilterIndex = 1;
+ ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
+ ofn.lpstrTitle = _theme->sczCaption;
+
+ if (::GetOpenFileNameW(&ofn)) {
+ HRESULT hr = _engine->SetLocalSource(wzPackageOrContainerId, wzPayloadId, ofn.lpstrFile);
+ nResult = SUCCEEDED(hr) ? IDRETRY : IDERROR;
+ } else {
+ nResult = IDCANCEL;
+ }
+ }
+ } else if (wzDownloadSource) {
+ // If doing a non-interactive install and download source is available, let's try downloading the package silently
+ nResult = IDDOWNLOAD;
+ }
+ // else there's nothing more we can do in non-interactive mode
+
+ return CheckCanceled() ? IDCANCEL : nResult;
+ }
+
+
+ virtual STDMETHODIMP_(int) OnApplyComplete(__in HRESULT hrStatus, __in BOOTSTRAPPER_APPLY_RESTART restart) {
+ _restartResult = restart; // remember the restart result so we return the correct error code no matter what the user chooses to do in the UI.
+
+ // If a restart was encountered and we are not suppressing restarts, then restart is required.
+ _restartRequired = (BOOTSTRAPPER_APPLY_RESTART_NONE != restart && BOOTSTRAPPER_RESTART_NEVER < _command.restart);
+ // If a restart is required and we're not displaying a UI or we are not supposed to prompt for restart then allow the restart.
+ _allowRestart = _restartRequired && (BOOTSTRAPPER_DISPLAY_FULL > _command.display || BOOTSTRAPPER_RESTART_PROMPT < _command.restart);
+
+ // If we are showing UI, wait a beat before moving to the final screen.
+ if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) {
+ ::Sleep(250);
+ }
+
+ SetState(PYBA_STATE_APPLIED, hrStatus);
+ SetTaskbarButtonProgress(100); // show full progress bar, green, yellow, or red
+
+ return IDNOACTION;
+ }
+
+ virtual STDMETHODIMP_(void) OnLaunchApprovedExeComplete(__in HRESULT hrStatus, __in DWORD /*processId*/) {
+ if (HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) == hrStatus) {
+ //try with ShelExec next time
+ OnClickLaunchButton();
+ } else {
+ ::PostMessageW(_hWnd, WM_CLOSE, 0, 0);
+ }
+ }
+
+
+private:
+ //
+ // UiThreadProc - entrypoint for UI thread.
+ //
+ static DWORD WINAPI UiThreadProc(__in LPVOID pvContext) {
+ HRESULT hr = S_OK;
+ PythonBootstrapperApplication* pThis = (PythonBootstrapperApplication*)pvContext;
+ BOOL comInitialized = FALSE;
+ BOOL ret = FALSE;
+ MSG msg = { };
+
+ // Initialize COM and theme.
+ hr = ::CoInitialize(nullptr);
+ BalExitOnFailure(hr, "Failed to initialize COM.");
+ comInitialized = TRUE;
+
+ hr = ThemeInitialize(pThis->_hModule);
+ BalExitOnFailure(hr, "Failed to initialize theme manager.");
+
+ hr = pThis->InitializeData();
+ BalExitOnFailure(hr, "Failed to initialize data in bootstrapper application.");
+
+ // Create main window.
+ pThis->InitializeTaskbarButton();
+ hr = pThis->CreateMainWindow();
+ BalExitOnFailure(hr, "Failed to create main window.");
+
+ if (FAILED(pThis->_hrFinal)) {
+ pThis->SetState(PYBA_STATE_FAILED, hr);
+ ::PostMessageW(pThis->_hWnd, WM_PYBA_SHOW_FAILURE, 0, 0);
+ } else {
+ // Okay, we're ready for packages now.
+ pThis->SetState(PYBA_STATE_INITIALIZED, hr);
+ ::PostMessageW(pThis->_hWnd, BOOTSTRAPPER_ACTION_HELP == pThis->_command.action ? WM_PYBA_SHOW_HELP : WM_PYBA_DETECT_PACKAGES, 0, 0);
+ }
+
+ // message pump
+ while (0 != (ret = ::GetMessageW(&msg, nullptr, 0, 0))) {
+ if (-1 == ret) {
+ hr = E_UNEXPECTED;
+ BalExitOnFailure(hr, "Unexpected return value from message pump.");
+ } else if (!ThemeHandleKeyboardMessage(pThis->_theme, msg.hwnd, &msg)) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ }
+
+ // Succeeded thus far, check to see if anything went wrong while actually
+ // executing changes.
+ if (FAILED(pThis->_hrFinal)) {
+ hr = pThis->_hrFinal;
+ } else if (pThis->CheckCanceled()) {
+ hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT);
+ }
+
+ LExit:
+ // destroy main window
+ pThis->DestroyMainWindow();
+
+ // initiate engine shutdown
+ DWORD dwQuit = HRESULT_CODE(hr);
+ if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == pThis->_restartResult) {
+ dwQuit = ERROR_SUCCESS_REBOOT_INITIATED;
+ } else if (BOOTSTRAPPER_APPLY_RESTART_REQUIRED == pThis->_restartResult) {
+ dwQuit = ERROR_SUCCESS_REBOOT_REQUIRED;
+ }
+ pThis->_engine->Quit(dwQuit);
+
+ ReleaseTheme(pThis->_theme);
+ ThemeUninitialize();
+
+ // uninitialize COM
+ if (comInitialized) {
+ ::CoUninitialize();
+ }
+
+ return hr;
+ }
+
+
+ //
+ // InitializeData - initializes all the package information.
+ //
+ HRESULT InitializeData() {
+ HRESULT hr = S_OK;
+ LPWSTR sczModulePath = nullptr;
+ IXMLDOMDocument *pixdManifest = nullptr;
+
+ hr = BalManifestLoad(_hModule, &pixdManifest);
+ BalExitOnFailure(hr, "Failed to load bootstrapper application manifest.");
+
+ hr = ParseOverridableVariablesFromXml(pixdManifest);
+ BalExitOnFailure(hr, "Failed to read overridable variables.");
+
+ hr = ProcessCommandLine(&_language);
+ ExitOnFailure(hr, "Unknown commandline parameters.");
+
+ hr = PathRelativeToModule(&sczModulePath, nullptr, _hModule);
+ BalExitOnFailure(hr, "Failed to get module path.");
+
+ hr = LoadLocalization(sczModulePath, _language);
+ ExitOnFailure(hr, "Failed to load localization.");
+
+ hr = LoadTheme(sczModulePath, _language);
+ ExitOnFailure(hr, "Failed to load theme.");
+
+ hr = BalInfoParseFromXml(&_bundle, pixdManifest);
+ BalExitOnFailure(hr, "Failed to load bundle information.");
+
+ hr = BalConditionsParseFromXml(&_conditions, pixdManifest, _wixLoc);
+ BalExitOnFailure(hr, "Failed to load conditions from XML.");
+
+ hr = LoadBootstrapperBAFunctions();
+ BalExitOnFailure(hr, "Failed to load bootstrapper functions.");
+ hr = UpdateUIStrings(_command.action);
+ BalExitOnFailure(hr, "Failed to load UI strings.");
+
+ GetBundleFileVersion();
+ // don't fail if we couldn't get the version info; best-effort only
+ LExit:
+ ReleaseObject(pixdManifest);
+ ReleaseStr(sczModulePath);
+
+ return hr;
+ }
+
+
+ //
+ // ProcessCommandLine - process the provided command line arguments.
+ //
+ HRESULT ProcessCommandLine(__inout LPWSTR* psczLanguage) {
+ HRESULT hr = S_OK;
+ int argc = 0;
+ LPWSTR* argv = nullptr;
+ LPWSTR sczVariableName = nullptr;
+ LPWSTR sczVariableValue = nullptr;
+
+ if (_command.wzCommandLine && *_command.wzCommandLine) {
+ argv = ::CommandLineToArgvW(_command.wzCommandLine, &argc);
+ ExitOnNullWithLastError(argv, hr, "Failed to get command line.");
+
+ for (int i = 0; i < argc; ++i) {
+ if (argv[i][0] == L'-' || argv[i][0] == L'/') {
+ if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"lang", -1)) {
+ if (i + 1 >= argc) {
+ hr = E_INVALIDARG;
+ BalExitOnFailure(hr, "Must specify a language.");
+ }
+
+ ++i;
+
+ hr = StrAllocString(psczLanguage, &argv[i][0], 0);
+ BalExitOnFailure(hr, "Failed to copy language.");
+ }
+ } else if (_overridableVariables) {
+ int value;
+ const wchar_t* pwc = wcschr(argv[i], L'=');
+ if (pwc) {
+ hr = StrAllocString(&sczVariableName, argv[i], pwc - argv[i]);
+ BalExitOnFailure(hr, "Failed to copy variable name.");
+
+ hr = DictKeyExists(_overridableVariables, sczVariableName);
+ if (E_NOTFOUND == hr) {
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Ignoring attempt to set non-overridable variable: '%ls'.", sczVariableName);
+ hr = S_OK;
+ continue;
+ }
+ ExitOnFailure(hr, "Failed to check the dictionary of overridable variables.");
+
+ hr = StrAllocString(&sczVariableValue, ++pwc, 0);
+ BalExitOnFailure(hr, "Failed to copy variable value.");
+
+ if (::StrToIntEx(sczVariableValue, STIF_DEFAULT, &value)) {
+ hr = _engine->SetVariableNumeric(sczVariableName, value);
+ } else {
+ hr = _engine->SetVariableString(sczVariableName, sczVariableValue);
+ }
+ BalExitOnFailure(hr, "Failed to set variable.");
+ } else {
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Ignoring unknown argument: %ls", argv[i]);
+ }
+ }
+ }
+ }
+
+ LExit:
+ if (argv) {
+ ::LocalFree(argv);
+ }
+
+ ReleaseStr(sczVariableName);
+ ReleaseStr(sczVariableValue);
+
+ return hr;
+ }
+
+ HRESULT LoadLocalization(__in_z LPCWSTR wzModulePath, __in_z_opt LPCWSTR wzLanguage) {
+ HRESULT hr = S_OK;
+ LPWSTR sczLocPath = nullptr;
+ LPCWSTR wzLocFileName = L"Default.wxl";
+
+ hr = LocProbeForFile(wzModulePath, wzLocFileName, wzLanguage, &sczLocPath);
+ BalExitOnFailure2(hr, "Failed to probe for loc file: %ls in path: %ls", wzLocFileName, wzModulePath);
+
+ hr = LocLoadFromFile(sczLocPath, &_wixLoc);
+ BalExitOnFailure1(hr, "Failed to load loc file from path: %ls", sczLocPath);
+
+ if (WIX_LOCALIZATION_LANGUAGE_NOT_SET != _wixLoc->dwLangId) {
+ ::SetThreadLocale(_wixLoc->dwLangId);
+ }
+
+ hr = StrAllocString(&_confirmCloseMessage, L"#(loc.ConfirmCancelMessage)", 0);
+ ExitOnFailure(hr, "Failed to initialize confirm message loc identifier.");
+
+ hr = LocLocalizeString(_wixLoc, &_confirmCloseMessage);
+ BalExitOnFailure1(hr, "Failed to localize confirm close message: %ls", _confirmCloseMessage);
+
+ LExit:
+ ReleaseStr(sczLocPath);
+
+ return hr;
+ }
+
+
+ HRESULT LoadTheme(__in_z LPCWSTR wzModulePath, __in_z_opt LPCWSTR wzLanguage) {
+ HRESULT hr = S_OK;
+ LPWSTR sczThemePath = nullptr;
+ LPCWSTR wzThemeFileName = L"Default.thm";
+ LPWSTR sczCaption = nullptr;
+
+ hr = LocProbeForFile(wzModulePath, wzThemeFileName, wzLanguage, &sczThemePath);
+ BalExitOnFailure2(hr, "Failed to probe for theme file: %ls in path: %ls", wzThemeFileName, wzModulePath);
+
+ hr = ThemeLoadFromFile(sczThemePath, &_theme);
+ BalExitOnFailure1(hr, "Failed to load theme from path: %ls", sczThemePath);
+
+ hr = ThemeLocalize(_theme, _wixLoc);
+ BalExitOnFailure1(hr, "Failed to localize theme: %ls", sczThemePath);
+
+ // Update the caption if there are any formatted strings in it.
+ // If the wix developer is showing a hidden variable in the UI, then
+ // obviously they don't care about keeping it safe so don't go down the
+ // rabbit hole of making sure that this is securely freed.
+ hr = BalFormatString(_theme->sczCaption, &sczCaption);
+ if (SUCCEEDED(hr)) {
+ ThemeUpdateCaption(_theme, sczCaption);
+ }
+
+ LExit:
+ ReleaseStr(sczCaption);
+ ReleaseStr(sczThemePath);
+
+ return hr;
+ }
+
+
+ HRESULT ParseOverridableVariablesFromXml(__in IXMLDOMDocument* pixdManifest) {
+ HRESULT hr = S_OK;
+ IXMLDOMNode* pNode = nullptr;
+ IXMLDOMNodeList* pNodes = nullptr;
+ DWORD cNodes = 0;
+ LPWSTR scz = nullptr;
+ BOOL hidden = FALSE;
+
+ // get the list of variables users can override on the command line
+ hr = XmlSelectNodes(pixdManifest, L"/BootstrapperApplicationData/WixStdbaOverridableVariable", &pNodes);
+ if (S_FALSE == hr) {
+ ExitFunction1(hr = S_OK);
+ }
+ ExitOnFailure(hr, "Failed to select overridable variable nodes.");
+
+ hr = pNodes->get_length((long*)&cNodes);
+ ExitOnFailure(hr, "Failed to get overridable variable node count.");
+
+ if (cNodes) {
+ hr = DictCreateStringList(&_overridableVariables, 32, DICT_FLAG_NONE);
+ ExitOnFailure(hr, "Failed to create the string dictionary.");
+
+ for (DWORD i = 0; i < cNodes; ++i) {
+ hr = XmlNextElement(pNodes, &pNode, nullptr);
+ ExitOnFailure(hr, "Failed to get next node.");
+
+ // @Name
+ hr = XmlGetAttributeEx(pNode, L"Name", &scz);
+ ExitOnFailure(hr, "Failed to get @Name.");
+
+ hr = XmlGetYesNoAttribute(pNode, L"Hidden", &hidden);
+
+ if (!hidden) {
+ hr = DictAddKey(_overridableVariables, scz);
+ ExitOnFailure1(hr, "Failed to add \"%ls\" to the string dictionary.", scz);
+ }
+
+ // prepare next iteration
+ ReleaseNullObject(pNode);
+ }
+ }
+
+ LExit:
+ ReleaseObject(pNode);
+ ReleaseObject(pNodes);
+ ReleaseStr(scz);
+ return hr;
+ }
+
+
+ //
+ // Get the file version of the bootstrapper and record in bootstrapper log file
+ //
+ HRESULT GetBundleFileVersion() {
+ HRESULT hr = S_OK;
+ ULARGE_INTEGER uliVersion = { };
+ LPWSTR sczCurrentPath = nullptr;
+
+ hr = PathForCurrentProcess(&sczCurrentPath, nullptr);
+ BalExitOnFailure(hr, "Failed to get bundle path.");
+
+ hr = FileVersion(sczCurrentPath, &uliVersion.HighPart, &uliVersion.LowPart);
+ BalExitOnFailure(hr, "Failed to get bundle file version.");
+
+ hr = _engine->SetVariableVersion(PYBA_VARIABLE_BUNDLE_FILE_VERSION, uliVersion.QuadPart);
+ BalExitOnFailure(hr, "Failed to set WixBundleFileVersion variable.");
+
+ LExit:
+ ReleaseStr(sczCurrentPath);
+
+ return hr;
+ }
+
+
+ //
+ // CreateMainWindow - creates the main install window.
+ //
+ HRESULT CreateMainWindow() {
+ HRESULT hr = S_OK;
+ HICON hIcon = reinterpret_cast<HICON>(_theme->hIcon);
+ WNDCLASSW wc = { };
+ DWORD dwWindowStyle = 0;
+ int x = CW_USEDEFAULT;
+ int y = CW_USEDEFAULT;
+ POINT ptCursor = { };
+ HMONITOR hMonitor = nullptr;
+ MONITORINFO mi = { };
+
+ // If the theme did not provide an icon, try using the icon from the bundle engine.
+ if (!hIcon) {
+ HMODULE hBootstrapperEngine = ::GetModuleHandleW(nullptr);
+ if (hBootstrapperEngine) {
+ hIcon = ::LoadIconW(hBootstrapperEngine, MAKEINTRESOURCEW(1));
+ }
+ }
+
+ // Register the window class and create the window.
+ wc.lpfnWndProc = PythonBootstrapperApplication::WndProc;
+ wc.hInstance = _hModule;
+ wc.hIcon = hIcon;
+ wc.hCursor = ::LoadCursorW(nullptr, (LPCWSTR)IDC_ARROW);
+ wc.hbrBackground = _theme->rgFonts[_theme->dwFontId].hBackground;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = PYBA_WINDOW_CLASS;
+ if (!::RegisterClassW(&wc)) {
+ ExitWithLastError(hr, "Failed to register window.");
+ }
+
+ _registered = TRUE;
+
+ // Calculate the window style based on the theme style and command display value.
+ dwWindowStyle = _theme->dwStyle;
+ if (BOOTSTRAPPER_DISPLAY_NONE >= _command.display) {
+ dwWindowStyle &= ~WS_VISIBLE;
+ }
+
+ // Don't show the window if there is a splash screen (it will be made visible when the splash screen is hidden)
+ if (::IsWindow(_command.hwndSplashScreen)) {
+ dwWindowStyle &= ~WS_VISIBLE;
+ }
+
+ // Center the window on the monitor with the mouse.
+ if (::GetCursorPos(&ptCursor)) {
+ hMonitor = ::MonitorFromPoint(ptCursor, MONITOR_DEFAULTTONEAREST);
+ if (hMonitor) {
+ mi.cbSize = sizeof(mi);
+ if (::GetMonitorInfoW(hMonitor, &mi)) {
+ x = mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - _theme->nWidth) / 2;
+ y = mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - _theme->nHeight) / 2;
+ }
+ }
+ }
+
+ _hWnd = ::CreateWindowExW(
+ 0,
+ wc.lpszClassName,
+ _theme->sczCaption,
+ dwWindowStyle,
+ x,
+ y,
+ _theme->nWidth,
+ _theme->nHeight,
+ HWND_DESKTOP,
+ nullptr,
+ _hModule,
+ this
+ );
+ ExitOnNullWithLastError(_hWnd, hr, "Failed to create window.");
+
+ hr = S_OK;
+
+ LExit:
+ return hr;
+ }
+
+
+ //
+ // InitializeTaskbarButton - initializes taskbar button for progress.
+ //
+ void InitializeTaskbarButton() {
+ HRESULT hr = S_OK;
+
+ hr = ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), reinterpret_cast<LPVOID*>(&_taskbarList));
+ if (REGDB_E_CLASSNOTREG == hr) {
+ // not supported before Windows 7
+ ExitFunction1(hr = S_OK);
+ }
+ BalExitOnFailure(hr, "Failed to create ITaskbarList3. Continuing.");
+
+ _taskbarButtonCreatedMessage = ::RegisterWindowMessageW(L"TaskbarButtonCreated");
+ BalExitOnNullWithLastError(_taskbarButtonCreatedMessage, hr, "Failed to get TaskbarButtonCreated message. Continuing.");
+
+ LExit:
+ return;
+ }
+
+ //
+ // DestroyMainWindow - clean up all the window registration.
+ //
+ void DestroyMainWindow() {
+ if (::IsWindow(_hWnd)) {
+ ::DestroyWindow(_hWnd);
+ _hWnd = nullptr;
+ _taskbarButtonOK = FALSE;
+ }
+
+ if (_registered) {
+ ::UnregisterClassW(PYBA_WINDOW_CLASS, _hModule);
+ _registered = FALSE;
+ }
+ }
+
+
+ //
+ // WndProc - standard windows message handler.
+ //
+ static LRESULT CALLBACK WndProc(
+ __in HWND hWnd,
+ __in UINT uMsg,
+ __in WPARAM wParam,
+ __in LPARAM lParam
+ ) {
+#pragma warning(suppress:4312)
+ auto pBA = reinterpret_cast<PythonBootstrapperApplication*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
+
+ switch (uMsg) {
+ case WM_NCCREATE: {
+ LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
+ pBA = reinterpret_cast<PythonBootstrapperApplication*>(lpcs->lpCreateParams);
+#pragma warning(suppress:4244)
+ ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pBA));
+ break;
+ }
+
+ case WM_NCDESTROY: {
+ LRESULT lres = ThemeDefWindowProc(pBA ? pBA->_theme : nullptr, hWnd, uMsg, wParam, lParam);
+ ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
+ return lres;
+ }
+
+ case WM_CREATE:
+ if (!pBA->OnCreate(hWnd)) {
+ return -1;
+ }
+ break;
+
+ case WM_QUERYENDSESSION:
+ return IDCANCEL != pBA->OnSystemShutdown(static_cast<DWORD>(lParam), IDCANCEL);
+
+ case WM_CLOSE:
+ // If the user chose not to close, do *not* let the default window proc handle the message.
+ if (!pBA->OnClose()) {
+ return 0;
+ }
+ break;
+
+ case WM_DESTROY:
+ ::PostQuitMessage(0);
+ break;
+
+ case WM_PAINT: __fallthrough;
+ case WM_ERASEBKGND:
+ if (pBA && pBA->_suppressPaint) {
+ return TRUE;
+ }
+ break;
+
+ case WM_PYBA_SHOW_HELP:
+ pBA->OnShowHelp();
+ return 0;
+
+ case WM_PYBA_DETECT_PACKAGES:
+ pBA->OnDetect();
+ return 0;
+
+ case WM_PYBA_PLAN_PACKAGES:
+ pBA->OnPlan(static_cast<BOOTSTRAPPER_ACTION>(lParam));
+ return 0;
+
+ case WM_PYBA_APPLY_PACKAGES:
+ pBA->OnApply();
+ return 0;
+
+ case WM_PYBA_CHANGE_STATE:
+ pBA->OnChangeState(static_cast<PYBA_STATE>(lParam));
+ return 0;
+
+ case WM_PYBA_SHOW_FAILURE:
+ pBA->OnShowFailure();
+ return 0;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ // Customize commands
+ // Success/failure commands
+ case ID_LAUNCH_BUTTON:
+ pBA->OnClickLaunchButton();
+ return 0;
+
+ case ID_SUCCESS_RESTART_BUTTON: __fallthrough;
+ case ID_FAILURE_RESTART_BUTTON:
+ pBA->OnClickRestartButton();
+ return 0;
+
+ case IDCANCEL: __fallthrough;
+ case ID_INSTALL_CANCEL_BUTTON: __fallthrough;
+ case ID_CUSTOM1_CANCEL_BUTTON: __fallthrough;
+ case ID_CUSTOM2_CANCEL_BUTTON: __fallthrough;
+ case ID_MODIFY_CANCEL_BUTTON: __fallthrough;
+ case ID_PROGRESS_CANCEL_BUTTON: __fallthrough;
+ case ID_SUCCESS_CANCEL_BUTTON: __fallthrough;
+ case ID_FAILURE_CANCEL_BUTTON: __fallthrough;
+ case ID_CLOSE_BUTTON:
+ pBA->OnCommand(ID_CLOSE_BUTTON);
+ return 0;
+
+ default:
+ pBA->OnCommand((CONTROL_ID)LOWORD(wParam));
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (lParam) {
+ LPNMHDR pnmhdr = reinterpret_cast<LPNMHDR>(lParam);
+ switch (pnmhdr->code) {
+ case NM_CLICK: __fallthrough;
+ case NM_RETURN:
+ switch (static_cast<DWORD>(pnmhdr->idFrom)) {
+ case ID_FAILURE_LOGFILE_LINK:
+ pBA->OnClickLogFileLink();
+ return 1;
+ }
+ }
+ }
+ break;
+
+ case WM_CTLCOLORBTN:
+ if (pBA) {
+ HWND button = (HWND)lParam;
+ DWORD style = GetWindowLong(button, GWL_STYLE) & BS_TYPEMASK;
+ if (style == BS_COMMANDLINK || style == BS_DEFCOMMANDLINK) {
+ return (LRESULT)pBA->_theme->rgFonts[pBA->_theme->dwFontId].hBackground;
+ }
+ }
+ break;
+ }
+
+ if (pBA && pBA->_taskbarList && uMsg == pBA->_taskbarButtonCreatedMessage) {
+ pBA->_taskbarButtonOK = TRUE;
+ return 0;
+ }
+
+ return ThemeDefWindowProc(pBA ? pBA->_theme : nullptr, hWnd, uMsg, wParam, lParam);
+ }
+
+ //
+ // OnCreate - finishes loading the theme.
+ //
+ BOOL OnCreate(__in HWND hWnd) {
+ HRESULT hr = S_OK;
+
+ hr = ThemeLoadControls(_theme, hWnd, CONTROL_ID_NAMES, countof(CONTROL_ID_NAMES));
+ BalExitOnFailure(hr, "Failed to load theme controls.");
+
+ C_ASSERT(COUNT_PAGE == countof(PAGE_NAMES));
+ C_ASSERT(countof(_pageIds) == countof(PAGE_NAMES));
+
+ ThemeGetPageIds(_theme, PAGE_NAMES, _pageIds, countof(_pageIds));
+
+ // Initialize the text on all "application" (non-page) controls.
+ for (DWORD i = 0; i < _theme->cControls; ++i) {
+ THEME_CONTROL* pControl = _theme->rgControls + i;
+ LPWSTR text = nullptr;
+ LPWSTR name = nullptr;
+ LOC_STRING *locText = nullptr;
+
+ // If a command link has a note, then add it.
+ if ((pControl->dwStyle & BS_TYPEMASK) == BS_COMMANDLINK ||
+ (pControl->dwStyle & BS_TYPEMASK) == BS_DEFCOMMANDLINK) {
+ hr = StrAllocFormatted(&name, L"#(loc.%lsNote)", pControl->sczName);
+ if (SUCCEEDED(hr)) {
+ hr = LocGetString(_wixLoc, name, &locText);
+ ReleaseStr(name);
+ if (SUCCEEDED(hr) && locText && locText->wzText && locText->wzText[0]) {
+ hr = BalFormatString(locText->wzText, &text);
+ if (SUCCEEDED(hr) && text && text[0]) {
+ ThemeSendControlMessage(_theme, pControl->wId, BCM_SETNOTE, 0, (LPARAM)text);
+ ReleaseStr(text);
+ text = nullptr;
+ }
+ }
+ }
+ hr = S_OK;
+ }
+
+ if (!pControl->wPageId && pControl->sczText && *pControl->sczText) {
+ HRESULT hrFormat;
+
+ // If the wix developer is showing a hidden variable in the UI,
+ // then obviously they don't care about keeping it safe so don't
+ // go down the rabbit hole of making sure that this is securely
+ // freed.
+ hrFormat = BalFormatString(pControl->sczText, &text);
+ if (SUCCEEDED(hrFormat)) {
+ ThemeSetTextControl(_theme, pControl->wId, text);
+ ReleaseStr(text);
+ }
+ }
+ }
+
+ LExit:
+ return SUCCEEDED(hr);
+ }
+
+
+ //
+ // OnShowFailure - display the failure page.
+ //
+ void OnShowFailure() {
+ SetState(PYBA_STATE_FAILED, S_OK);
+
+ // If the UI should be visible, display it now and hide the splash screen
+ if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) {
+ ::ShowWindow(_theme->hwndParent, SW_SHOW);
+ }
+
+ _engine->CloseSplashScreen();
+
+ return;
+ }
+
+
+ //
+ // OnShowHelp - display the help page.
+ //
+ void OnShowHelp() {
+ SetState(PYBA_STATE_HELP, S_OK);
+
+ // If the UI should be visible, display it now and hide the splash screen
+ if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) {
+ ::ShowWindow(_theme->hwndParent, SW_SHOW);
+ }
+
+ _engine->CloseSplashScreen();
+
+ return;
+ }
+
+
+ //
+ // OnDetect - start the processing of packages.
+ //
+ void OnDetect() {
+ HRESULT hr = S_OK;
+
+ if (_baFunction) {
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running detect BA function");
+ hr = _baFunction->OnDetect();
+ BalExitOnFailure(hr, "Failed calling detect BA function.");
+ }
+
+ SetState(PYBA_STATE_DETECTING, hr);
+
+ // If the UI should be visible, display it now and hide the splash screen
+ if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) {
+ ::ShowWindow(_theme->hwndParent, SW_SHOW);
+ }
+
+ _engine->CloseSplashScreen();
+
+ // Tell the core we're ready for the packages to be processed now.
+ hr = _engine->Detect();
+ BalExitOnFailure(hr, "Failed to start detecting chain.");
+
+ LExit:
+ if (FAILED(hr)) {
+ SetState(PYBA_STATE_DETECTING, hr);
+ }
+
+ return;
+ }
+ HRESULT UpdateUIStrings(__in BOOTSTRAPPER_ACTION action) {
+ HRESULT hr = S_OK;
+ LPCWSTR likeInstalling = nullptr;
+ LPCWSTR likeInstallation = nullptr;
+ switch (action) {
+ case BOOTSTRAPPER_ACTION_INSTALL:
+ likeInstalling = L"Installing";
+ likeInstallation = L"Installation";
+ break;
+ case BOOTSTRAPPER_ACTION_MODIFY:
+ // For modify, we actually want to pass INSTALL
+ action = BOOTSTRAPPER_ACTION_INSTALL;
+ likeInstalling = L"Modifying";
+ likeInstallation = L"Modification";
+ break;
+ case BOOTSTRAPPER_ACTION_REPAIR:
+ likeInstalling = L"Repairing";
+ likeInstallation = L"Repair";
+ break;
+ case BOOTSTRAPPER_ACTION_UNINSTALL:
+ likeInstalling = L"Uninstalling";
+ likeInstallation = L"Uninstallation";
+ break;
+ }
+
+ if (likeInstalling) {
+ LPWSTR locName = nullptr;
+ LOC_STRING *locText = nullptr;
+ hr = StrAllocFormatted(&locName, L"#(loc.%ls)", likeInstalling);
+ if (SUCCEEDED(hr)) {
+ hr = LocGetString(_wixLoc, locName, &locText);
+ ReleaseStr(locName);
+ }
+ _engine->SetVariableString(
+ L"ActionLikeInstalling",
+ SUCCEEDED(hr) && locText ? locText->wzText : likeInstalling
+ );
+ }
+
+ if (likeInstallation) {
+ LPWSTR locName = nullptr;
+ LOC_STRING *locText = nullptr;
+ hr = StrAllocFormatted(&locName, L"#(loc.%ls)", likeInstallation);
+ if (SUCCEEDED(hr)) {
+ hr = LocGetString(_wixLoc, locName, &locText);
+ ReleaseStr(locName);
+ }
+ _engine->SetVariableString(
+ L"ActionLikeInstallation",
+ SUCCEEDED(hr) && locText ? locText->wzText : likeInstallation
+ );
+ }
+ return hr;
+ }
+
+ //
+ // OnPlan - plan the detected changes.
+ //
+ void OnPlan(__in BOOTSTRAPPER_ACTION action) {
+ HRESULT hr = S_OK;
+
+ _plannedAction = action;
+
+ hr = UpdateUIStrings(action);
+ BalExitOnFailure(hr, "Failed to update strings");
+
+ // If we are going to apply a downgrade, bail.
+ if (_downgrading && BOOTSTRAPPER_ACTION_UNINSTALL < action) {
+ if (_suppressDowngradeFailure) {
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "A newer version of this product is installed but downgrade failure has been suppressed; continuing...");
+ } else {
+ hr = HRESULT_FROM_WIN32(ERROR_PRODUCT_VERSION);
+ BalExitOnFailure(hr, "Cannot install a product when a newer version is installed.");
+ }
+ }
+
+ SetState(PYBA_STATE_PLANNING, hr);
+
+ if (_baFunction) {
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running plan BA function");
+ _baFunction->OnPlan();
+ }
+
+ hr = _engine->Plan(action);
+ BalExitOnFailure(hr, "Failed to start planning packages.");
+
+ LExit:
+ if (FAILED(hr)) {
+ SetState(PYBA_STATE_PLANNING, hr);
+ }
+
+ return;
+ }
+
+
+ //
+ // OnApply - apply the packages.
+ //
+ void OnApply() {
+ HRESULT hr = S_OK;
+
+ SetState(PYBA_STATE_APPLYING, hr);
+ SetProgressState(hr);
+ SetTaskbarButtonProgress(0);
+
+ hr = _engine->Apply(_hWnd);
+ BalExitOnFailure(hr, "Failed to start applying packages.");
+
+ ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, TRUE); // ensure the cancel button is enabled before starting.
+
+ LExit:
+ if (FAILED(hr)) {
+ SetState(PYBA_STATE_APPLYING, hr);
+ }
+
+ return;
+ }
+
+
+ //
+ // OnChangeState - change state.
+ //
+ void OnChangeState(__in PYBA_STATE state) {
+ LPWSTR unformattedText = nullptr;
+
+ _state = state;
+
+ // If our install is at the end (success or failure) and we're not showing full UI
+ // then exit (prompt for restart if required).
+ if ((PYBA_STATE_APPLIED <= _state && BOOTSTRAPPER_DISPLAY_FULL > _command.display)) {
+ // If a restart was required but we were not automatically allowed to
+ // accept the reboot then do the prompt.
+ if (_restartRequired && !_allowRestart) {
+ StrAllocFromError(&unformattedText, HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED), nullptr);
+
+ _allowRestart = IDOK == ::MessageBoxW(
+ _hWnd,
+ unformattedText ? unformattedText : L"The requested operation is successful. Changes will not be effective until the system is rebooted.",
+ _theme->sczCaption,
+ MB_ICONEXCLAMATION | MB_OKCANCEL
+ );
+ }
+
+ // Quietly exit.
+ ::PostMessageW(_hWnd, WM_CLOSE, 0, 0);
+ } else { // try to change the pages.
+ DWORD newPageId = 0;
+ DeterminePageId(_state, &newPageId);
+
+ if (_visiblePageId != newPageId) {
+ ShowPage(newPageId);
+ }
+ }
+
+ ReleaseStr(unformattedText);
+ }
+
+ //
+ // Called before showing a page to handle all controls.
+ //
+ void ProcessPageControls(THEME_PAGE *pPage) {
+ if (!pPage) {
+ return;
+ }
+
+ for (DWORD i = 0; i < pPage->cControlIndices; ++i) {
+ THEME_CONTROL* pControl = _theme->rgControls + pPage->rgdwControlIndices[i];
+
+ // If this is a named control, try to set its default state.
+ if (pControl->sczName && *pControl->sczName) {
+ // If this is a checkable control, try to set its default state
+ // to the state of a matching named Burn variable.
+ if (IsCheckable(pControl)) {
+ LONGLONG llValue = 0;
+ HRESULT hr = BalGetNumericVariable(pControl->sczName, &llValue);
+
+ // If the control value isn't set then disable it.
+ if (!SUCCEEDED(hr)) {
+ ThemeControlEnable(_theme, pControl->wId, FALSE);
+ } else {
+ ThemeSendControlMessage(
+ _theme,
+ pControl->wId,
+ BM_SETCHECK,
+ SUCCEEDED(hr) && llValue ? BST_CHECKED : BST_UNCHECKED,
+ 0
+ );
+ }
+ }
+
+ // Hide or disable controls based on the control name with 'State' appended
+ LPWSTR controlName = nullptr;
+ HRESULT hr = StrAllocFormatted(&controlName, L"%lsState", pControl->sczName);
+ if (SUCCEEDED(hr)) {
+ LPWSTR controlState = nullptr;
+ hr = BalGetStringVariable(controlName, &controlState);
+ if (SUCCEEDED(hr) && controlState && *controlState) {
+ if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, controlState, -1, L"disable", -1)) {
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Disable control %ls", pControl->sczName);
+ ThemeControlEnable(_theme, pControl->wId, FALSE);
+ } else if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, controlState, -1, L"hide", -1)) {
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Hide control %ls", pControl->sczName);
+ // TODO: This doesn't work
+ ThemeShowControl(_theme, pControl->wId, SW_HIDE);
+ }
+ }
+ StrFree(controlState);
+ }
+ StrFree(controlName);
+ }
+
+ // Format the text in each of the new page's controls
+ if (pControl->sczText && *pControl->sczText) {
+ // If the wix developer is showing a hidden variable
+ // in the UI, then obviously they don't care about
+ // keeping it safe so don't go down the rabbit hole
+ // of making sure that this is securely freed.
+ LPWSTR text = nullptr;
+ HRESULT hr = BalFormatString(pControl->sczText, &text);
+ if (SUCCEEDED(hr)) {
+ ThemeSetTextControl(_theme, pControl->wId, text);
+ }
+ }
+ }
+ }
+
+ //
+ // OnClose - called when the window is trying to be closed.
+ //
+ BOOL OnClose() {
+ BOOL close = FALSE;
+
+ // If we've already succeeded or failed or showing the help page, just close (prompts are annoying if the bootstrapper is done).
+ if (PYBA_STATE_APPLIED <= _state || PYBA_STATE_HELP == _state) {
+ close = TRUE;
+ } else {
+ // prompt the user or force the cancel if there is no UI.
+ close = PromptCancel(
+ _hWnd,
+ BOOTSTRAPPER_DISPLAY_FULL != _command.display,
+ _confirmCloseMessage ? _confirmCloseMessage : L"Are you sure you want to cancel?",
+ _theme->sczCaption
+ );
+ }
+
+ // If we're doing progress then we never close, we just cancel to let rollback occur.
+ if (PYBA_STATE_APPLYING <= _state && PYBA_STATE_APPLIED > _state) {
+ // If we canceled disable cancel button since clicking it again is silly.
+ if (close) {
+ ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, FALSE);
+ }
+
+ close = FALSE;
+ }
+
+ return close;
+ }
+
+ //
+ // OnClickCloseButton - close the application.
+ //
+ void OnClickCloseButton() {
+ ::SendMessageW(_hWnd, WM_CLOSE, 0, 0);
+ }
+
+
+ //
+ // OnClickLaunchButton - launch the app from the success page.
+ //
+ void OnClickLaunchButton() {
+ HRESULT hr = S_OK;
+ LPWSTR sczUnformattedLaunchTarget = nullptr;
+ LPWSTR sczLaunchTarget = nullptr;
+ LPWSTR sczLaunchTargetElevatedId = nullptr;
+ LPWSTR sczUnformattedArguments = nullptr;
+ LPWSTR sczArguments = nullptr;
+ int nCmdShow = SW_SHOWNORMAL;
+
+ hr = BalGetStringVariable(PYBA_VARIABLE_LAUNCH_TARGET_PATH, &sczUnformattedLaunchTarget);
+ BalExitOnFailure1(hr, "Failed to get launch target variable '%ls'.", PYBA_VARIABLE_LAUNCH_TARGET_PATH);
+
+ hr = BalFormatString(sczUnformattedLaunchTarget, &sczLaunchTarget);
+ BalExitOnFailure1(hr, "Failed to format launch target variable: %ls", sczUnformattedLaunchTarget);
+
+ if (BalStringVariableExists(PYBA_VARIABLE_LAUNCH_TARGET_ELEVATED_ID)) {
+ hr = BalGetStringVariable(PYBA_VARIABLE_LAUNCH_TARGET_ELEVATED_ID, &sczLaunchTargetElevatedId);
+ BalExitOnFailure1(hr, "Failed to get launch target elevated id '%ls'.", PYBA_VARIABLE_LAUNCH_TARGET_ELEVATED_ID);
+ }
+
+ if (BalStringVariableExists(PYBA_VARIABLE_LAUNCH_ARGUMENTS)) {
+ hr = BalGetStringVariable(PYBA_VARIABLE_LAUNCH_ARGUMENTS, &sczUnformattedArguments);
+ BalExitOnFailure1(hr, "Failed to get launch arguments '%ls'.", PYBA_VARIABLE_LAUNCH_ARGUMENTS);
+ }
+
+ if (BalStringVariableExists(PYBA_VARIABLE_LAUNCH_HIDDEN)) {
+ nCmdShow = SW_HIDE;
+ }
+
+ if (sczLaunchTargetElevatedId && !_triedToLaunchElevated) {
+ _triedToLaunchElevated = TRUE;
+ hr = _engine->LaunchApprovedExe(_hWnd, sczLaunchTargetElevatedId, sczUnformattedArguments, 0);
+ if (FAILED(hr)) {
+ BalLogError(hr, "Failed to launch elevated target: %ls", sczLaunchTargetElevatedId);
+
+ //try with ShelExec next time
+ OnClickLaunchButton();
+ }
+ } else {
+ if (sczUnformattedArguments) {
+ hr = BalFormatString(sczUnformattedArguments, &sczArguments);
+ BalExitOnFailure1(hr, "Failed to format launch arguments variable: %ls", sczUnformattedArguments);
+ }
+
+ hr = ShelExec(sczLaunchTarget, sczArguments, L"open", nullptr, nCmdShow, _hWnd, nullptr);
+ BalExitOnFailure1(hr, "Failed to launch target: %ls", sczLaunchTarget);
+
+ ::PostMessageW(_hWnd, WM_CLOSE, 0, 0);
+ }
+
+ LExit:
+ StrSecureZeroFreeString(sczArguments);
+ ReleaseStr(sczUnformattedArguments);
+ ReleaseStr(sczLaunchTargetElevatedId);
+ StrSecureZeroFreeString(sczLaunchTarget);
+ ReleaseStr(sczUnformattedLaunchTarget);
+
+ return;
+ }
+
+
+ //
+ // OnClickRestartButton - allows the restart and closes the app.
+ //
+ void OnClickRestartButton() {
+ AssertSz(_restartRequired, "Restart must be requested to be able to click on the restart button.");
+
+ _allowRestart = TRUE;
+ ::SendMessageW(_hWnd, WM_CLOSE, 0, 0);
+
+ return;
+ }
+
+
+ //
+ // OnClickLogFileLink - show the log file.
+ //
+ void OnClickLogFileLink() {
+ HRESULT hr = S_OK;
+ LPWSTR sczLogFile = nullptr;
+
+ hr = BalGetStringVariable(_bundle.sczLogVariable, &sczLogFile);
+ BalExitOnFailure1(hr, "Failed to get log file variable '%ls'.", _bundle.sczLogVariable);
+
+ hr = ShelExec(L"notepad.exe", sczLogFile, L"open", nullptr, SW_SHOWDEFAULT, _hWnd, nullptr);
+ BalExitOnFailure1(hr, "Failed to open log file target: %ls", sczLogFile);
+
+ LExit:
+ ReleaseStr(sczLogFile);
+
+ return;
+ }
+
+
+ //
+ // SetState
+ //
+ void SetState(__in PYBA_STATE state, __in HRESULT hrStatus) {
+ if (FAILED(hrStatus)) {
+ _hrFinal = hrStatus;
+ }
+
+ if (FAILED(_hrFinal)) {
+ state = PYBA_STATE_FAILED;
+ }
+
+ if (_state != state) {
+ ::PostMessageW(_hWnd, WM_PYBA_CHANGE_STATE, 0, state);
+ }
+ }
+
+ //
+ // GoToPage
+ //
+ void GoToPage(__in PAGE page) {
+ _installPage = page;
+ ::PostMessageW(_hWnd, WM_PYBA_CHANGE_STATE, 0, _state);
+ }
+
+ void DeterminePageId(__in PYBA_STATE state, __out DWORD* pdwPageId) {
+ LONGLONG simple;
+
+ if (BOOTSTRAPPER_DISPLAY_PASSIVE == _command.display) {
+ switch (state) {
+ case PYBA_STATE_INITIALIZED:
+ *pdwPageId = BOOTSTRAPPER_ACTION_HELP == _command.action
+ ? _pageIds[PAGE_HELP]
+ : _pageIds[PAGE_LOADING];
+ break;
+
+ case PYBA_STATE_HELP:
+ *pdwPageId = _pageIds[PAGE_HELP];
+ break;
+
+ case PYBA_STATE_DETECTING:
+ *pdwPageId = _pageIds[PAGE_LOADING]
+ ? _pageIds[PAGE_LOADING]
+ : _pageIds[PAGE_PROGRESS_PASSIVE]
+ ? _pageIds[PAGE_PROGRESS_PASSIVE]
+ : _pageIds[PAGE_PROGRESS];
+ break;
+
+ case PYBA_STATE_DETECTED: __fallthrough;
+ case PYBA_STATE_PLANNING: __fallthrough;
+ case PYBA_STATE_PLANNED: __fallthrough;
+ case PYBA_STATE_APPLYING: __fallthrough;
+ case PYBA_STATE_CACHING: __fallthrough;
+ case PYBA_STATE_CACHED: __fallthrough;
+ case PYBA_STATE_EXECUTING: __fallthrough;
+ case PYBA_STATE_EXECUTED:
+ *pdwPageId = _pageIds[PAGE_PROGRESS_PASSIVE]
+ ? _pageIds[PAGE_PROGRESS_PASSIVE]
+ : _pageIds[PAGE_PROGRESS];
+ break;
+
+ default:
+ *pdwPageId = 0;
+ break;
+ }
+ } else if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) {
+ switch (state) {
+ case PYBA_STATE_INITIALIZING:
+ *pdwPageId = 0;
+ break;
+
+ case PYBA_STATE_INITIALIZED:
+ *pdwPageId = BOOTSTRAPPER_ACTION_HELP == _command.action
+ ? _pageIds[PAGE_HELP]
+ : _pageIds[PAGE_LOADING];
+ break;
+
+ case PYBA_STATE_HELP:
+ *pdwPageId = _pageIds[PAGE_HELP];
+ break;
+
+ case PYBA_STATE_DETECTING:
+ *pdwPageId = _pageIds[PAGE_LOADING];
+ break;
+
+ case PYBA_STATE_DETECTED:
+ if (_installPage == PAGE_LOADING) {
+ switch (_command.action) {
+ case BOOTSTRAPPER_ACTION_INSTALL:
+ if (SUCCEEDED(BalGetNumericVariable(L"SimpleInstall", &simple)) && simple) {
+ _installPage = PAGE_SIMPLE_INSTALL;
+ } else {
+ _installPage = PAGE_INSTALL;
+ }
+ break;
+
+ case BOOTSTRAPPER_ACTION_MODIFY: __fallthrough;
+ case BOOTSTRAPPER_ACTION_REPAIR: __fallthrough;
+ case BOOTSTRAPPER_ACTION_UNINSTALL:
+ _installPage = PAGE_MODIFY;
+ break;
+ }
+ }
+ *pdwPageId = _pageIds[_installPage];
+ break;
+
+ case PYBA_STATE_PLANNING: __fallthrough;
+ case PYBA_STATE_PLANNED: __fallthrough;
+ case PYBA_STATE_APPLYING: __fallthrough;
+ case PYBA_STATE_CACHING: __fallthrough;
+ case PYBA_STATE_CACHED: __fallthrough;
+ case PYBA_STATE_EXECUTING: __fallthrough;
+ case PYBA_STATE_EXECUTED:
+ *pdwPageId = _pageIds[PAGE_PROGRESS];
+ break;
+
+ case PYBA_STATE_APPLIED:
+ *pdwPageId = _pageIds[PAGE_SUCCESS];
+ break;
+
+ case PYBA_STATE_FAILED:
+ *pdwPageId = _pageIds[PAGE_FAILURE];
+ break;
+ }
+ }
+ }
+
+ BOOL IsCrtInstalled() {
+ if (_crtInstalledToken > 0) {
+ return TRUE;
+ } else if (_crtInstalledToken == 0) {
+ return FALSE;
+ }
+
+ // Check whether at least CRT v10.0.9920.0 is available.
+ // It should only be installed as a Windows Update package, which means
+ // we don't need to worry about 32-bit/64-bit.
+ // However, since the WU package does not include vcruntime140.dll, we
+ // still install that ourselves.
+ LPCWSTR crtFile = L"api-ms-win-crt-runtime-l1-1-0.dll";
+
+ DWORD cbVer = GetFileVersionInfoSizeW(crtFile, nullptr);
+ if (!cbVer) {
+ _crtInstalledToken = 0;
+ return FALSE;
+ }
+
+ void *pData = malloc(cbVer);
+ if (!pData) {
+ _crtInstalledToken = 0;
+ return FALSE;
+ }
+
+ if (!GetFileVersionInfoW(crtFile, 0, cbVer, pData)) {
+ free(pData);
+ _crtInstalledToken = 0;
+ return FALSE;
+ }
+
+ VS_FIXEDFILEINFO *ffi;
+ UINT cb;
+ BOOL result = FALSE;
+
+ if (VerQueryValueW(pData, L"\\", (LPVOID*)&ffi, &cb) &&
+ ffi->dwFileVersionMS == 0x000A0000 && ffi->dwFileVersionLS >= 0x26C00000) {
+ result = TRUE;
+ }
+
+ free(pData);
+ _crtInstalledToken = result ? 1 : 0;
+ return result;
+ }
+
+ BOOL QueryElevateForCrtInstall() {
+ // Called to prompt the user that even though they think they won't need
+ // to elevate, they actually will because of the CRT install.
+ if (IsCrtInstalled()) {
+ // CRT is already installed - no need to prompt
+ return TRUE;
+ }
+
+ LONGLONG elevated;
+ HRESULT hr = BalGetNumericVariable(L"WixBundleElevated", &elevated);
+ if (SUCCEEDED(hr) && elevated) {
+ // Already elevated - no need to prompt
+ return TRUE;
+ }
+
+ LOC_STRING *locStr;
+ hr = LocGetString(_wixLoc, L"#(loc.ElevateForCRTInstall)", &locStr);
+ if (FAILED(hr)) {
+ BalLogError(hr, "Failed to get ElevateForCRTInstall string");
+ return FALSE;
+ }
+ return ::MessageBoxW(_hWnd, locStr->wzText, _theme->sczCaption, MB_YESNO) != IDNO;
+ }
+
+ HRESULT EvaluateConditions() {
+ HRESULT hr = S_OK;
+ BOOL result = FALSE;
+
+ for (DWORD i = 0; i < _conditions.cConditions; ++i) {
+ BAL_CONDITION* pCondition = _conditions.rgConditions + i;
+
+ hr = BalConditionEvaluate(pCondition, _engine, &result, &_failedMessage);
+ BalExitOnFailure(hr, "Failed to evaluate condition.");
+
+ if (!result) {
+ // Hope they didn't have hidden variables in their message, because it's going in the log in plaintext.
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "%ls", _failedMessage);
+
+ hr = E_WIXSTDBA_CONDITION_FAILED;
+ // todo: remove in WiX v4, in case people are relying on v3.x logging behavior
+ BalExitOnFailure1(hr, "Bundle condition evaluated to false: %ls", pCondition->sczCondition);
+ }
+ }
+
+ ReleaseNullStrSecure(_failedMessage);
+
+ LExit:
+ return hr;
+ }
+
+
+ void SetTaskbarButtonProgress(__in DWORD dwOverallPercentage) {
+ HRESULT hr = S_OK;
+
+ if (_taskbarButtonOK) {
+ hr = _taskbarList->SetProgressValue(_hWnd, dwOverallPercentage, 100UL);
+ BalExitOnFailure1(hr, "Failed to set taskbar button progress to: %d%%.", dwOverallPercentage);
+ }
+
+ LExit:
+ return;
+ }
+
+
+ void SetTaskbarButtonState(__in TBPFLAG tbpFlags) {
+ HRESULT hr = S_OK;
+
+ if (_taskbarButtonOK) {
+ hr = _taskbarList->SetProgressState(_hWnd, tbpFlags);
+ BalExitOnFailure1(hr, "Failed to set taskbar button state.", tbpFlags);
+ }
+
+ LExit:
+ return;
+ }
+
+
+ void SetProgressState(__in HRESULT hrStatus) {
+ TBPFLAG flag = TBPF_NORMAL;
+
+ if (IsCanceled() || HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) == hrStatus) {
+ flag = TBPF_PAUSED;
+ } else if (IsRollingBack() || FAILED(hrStatus)) {
+ flag = TBPF_ERROR;
+ }
+
+ SetTaskbarButtonState(flag);
+ }
+
+
+ HRESULT LoadBootstrapperBAFunctions() {
+ HRESULT hr = S_OK;
+ LPWSTR sczBafPath = nullptr;
+
+ hr = PathRelativeToModule(&sczBafPath, L"bafunctions.dll", _hModule);
+ BalExitOnFailure(hr, "Failed to get path to BA function DLL.");
+
+#ifdef DEBUG
+ BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: LoadBootstrapperBAFunctions() - BA function DLL %ls", sczBafPath);
+#endif
+
+ _hBAFModule = ::LoadLibraryW(sczBafPath);
+ if (_hBAFModule) {
+ auto pfnBAFunctionCreate = reinterpret_cast<PFN_BOOTSTRAPPER_BA_FUNCTION_CREATE>(::GetProcAddress(_hBAFModule, "CreateBootstrapperBAFunction"));
+ BalExitOnNullWithLastError1(pfnBAFunctionCreate, hr, "Failed to get CreateBootstrapperBAFunction entry-point from: %ls", sczBafPath);
+
+ hr = pfnBAFunctionCreate(_engine, _hBAFModule, &_baFunction);
+ BalExitOnFailure(hr, "Failed to create BA function.");
+ }
+#ifdef DEBUG
+ else {
+ BalLogError(HRESULT_FROM_WIN32(::GetLastError()), "PYBA: LoadBootstrapperBAFunctions() - Failed to load DLL %ls", sczBafPath);
+ }
+#endif
+
+ LExit:
+ if (_hBAFModule && !_baFunction) {
+ ::FreeLibrary(_hBAFModule);
+ _hBAFModule = nullptr;
+ }
+ ReleaseStr(sczBafPath);
+
+ return hr;
+ }
+
+ BOOL IsCheckable(THEME_CONTROL* pControl) {
+ if (!pControl->sczName || !pControl->sczName[0]) {
+ return FALSE;
+ }
+
+ if (pControl->type == THEME_CONTROL_TYPE_CHECKBOX) {
+ return TRUE;
+ }
+
+ if (pControl->type == THEME_CONTROL_TYPE_BUTTON) {
+ if ((pControl->dwStyle & BS_TYPEMASK) == BS_AUTORADIOBUTTON) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+ }
+
+ void SavePageSettings() {
+ DWORD pageId = 0;
+ THEME_PAGE* pPage = nullptr;
+
+ DeterminePageId(_state, &pageId);
+ pPage = ThemeGetPage(_theme, pageId);
+ if (!pPage) {
+ return;
+ }
+
+ for (DWORD i = 0; i < pPage->cControlIndices; ++i) {
+ // Loop through all the checkable controls and set a Burn variable
+ // with that name to true or false.
+ THEME_CONTROL* pControl = _theme->rgControls + pPage->rgdwControlIndices[i];
+ if (IsCheckable(pControl) && ThemeControlEnabled(_theme, pControl->wId)) {
+ BOOL checked = ThemeIsControlChecked(_theme, pControl->wId);
+ _engine->SetVariableNumeric(pControl->sczName, checked ? 1 : 0);
+ }
+
+ // Loop through all the editbox controls with names and set a
+ // Burn variable with that name to the contents.
+ if (THEME_CONTROL_TYPE_EDITBOX == pControl->type && pControl->sczName && *pControl->sczName) {
+ LPWSTR sczValue = nullptr;
+ ThemeGetTextControl(_theme, pControl->wId, &sczValue);
+ _engine->SetVariableString(pControl->sczName, sczValue);
+ }
+ }
+ }
+
+
+public:
+ //
+ // Constructor - initialize member variables.
+ //
+ PythonBootstrapperApplication(
+ __in HMODULE hModule,
+ __in BOOL fPrereq,
+ __in HRESULT hrHostInitialization,
+ __in IBootstrapperEngine* pEngine,
+ __in const BOOTSTRAPPER_COMMAND* pCommand
+ ) : CBalBaseBootstrapperApplication(pEngine, pCommand, 3, 3000) {
+ _hModule = hModule;
+ memcpy_s(&_command, sizeof(_command), pCommand, sizeof(BOOTSTRAPPER_COMMAND));
+
+ LONGLONG llInstalled = 0;
+ HRESULT hr = BalGetNumericVariable(L"WixBundleInstalled", &llInstalled);
+ if (SUCCEEDED(hr) && BOOTSTRAPPER_RESUME_TYPE_REBOOT != _command.resumeType && 0 < llInstalled && BOOTSTRAPPER_ACTION_INSTALL == _command.action) {
+ _command.action = BOOTSTRAPPER_ACTION_MODIFY;
+ } else if (0 == llInstalled && (BOOTSTRAPPER_ACTION_MODIFY == _command.action || BOOTSTRAPPER_ACTION_REPAIR == _command.action)) {
+ _command.action = BOOTSTRAPPER_ACTION_INSTALL;
+ }
+
+ _plannedAction = BOOTSTRAPPER_ACTION_UNKNOWN;
+
+
+ // When resuming from restart doing some install-like operation, try to find the package that forced the
+ // restart. We'll use this information during planning.
+ _nextPackageAfterRestart = nullptr;
+
+ if (BOOTSTRAPPER_RESUME_TYPE_REBOOT == _command.resumeType && BOOTSTRAPPER_ACTION_UNINSTALL < _command.action) {
+ // Ensure the forced restart package variable is null when it is an empty string.
+ HRESULT hr = BalGetStringVariable(L"WixBundleForcedRestartPackage", &_nextPackageAfterRestart);
+ if (FAILED(hr) || !_nextPackageAfterRestart || !*_nextPackageAfterRestart) {
+ ReleaseNullStr(_nextPackageAfterRestart);
+ }
+ }
+
+ _crtInstalledToken = -1;
+ pEngine->SetVariableNumeric(L"CRTInstalled", IsCrtInstalled() ? 1 : 0);
+
+ _wixLoc = nullptr;
+ memset(&_bundle, 0, sizeof(_bundle));
+ memset(&_conditions, 0, sizeof(_conditions));
+ _confirmCloseMessage = nullptr;
+ _failedMessage = nullptr;
+
+ _language = nullptr;
+ _theme = nullptr;
+ memset(_pageIds, 0, sizeof(_pageIds));
+ _hUiThread = nullptr;
+ _registered = FALSE;
+ _hWnd = nullptr;
+
+ _state = PYBA_STATE_INITIALIZING;
+ _visiblePageId = 0;
+ _installPage = PAGE_LOADING;
+ _hrFinal = hrHostInitialization;
+
+ _downgrading = FALSE;
+ _restartResult = BOOTSTRAPPER_APPLY_RESTART_NONE;
+ _restartRequired = FALSE;
+ _allowRestart = FALSE;
+
+ _suppressDowngradeFailure = FALSE;
+ _suppressRepair = FALSE;
+ _modifying = FALSE;
+
+ _overridableVariables = nullptr;
+ _taskbarList = nullptr;
+ _taskbarButtonCreatedMessage = UINT_MAX;
+ _taskbarButtonOK = FALSE;
+ _showingInternalUIThisPackage = FALSE;
+ _triedToLaunchElevated = FALSE;
+
+ _suppressPaint = FALSE;
+
+ pEngine->AddRef();
+ _engine = pEngine;
+
+ _hBAFModule = nullptr;
+ _baFunction = nullptr;
+ }
+
+
+ //
+ // Destructor - release member variables.
+ //
+ ~PythonBootstrapperApplication() {
+ AssertSz(!::IsWindow(_hWnd), "Window should have been destroyed before destructor.");
+ AssertSz(!_theme, "Theme should have been released before destructor.");
+
+ ReleaseObject(_taskbarList);
+ ReleaseDict(_overridableVariables);
+ ReleaseStr(_failedMessage);
+ ReleaseStr(_confirmCloseMessage);
+ BalConditionsUninitialize(&_conditions);
+ BalInfoUninitialize(&_bundle);
+ LocFree(_wixLoc);
+
+ ReleaseStr(_language);
+ ReleaseStr(_nextPackageAfterRestart);
+ ReleaseNullObject(_engine);
+
+ if (_hBAFModule) {
+ ::FreeLibrary(_hBAFModule);
+ _hBAFModule = nullptr;
+ }
+ }
+
+private:
+ HMODULE _hModule;
+ BOOTSTRAPPER_COMMAND _command;
+ IBootstrapperEngine* _engine;
+ BOOTSTRAPPER_ACTION _plannedAction;
+
+ LPWSTR _nextPackageAfterRestart;
+
+ WIX_LOCALIZATION* _wixLoc;
+ BAL_INFO_BUNDLE _bundle;
+ BAL_CONDITIONS _conditions;
+ LPWSTR _failedMessage;
+ LPWSTR _confirmCloseMessage;
+
+ LPWSTR _language;
+ THEME* _theme;
+ DWORD _pageIds[countof(PAGE_NAMES)];
+ HANDLE _hUiThread;
+ BOOL _registered;
+ HWND _hWnd;
+
+ PYBA_STATE _state;
+ HRESULT _hrFinal;
+ DWORD _visiblePageId;
+ PAGE _installPage;
+
+ BOOL _startedExecution;
+ DWORD _calculatedCacheProgress;
+ DWORD _calculatedExecuteProgress;
+
+ BOOL _downgrading;
+ BOOTSTRAPPER_APPLY_RESTART _restartResult;
+ BOOL _restartRequired;
+ BOOL _allowRestart;
+
+ BOOL _suppressDowngradeFailure;
+ BOOL _suppressRepair;
+ BOOL _modifying;
+
+ int _crtInstalledToken;
+
+ STRINGDICT_HANDLE _overridableVariables;
+
+ ITaskbarList3* _taskbarList;
+ UINT _taskbarButtonCreatedMessage;
+ BOOL _taskbarButtonOK;
+ BOOL _showingInternalUIThisPackage;
+ BOOL _triedToLaunchElevated;
+
+ BOOL _suppressPaint;
+
+ HMODULE _hBAFModule;
+ IBootstrapperBAFunction* _baFunction;
+};
+
+//
+// CreateBootstrapperApplication - creates a new IBootstrapperApplication object.
+//
+HRESULT CreateBootstrapperApplication(
+ __in HMODULE hModule,
+ __in BOOL fPrereq,
+ __in HRESULT hrHostInitialization,
+ __in IBootstrapperEngine* pEngine,
+ __in const BOOTSTRAPPER_COMMAND* pCommand,
+ __out IBootstrapperApplication** ppApplication
+ ) {
+ HRESULT hr = S_OK;
+
+ if (fPrereq) {
+ hr = E_INVALIDARG;
+ ExitWithLastError(hr, "Failed to create UI thread.");
+ }
+
+ PythonBootstrapperApplication* pApplication = nullptr;
+
+ pApplication = new PythonBootstrapperApplication(hModule, fPrereq, hrHostInitialization, pEngine, pCommand);
+ ExitOnNull(pApplication, hr, E_OUTOFMEMORY, "Failed to create new standard bootstrapper application object.");
+
+ *ppApplication = pApplication;
+ pApplication = nullptr;
+
+LExit:
+ ReleaseObject(pApplication);
+ return hr;
+}
diff --git a/Tools/msi/bundle/bootstrap/pch.cpp b/Tools/msi/bundle/bootstrap/pch.cpp
new file mode 100644
index 0000000000..1d9f38c57d
--- /dev/null
+++ b/Tools/msi/bundle/bootstrap/pch.cpp
@@ -0,0 +1 @@
+#include "pch.h"
diff --git a/Tools/msi/bundle/bootstrap/pch.h b/Tools/msi/bundle/bootstrap/pch.h
new file mode 100644
index 0000000000..0956413d5c
--- /dev/null
+++ b/Tools/msi/bundle/bootstrap/pch.h
@@ -0,0 +1,59 @@
+//-------------------------------------------------------------------------------------------------
+// <copyright file="precomp.h" company="Outercurve Foundation">
+// Copyright (c) 2004, Outercurve Foundation.
+// This software is released under Microsoft Reciprocal License (MS-RL).
+// The license and further copyright text can be found in the file
+// LICENSE.TXT at the root directory of the distribution.
+// </copyright>
+//
+// <summary>
+// Precompiled header for standard bootstrapper application.
+// </summary>
+//-------------------------------------------------------------------------------------------------
+
+#pragma once
+
+#include <windows.h>
+#include <gdiplus.h>
+#include <Uxtheme.h>
+#include <msiquery.h>
+#include <objbase.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <stdlib.h>
+#include <strsafe.h>
+#include <stddef.h>
+
+#include "dutil.h"
+#include "memutil.h"
+#include "dictutil.h"
+#include "dirutil.h"
+#include "fileutil.h"
+#include "locutil.h"
+#include "logutil.h"
+#include "pathutil.h"
+#include "resrutil.h"
+#include "shelutil.h"
+#include "strutil.h"
+#include "thmutil.h"
+#include "uriutil.h"
+#include "xmlutil.h"
+
+#include "IBootstrapperEngine.h"
+#include "IBootstrapperApplication.h"
+
+#include "BalBaseBootstrapperApplication.h"
+#include "balinfo.h"
+#include "balcondition.h"
+
+HRESULT CreateBootstrapperApplication(
+ __in HMODULE hModule,
+ __in BOOL fPrereq,
+ __in HRESULT hrHostInitialization,
+ __in IBootstrapperEngine* pEngine,
+ __in const BOOTSTRAPPER_COMMAND* pCommand,
+ __out IBootstrapperApplication** ppApplication
+);
+
+#include "IBootstrapperBAFunction.h"
+
diff --git a/Tools/msi/bundle/bootstrap/pythonba.cpp b/Tools/msi/bundle/bootstrap/pythonba.cpp
new file mode 100644
index 0000000000..0ce45ad31d
--- /dev/null
+++ b/Tools/msi/bundle/bootstrap/pythonba.cpp
@@ -0,0 +1,76 @@
+//-------------------------------------------------------------------------------------------------
+// <copyright file="wixstdba.cpp" company="Outercurve Foundation">
+// Copyright (c) 2004, Outercurve Foundation.
+// This software is released under Microsoft Reciprocal License (MS-RL).
+// The license and further copyright text can be found in the file
+// LICENSE.TXT at the root directory of the distribution.
+// </copyright>
+//
+// <summary>
+// Setup chainer/bootstrapper standard UI for WiX toolset.
+// </summary>
+//-------------------------------------------------------------------------------------------------
+
+#include "pch.h"
+
+static HINSTANCE vhInstance = NULL;
+
+extern "C" BOOL WINAPI DllMain(
+ IN HINSTANCE hInstance,
+ IN DWORD dwReason,
+ IN LPVOID /* pvReserved */
+ )
+{
+ switch(dwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ ::DisableThreadLibraryCalls(hInstance);
+ vhInstance = hInstance;
+ break;
+
+ case DLL_PROCESS_DETACH:
+ vhInstance = NULL;
+ break;
+ }
+
+ return TRUE;
+}
+
+
+extern "C" HRESULT WINAPI BootstrapperApplicationCreate(
+ __in IBootstrapperEngine* pEngine,
+ __in const BOOTSTRAPPER_COMMAND* pCommand,
+ __out IBootstrapperApplication** ppApplication
+ )
+{
+ HRESULT hr = S_OK;
+
+ BalInitialize(pEngine);
+
+ hr = CreateBootstrapperApplication(vhInstance, FALSE, S_OK, pEngine, pCommand, ppApplication);
+ BalExitOnFailure(hr, "Failed to create bootstrapper application interface.");
+
+LExit:
+ return hr;
+}
+
+
+extern "C" void WINAPI BootstrapperApplicationDestroy()
+{
+ BalUninitialize();
+}
+
+
+extern "C" HRESULT WINAPI MbaPrereqBootstrapperApplicationCreate(
+ __in HRESULT hrHostInitialization,
+ __in IBootstrapperEngine* pEngine,
+ __in const BOOTSTRAPPER_COMMAND* pCommand,
+ __out IBootstrapperApplication** ppApplication
+ )
+{
+ return E_NOTIMPL;
+}
+
+
+extern "C" void WINAPI MbaPrereqBootstrapperApplicationDestroy()
+{ }
diff --git a/Tools/msi/bundle/bootstrap/pythonba.def b/Tools/msi/bundle/bootstrap/pythonba.def
new file mode 100644
index 0000000000..29b3fa50df
--- /dev/null
+++ b/Tools/msi/bundle/bootstrap/pythonba.def
@@ -0,0 +1,18 @@
+;-------------------------------------------------------------------------------------------------
+; <copyright file="wixstdba.def" company="Outercurve Foundation">
+; Copyright (c) 2004, Outercurve Foundation.
+; This software is released under Microsoft Reciprocal License (MS-RL).
+; The license and further copyright text can be found in the file
+; LICENSE.TXT at the root directory of the distribution.
+; </copyright>
+;
+; <summary>
+; WiX Standard Bootstrapper Application DLL entry points.
+; </summary>
+;-------------------------------------------------------------------------------------------------
+
+EXPORTS
+ BootstrapperApplicationCreate
+ BootstrapperApplicationDestroy
+ MbaPrereqBootstrapperApplicationCreate
+ MbaPrereqBootstrapperApplicationDestroy
diff --git a/Tools/msi/bundle/bootstrap/pythonba.sln b/Tools/msi/bundle/bootstrap/pythonba.sln
new file mode 100644
index 0000000000..bf43fed901
--- /dev/null
+++ b/Tools/msi/bundle/bootstrap/pythonba.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.30501.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pythonba", "pythonba.vcxproj", "{7A09B132-B3EE-499B-A700-A4B2157FEA3D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {7A09B132-B3EE-499B-A700-A4B2157FEA3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {7A09B132-B3EE-499B-A700-A4B2157FEA3D}.Debug|Win32.Build.0 = Debug|Win32
+ {7A09B132-B3EE-499B-A700-A4B2157FEA3D}.Release|Win32.ActiveCfg = Release|Win32
+ {7A09B132-B3EE-499B-A700-A4B2157FEA3D}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Tools/msi/bundle/bootstrap/pythonba.vcxproj b/Tools/msi/bundle/bootstrap/pythonba.vcxproj
new file mode 100644
index 0000000000..5d8337c134
--- /dev/null
+++ b/Tools/msi/bundle/bootstrap/pythonba.vcxproj
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ <copyright file="wixstdba.vcxproj" company="Outercurve Foundation">
+ Copyright (c) 2004, Outercurve Foundation.
+ This software is released under Microsoft Reciprocal License (MS-RL).
+ The license and further copyright text can be found in the file
+ LICENSE.TXT at the root directory of the distribution.
+ </copyright>
+-->
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <Configuration Condition="'$(Configuration)' == ''">Release</Configuration>
+ <Platform Condition="'$(Platform)' == ''">Win32</Platform>
+ <PlatformToolset Condition="'$(PlatformToolset)' == '' and '$(VCTargetsPath14)' != ''">v140</PlatformToolset>
+ <PlatformToolset Condition="'$(PlatformToolset)' == '' and '$(VCTargetsPath12)' != ''">v120</PlatformToolset>
+ <ProjectGuid>{7A09B132-B3EE-499B-A700-A4B2157FEA3D}</ProjectGuid>
+ <TargetName>PythonBA</TargetName>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <Import Project="..\..\wix.props" />
+ <PropertyGroup Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <IntDir>$(ProjectDir)..\..\obj\$(Configuration)_Bootstrap\</IntDir>
+ <OutDir>$(IntDir)</OutDir>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <PreprocessorDefinitions>_CRT_STDIO_LEGACY_WIDE_SPECIFIERS=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>$(WixInstallPath)sdk\inc</AdditionalIncludeDirectories>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;gdiplus.lib;msimg32.lib;shlwapi.lib;wininet.lib;dutil.lib;balutil.lib;version.lib;uxtheme.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories Condition="'$(PlatformToolset)' == 'v140'">$(WixInstallPath)sdk\vs2015\lib\x86</AdditionalLibraryDirectories>
+ <AdditionalLibraryDirectories Condition="'$(PlatformToolset)' == 'v120'">$(WixInstallPath)sdk\vs2013\lib\x86</AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>pythonba.def</ModuleDefinitionFile>
+ <GenerateDebugInformation Condition="'$(Configuration)'=='Debug'">true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="PythonBootstrapperApplication.cpp" />
+ <ClCompile Include="pythonba.cpp" />
+ <ClCompile Include="pch.cpp">
+ <PrecompiledHeader>Create</PrecompiledHeader>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="pch.h" />
+ <ClInclude Include="resource.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="pythonba.def" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+</Project> \ No newline at end of file
diff --git a/Tools/msi/bundle/bootstrap/resource.h b/Tools/msi/bundle/bootstrap/resource.h
new file mode 100644
index 0000000000..53c03c319f
--- /dev/null
+++ b/Tools/msi/bundle/bootstrap/resource.h
@@ -0,0 +1,25 @@
+//-------------------------------------------------------------------------------------------------
+// <copyright file="resource.h" company="Outercurve Foundation">
+// Copyright (c) 2004, Outercurve Foundation.
+// This software is released under Microsoft Reciprocal License (MS-RL).
+// The license and further copyright text can be found in the file
+// LICENSE.TXT at the root directory of the distribution.
+// </copyright>
+//-------------------------------------------------------------------------------------------------
+
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+//
+#define IDC_STATIC -1
+
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1003
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Tools/msi/bundle/bundle.ico b/Tools/msi/bundle/bundle.ico
new file mode 100644
index 0000000000..1ab629eff2
--- /dev/null
+++ b/Tools/msi/bundle/bundle.ico
Binary files differ
diff --git a/Tools/msi/bundle/bundle.targets b/Tools/msi/bundle/bundle.targets
new file mode 100644
index 0000000000..cc7e445023
--- /dev/null
+++ b/Tools/msi/bundle/bundle.targets
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" TreatAsLocalProperty="DownloadUrl">
+ <PropertyGroup>
+ <SchemaVersion>2.0</SchemaVersion>
+ <OutputType>Bundle</OutputType>
+
+ <BootstrapConfiguration Condition="'$(BootstrapConfiguration)' == ''">Release</BootstrapConfiguration>
+ <LinkerSuppressSpecificWarnings>1132;1135;1140</LinkerSuppressSpecificWarnings>
+ <OutputName Condition="$(BuildForRelease)">$(OutputName)-$(PythonVersion)</OutputName>
+ <OutputName Condition="!$(BuildForRelease)">$(OutputName)-$(MajorVersionNumber).$(MinorVersionNumber).$(MicroVersionNumber).$(RevisionNumber)</OutputName>
+ <OutputName Condition="$(Platform) == 'x64'">$(OutputName)-amd64</OutputName>
+ <OutputName Condition="'$(OutputSuffix)' != ''">$(OutputName)-$(OutputSuffix)</OutputName>
+ <OutputName Condition="'$(Configuration)' == 'Debug'">$(OutputName)-d</OutputName>
+ <TargetName>$(OutputName)</TargetName>
+
+ <OutputPath>$(OutputPath)en-us\</OutputPath>
+ <OutDir>$(OutputPath)</OutDir>
+
+ <DownloadUrl Condition="'$(DownloadUrl)' == '' and '$(DownloadUrlBase)' != ''">$(DownloadUrlBase.TrimEnd(`/`))/$(MajorVersionNumber).$(MinorVersionNumber).$(MicroVersionNumber)/$(ArchName)$(ReleaseLevelName)/</DownloadUrl>
+ <DefineConstants Condition="'$(DownloadUrl)' != ''">$(DefineConstants);DownloadUrl=$(DownloadUrl){2}</DefineConstants>
+ <DefineConstants Condition="'$(DownloadUrl)' == ''">$(DefineConstants);DownloadUrl={2}</DefineConstants>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <WixExtension Include="WixUtilExtension">
+ <HintPath>WixUtilExtension</HintPath>
+ <Name>WixUtilExtension</Name>
+ </WixExtension>
+ <WixExtension Include="WixDependencyExtension">
+ <HintPath>WixDependencyExtension</HintPath>
+ <Name>WixDependencyExtension</Name>
+ </WixExtension>
+ <WixExtension Include="WixBalExtension">
+ <HintPath>WixBalExtension</HintPath>
+ <Name>WixBalExtension</Name>
+ </WixExtension>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="bundle.wxs" />
+ <Compile Include="packagegroups\*.wxs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="Default.thm" />
+ <Content Include="Default.wxl" />
+ <Content Include="SideBar.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="bundle.wxl" />
+ <WxlTemplate Include="*_en-US.wxl_template" />
+ </ItemGroup>
+ <ItemGroup>
+ <LinkerBindInputPaths Include="$(OutputPath)">
+ <BindName></BindName>
+ </LinkerBindInputPaths>
+ </ItemGroup>
+
+ <ItemDefinitionGroup>
+ <Package>
+ <Properties>BuildForRelease=$(BuildForRelease)</Properties>
+ </Package>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <Package Include="..\core\core*.wixproj" />
+ <Package Include="..\crt\crt*.wixproj" />
+ <Package Include="..\dev\dev*.wixproj" />
+ <Package Include="..\doc\doc*.wixproj" />
+ <Package Include="..\exe\exe*.wixproj" />
+ <Package Include="..\lib\lib*.wixproj" />
+ <Package Include="..\path\path*.wixproj" />
+ <Package Include="..\pip\pip*.wixproj" />
+ <Package Include="..\tcltk\tcltk*.wixproj" />
+ <Package Include="..\test\test*.wixproj" />
+ <Package Include="..\tools\tools*.wixproj" />
+ </ItemGroup>
+
+ <Target Name="BuildPackages" BeforeTargets="BeforeBuild" Condition="'$(RebuildAll)' != 'false'">
+ <MSBuild Projects="@(Package)" BuildInParallel="true" />
+ </Target>
+
+ <Target Name="BuildLauncher" BeforeTargets="BeforeBuild" Condition="'$(RebuildAll)' != 'false'">
+ <!-- Build the launcher MSI using Exec rather than MSBuild -->
+ <Exec Command='msbuild ..\launcher\launcher.wixproj /p:Platform=x86 /p:OutputPath="$(BuildPath.TrimEnd(`\`))" /p:OutputSuffix=$(Platform) /p:BuildForRelease=$(BuildForRelease)'
+ ContinueOnError="false" />
+ </Target>
+
+ <Target Name="BuildBootstrapApplication" BeforeTargets="BeforeBuild">
+ <Message Text="Building bootstrap app" Importance="high" />
+
+ <MSBuild Projects="bootstrap\pythonba.vcxproj"
+ Targets="Build;GetNativeTargetPath"
+ UseResultsCache="true"
+ Properties="Configuration=$(BootstrapConfiguration);Platform=Win32">
+ <Output TaskParameter="TargetOutputs" PropertyName="BootstrapAppPath" />
+ </MSBuild>
+
+ <PropertyGroup>
+ <DefineConstants>$(DefineConstants);BootstrapApp=$(BootstrapAppPath)</DefineConstants>
+ </PropertyGroup>
+ </Target>
+
+ <Import Project="..\msi.targets" />
+</Project> \ No newline at end of file
diff --git a/Tools/msi/bundle/bundle.wxl b/Tools/msi/bundle/bundle.wxl
new file mode 100644
index 0000000000..684e0dac4e
--- /dev/null
+++ b/Tools/msi/bundle/bundle.wxl
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization">
+ <String Id="CRTDescription">C Runtime Update (KB2999226)</String>
+ <String Id="CompileAllDescription">Precompiling standard library</String>
+</WixLocalization>
diff --git a/Tools/msi/bundle/bundle.wxs b/Tools/msi/bundle/bundle.wxs
new file mode 100644
index 0000000000..ef9ed13f58
--- /dev/null
+++ b/Tools/msi/bundle/bundle.wxs
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
+ xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
+ <Bundle Name="!(loc.FullProductName)"
+ UpgradeCode="$(var.CoreUpgradeCode)"
+ Version="$(var.Version)"
+ IconSourceFile="bundle.ico"
+ Manufacturer="!(loc.Manufacturer)"
+ AboutUrl="http://www.python.org/"
+ DisableModify="button"
+ Compressed="no">
+ <BootstrapperApplication Id="PythonBA" SourceFile="$(var.BootstrapApp)">
+ <Payload Compressed='yes' SourceFile='Default.thm' />
+ <Payload Compressed='yes' SourceFile='Default.wxl' />
+ <Payload Compressed='yes' SourceFile='SideBar.png' />
+ </BootstrapperApplication>
+
+ <!-- May be set to "Removing" or "Repairing" -->
+ <Variable Name="ActionLikeInstalling" Value="Installing" />
+ <!-- May be set to "Uninstallation" or "Repair" -->
+ <Variable Name="ActionLikeInstallation" Value="Setup" />
+
+ <Variable Name="ShortVersion" Value="$(var.MajorVersionNumber).$(var.MinorVersionNumber)" />
+ <Variable Name="ShortVersionNoDot" Value="$(var.MajorVersionNumber)$(var.MinorVersionNumber)" />
+
+ <Variable Name="InstallAllUsers" Value="1" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="TargetDir" Value="" Persisted="yes" bal:Overridable="yes" />
+ <?if $(var.Platform)~="x64" ?>
+ <Variable Name="DefaultAllUsersTargetDir" Value="[ProgramFiles64Folder]Python [ShortVersion]" bal:Overridable="yes" />
+ <?else ?>
+ <Variable Name="DefaultAllUsersTargetDir" Value="[ProgramFilesFolder]Python [ShortVersion]" bal:Overridable="yes" />
+ <?endif ?>
+ <Variable Name="DefaultJustForMeTargetDir" Value="[LocalAppDataFolder]Programs\Python\Python[ShortVersionNoDot]$(var.Suffix32)" bal:Overridable="yes" />
+
+ <!--
+ An empty string will use the other defaults based on InstallAllUsers
+ (and switch dynamically in the UI). To get the old default, pass
+ this property on the command line:
+ DefaultCustomTargetDir=[WindowsVolume]Python[ShortVersionNoDot]
+ -->
+ <Variable Name="DefaultCustomTargetDir" Value="" bal:Overridable="yes" />
+
+ <Variable Name="InstallAllUsersState" Value="enabled" />
+ <Variable Name="TargetDirState" Value="enabled" />
+ <Variable Name="CustomBrowseButtonState" Value="enabled" />
+
+ <Variable Name="Include_core" Value="1" Persisted="yes" />
+ <Variable Name="Include_exe" Value="1" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="Include_dev" Value="1" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="Include_lib" Value="1" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="Include_test" Value="1" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="Include_doc" Value="1" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="Include_tools" Value="1" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="Include_tcltk" Value="1" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="Include_pip" Value="1" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="Include_launcher" Value="1" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="Include_symbols" Value="0" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="Include_debug" Value="0" Persisted="yes" bal:Overridable="yes" />
+
+ <Variable Name="AssociateFiles" Value="1" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="PrependPath" Value="0" Persisted="yes" bal:Overridable="yes" />
+ <Variable Name="CompileAll" Value="0" Persisted="yes" bal:Overridable="yes" />
+
+ <Variable Name="SimpleInstall" Value="0" bal:Overridable="yes" />
+
+ <Chain ParallelCache="yes">
+ <PackageGroupRef Id="crt" />
+ <PackageGroupRef Id="core" />
+ <PackageGroupRef Id="dev" />
+ <PackageGroupRef Id="exe" />
+ <PackageGroupRef Id="lib" />
+ <PackageGroupRef Id="test" />
+ <PackageGroupRef Id="doc" />
+ <PackageGroupRef Id="tools" />
+ <PackageGroupRef Id="tcltk" />
+ <PackageGroupRef Id="launcher" />
+ <PackageGroupRef Id="postinstall" />
+ </Chain>
+ </Bundle>
+</Wix>
diff --git a/Tools/msi/bundle/full.wixproj b/Tools/msi/bundle/full.wixproj
new file mode 100644
index 0000000000..bdbdd8e175
--- /dev/null
+++ b/Tools/msi/bundle/full.wixproj
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <ProjectGuid>{3E204ADD-238D-4D10-852C-4F859325C839}</ProjectGuid>
+ <OutputName>python</OutputName>
+ <OutputSuffix>full</OutputSuffix>
+ </PropertyGroup>
+
+ <Import Project="..\msi.props" />
+
+ <PropertyGroup>
+ <DefineConstants>
+ $(DefineConstants);
+ CompressMSI=yes;
+ CompressPDB=yes;
+ CompressMSI_D=yes;
+ </DefineConstants>
+ </PropertyGroup>
+
+ <Import Project="bundle.targets" />
+</Project> \ No newline at end of file
diff --git a/Tools/msi/bundle/packagegroups/core.wxs b/Tools/msi/bundle/packagegroups/core.wxs
new file mode 100644
index 0000000000..9df6b79194
--- /dev/null
+++ b/Tools/msi/bundle/packagegroups/core.wxs
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+ <PackageGroup Id="core">
+ <MsiPackage Id="core_AllUsers"
+ SourceFile="core.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and (Include_core or Include_exe or Include_launcher or Include_pip)">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="core_AllUsers_pdb"
+ SourceFile="core_pdb.msi"
+ Compressed="$(var.CompressPDB)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and (Include_core or Include_exe or Include_launcher or Include_pip) and Include_symbols">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="core_AllUsers_d"
+ SourceFile="core_d.msi"
+ Compressed="$(var.CompressMSI_D)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and (Include_core or Include_exe or Include_launcher or Include_pip) and Include_debug">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+
+ <MsiPackage Id="core_JustForMe"
+ SourceFile="core.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and (Include_core or Include_exe or Include_launcher or Include_pip)">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="core_JustForMe_pdb"
+ SourceFile="core_pdb.msi"
+ Compressed="$(var.CompressPDB)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and (Include_core or Include_exe or Include_launcher or Include_pip) and Include_symbols">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="core_JustForMe_d"
+ SourceFile="core_d.msi"
+ Compressed="$(var.CompressMSI_D)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and (Include_core or Include_exe or Include_launcher or Include_pip) and Include_debug">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ </PackageGroup>
+ </Fragment>
+</Wix> \ No newline at end of file
diff --git a/Tools/msi/bundle/packagegroups/crt.wxs b/Tools/msi/bundle/packagegroups/crt.wxs
new file mode 100644
index 0000000000..b0797009f6
--- /dev/null
+++ b/Tools/msi/bundle/packagegroups/crt.wxs
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+ <PackageGroup Id="crt">
+ <PackageGroupRef Id="crt_14.0_v6.0" />
+ <PackageGroupRef Id="crt_14.0_v6.1" />
+ <PackageGroupRef Id="crt_14.0_v6.2" />
+ <PackageGroupRef Id="crt_14.0_v6.3" />
+
+ <MsiPackage Id="crt_AllUsers"
+ SourceFile="crt.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and (Include_core or Include_exe or Include_launcher or Include_pip)">
+ <!-- TARGETDIR will be ignored, but must still be provided -->
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+
+ <MsiPackage Id="crt_JustForMe"
+ SourceFile="crt.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and (Include_core or Include_exe or Include_launcher or Include_pip)">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ </PackageGroup>
+ </Fragment>
+
+ <?foreach ver in v6.0;v6.1;v6.2;v6.3 ?>
+ <?if "$(var.ver)" = "v6.0" ?>
+ <?define msuver=6.0 ?>
+ <?elseif "$(var.ver)" = "v6.1" ?>
+ <?define msuver=6.1 ?>
+ <?elseif "$(var.ver)" = "v6.2" ?>
+ <?define msuver=8-RT ?>
+ <?elseif "$(var.ver)" = "v6.3" ?>
+ <?define msuver=8.1 ?>
+ <?else ?>
+ <?error unknown version $(var.ver) ?>
+ <?endif ?>
+
+ <Fragment>
+ <PackageGroup Id="crt_14.0_$(var.ver)">
+ <MsuPackage Id="crt_14.0_$(var.ver)_x86"
+ KB="2999226"
+ SourceFile="!(bindpath.redist)\Windows$(var.msuver)-KB2999226-x86.msu"
+ DisplayName="!(loc.CRTDescription)"
+ Description="!(loc.CRTDescription)"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ InstallCondition="not CRTInstalled and VersionNT = $(var.ver) and not VersionNT64 and (Include_core or Include_exe or Include_launcher or Include_pip)" />
+
+ <MsuPackage Id="crt_14.0_$(var.ver)_x64"
+ KB="2999226"
+ SourceFile="!(bindpath.redist)\Windows$(var.msuver)-KB2999226-x64.msu"
+ DisplayName="!(loc.CRTDescription)"
+ Description="!(loc.CRTDescription)"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ InstallCondition="not CRTInstalled and VersionNT64 = $(var.ver) and (Include_core or Include_exe or Include_launcher or Include_pip)" />
+ </PackageGroup>
+ </Fragment>
+
+ <?undef msuver ?>
+ <?endforeach ?>
+</Wix> \ No newline at end of file
diff --git a/Tools/msi/bundle/packagegroups/dev.wxs b/Tools/msi/bundle/packagegroups/dev.wxs
new file mode 100644
index 0000000000..f2ea29b611
--- /dev/null
+++ b/Tools/msi/bundle/packagegroups/dev.wxs
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+ <PackageGroup Id="dev">
+ <MsiPackage Id="dev_AllUsers"
+ SourceFile="dev.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and Include_dev">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="dev_AllUsers_d"
+ SourceFile="dev_d.msi"
+ Compressed="$(var.CompressMSI_D)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and Include_dev and Include_debug">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+
+ <MsiPackage Id="dev_JustForMe"
+ SourceFile="dev.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and Include_dev">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="dev_JustForMe_d"
+ SourceFile="dev_d.msi"
+ Compressed="$(var.CompressMSI_D)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and Include_dev and Include_debug">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ </PackageGroup>
+ </Fragment>
+</Wix> \ No newline at end of file
diff --git a/Tools/msi/bundle/packagegroups/doc.wxs b/Tools/msi/bundle/packagegroups/doc.wxs
new file mode 100644
index 0000000000..24b9ff1883
--- /dev/null
+++ b/Tools/msi/bundle/packagegroups/doc.wxs
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+ <PackageGroup Id="doc">
+ <MsiPackage Id="doc_AllUsers"
+ SourceFile="doc.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and Include_doc">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+
+ <MsiPackage Id="doc_JustForMe"
+ SourceFile="doc.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and Include_doc">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ </PackageGroup>
+ </Fragment>
+</Wix> \ No newline at end of file
diff --git a/Tools/msi/bundle/packagegroups/exe.wxs b/Tools/msi/bundle/packagegroups/exe.wxs
new file mode 100644
index 0000000000..046b90ec2c
--- /dev/null
+++ b/Tools/msi/bundle/packagegroups/exe.wxs
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+ <PackageGroup Id="exe">
+ <MsiPackage Id="exe_AllUsers"
+ SourceFile="exe.msi"
+ ForcePerMachine="yes"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ InstallCondition="InstallAllUsers and (Include_exe or Include_launcher or Include_pip)">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="exe_AllUsers_pdb"
+ SourceFile="exe_pdb.msi"
+ ForcePerMachine="yes"
+ Compressed="$(var.CompressPDB)"
+ DownloadUrl="$(var.DownloadUrl)"
+ InstallCondition="InstallAllUsers and (Include_exe or Include_launcher or Include_pip) and Include_symbols">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="exe_AllUsers_d"
+ SourceFile="exe_d.msi"
+ ForcePerMachine="yes"
+ Compressed="$(var.CompressMSI_D)"
+ DownloadUrl="$(var.DownloadUrl)"
+ InstallCondition="InstallAllUsers and (Include_exe or Include_launcher or Include_pip) and Include_debug">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+
+ <MsiPackage Id="exe_JustForMe"
+ SourceFile="exe.msi"
+ ForcePerMachine="no"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ InstallCondition="not InstallAllUsers and (Include_exe or Include_launcher or Include_pip)">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="exe_JustForMe_pdb"
+ SourceFile="exe_pdb.msi"
+ ForcePerMachine="no"
+ Compressed="$(var.CompressPDB)"
+ DownloadUrl="$(var.DownloadUrl)"
+ InstallCondition="not InstallAllUsers and (Include_exe or Include_launcher or Include_pip) and Include_symbols">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="exe_JustForMe_d"
+ SourceFile="exe_d.msi"
+ ForcePerMachine="no"
+ Compressed="$(var.CompressMSI_D)"
+ DownloadUrl="$(var.DownloadUrl)"
+ InstallCondition="not InstallAllUsers and (Include_exe or Include_launcher or Include_pip) and Include_debug">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ </PackageGroup>
+ </Fragment>
+</Wix> \ No newline at end of file
diff --git a/Tools/msi/bundle/packagegroups/launcher.wxs b/Tools/msi/bundle/packagegroups/launcher.wxs
new file mode 100644
index 0000000000..d09175f7dc
--- /dev/null
+++ b/Tools/msi/bundle/packagegroups/launcher.wxs
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+ <PackageGroup Id="launcher">
+ <!-- The All Users launcher is always the 32-bit version -->
+ <MsiPackage Id="launcher_AllUsers"
+ SourceFile="launcher.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ EnableFeatureSelection="yes"
+ InstallCondition="InstallAllUsers and Include_launcher" />
+
+ <MsiPackage Id="launcher_JustForMe"
+ SourceFile="launcher.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ EnableFeatureSelection="yes"
+ InstallCondition="not InstallAllUsers and Include_launcher" />
+ </PackageGroup>
+ </Fragment>
+</Wix> \ No newline at end of file
diff --git a/Tools/msi/bundle/packagegroups/lib.wxs b/Tools/msi/bundle/packagegroups/lib.wxs
new file mode 100644
index 0000000000..47a97f16d0
--- /dev/null
+++ b/Tools/msi/bundle/packagegroups/lib.wxs
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+ <PackageGroup Id="lib">
+ <MsiPackage Id="lib_AllUsers"
+ SourceFile="lib.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and Include_lib">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="lib_AllUsers_pdb"
+ SourceFile="lib_pdb.msi"
+ Compressed="$(var.CompressPDB)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and Include_lib and Include_symbols">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="lib_AllUsers_d"
+ SourceFile="lib_d.msi"
+ Compressed="$(var.CompressMSI_D)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and Include_lib and Include_debug">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+
+ <MsiPackage Id="lib_JustForMe"
+ SourceFile="lib.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and Include_lib">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="lib_JustForMe_pdb"
+ SourceFile="lib_pdb.msi"
+ Compressed="$(var.CompressPDB)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and Include_lib and Include_symbols">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="lib_JustForMe_d"
+ SourceFile="lib_d.msi"
+ Compressed="$(var.CompressMSI_D)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and Include_lib and Include_debug">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ </PackageGroup>
+ </Fragment>
+</Wix> \ No newline at end of file
diff --git a/Tools/msi/bundle/packagegroups/postinstall.wxs b/Tools/msi/bundle/packagegroups/postinstall.wxs
new file mode 100644
index 0000000000..b20cc501b7
--- /dev/null
+++ b/Tools/msi/bundle/packagegroups/postinstall.wxs
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+ <PackageGroup Id="postinstall">
+ <MsiPackage Id="pip_AllUsers"
+ SourceFile="pip.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and Include_pip">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="pip_JustForMe"
+ SourceFile="pip.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and Include_pip">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+
+ <MsiPackage Id="path_AllUsers"
+ SourceFile="path.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and PrependPath">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="path_JustForMe"
+ SourceFile="path.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and PrependPath">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+
+ <?define CompileAllCommand=-$(var.ShortVersion)$(var.Suffix32) -Wi "[TargetDir]\Lib\compileall.py" -f -x "bad_coding|badsyntax|site-packages|py2_|lib2to3\\tests|venv\\scripts" "[TargetDir]\Lib"?>
+ <ExePackage Id="compileall_AllUsers"
+ SourceFile="py.exe"
+ Compressed="yes"
+ DisplayName="!(loc.CompileAllDescription)"
+ InstallCommand='$(var.CompileAllCommand)'
+ RepairCommand='$(var.CompileAllCommand)'
+ Permanent="yes"
+ PerMachine="yes"
+ Vital="no"
+ InstallCondition="InstallAllUsers and CompileAll" />
+ <ExePackage Id="compileall_JustForMe"
+ SourceFile="py.exe"
+ Compressed="yes"
+ DisplayName="!(loc.CompileAllDescription)"
+ InstallCommand='$(var.CompileAllCommand)'
+ RepairCommand='$(var.CompileAllCommand)'
+ Permanent="yes"
+ PerMachine="no"
+ Vital="no"
+ InstallCondition="not InstallAllUsers and CompileAll" />
+ </PackageGroup>
+ </Fragment>
+</Wix> \ No newline at end of file
diff --git a/Tools/msi/bundle/packagegroups/tcltk.wxs b/Tools/msi/bundle/packagegroups/tcltk.wxs
new file mode 100644
index 0000000000..e0e3958d56
--- /dev/null
+++ b/Tools/msi/bundle/packagegroups/tcltk.wxs
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+ <PackageGroup Id="tcltk">
+ <MsiPackage Id="tcltk_AllUsers"
+ SourceFile="tcltk.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ EnableFeatureSelection="yes"
+ InstallCondition="InstallAllUsers and Include_tcltk">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="tcltk_AllUsers_pdb"
+ SourceFile="tcltk_pdb.msi"
+ Compressed="$(var.CompressPDB)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ EnableFeatureSelection="yes"
+ InstallCondition="InstallAllUsers and Include_tcltk and Include_symbols">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="tcltk_AllUsers_d"
+ SourceFile="tcltk_d.msi"
+ Compressed="$(var.CompressMSI_D)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ EnableFeatureSelection="yes"
+ InstallCondition="InstallAllUsers and Include_tcltk and Include_debug">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+
+ <MsiPackage Id="tcltk_JustForMe"
+ SourceFile="tcltk.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ EnableFeatureSelection="yes"
+ InstallCondition="not InstallAllUsers and Include_tcltk">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="tcltk_JustForMe_pdb"
+ SourceFile="tcltk_pdb.msi"
+ Compressed="$(var.CompressPDB)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ EnableFeatureSelection="yes"
+ InstallCondition="not InstallAllUsers and Include_tcltk and Include_symbols">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="tcltk_JustForMe_d"
+ SourceFile="tcltk_d.msi"
+ Compressed="$(var.CompressMSI_D)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ EnableFeatureSelection="yes"
+ InstallCondition="not InstallAllUsers and Include_tcltk and Include_debug">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ </PackageGroup>
+ </Fragment>
+</Wix> \ No newline at end of file
diff --git a/Tools/msi/bundle/packagegroups/test.wxs b/Tools/msi/bundle/packagegroups/test.wxs
new file mode 100644
index 0000000000..b64e8ff942
--- /dev/null
+++ b/Tools/msi/bundle/packagegroups/test.wxs
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+ <PackageGroup Id="test">
+ <MsiPackage Id="test_AllUsers"
+ SourceFile="test.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and Include_test">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="test_AllUsers_pdb"
+ SourceFile="test_pdb.msi"
+ Compressed="$(var.CompressPDB)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and Include_test and Include_symbols">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="test_AllUsers_d"
+ SourceFile="test_d.msi"
+ Compressed="$(var.CompressMSI_D)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and Include_test and Include_debug">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+
+ <MsiPackage Id="test_JustForMe"
+ SourceFile="test.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and Include_test">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="test_JustForMe_pdb"
+ SourceFile="test_pdb.msi"
+ Compressed="$(var.CompressPDB)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and Include_test and Include_symbols">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ <MsiPackage Id="test_JustForMe_d"
+ SourceFile="test_d.msi"
+ Compressed="$(var.CompressMSI_D)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and Include_test and Include_debug">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ </PackageGroup>
+ </Fragment>
+</Wix> \ No newline at end of file
diff --git a/Tools/msi/bundle/packagegroups/tools.wxs b/Tools/msi/bundle/packagegroups/tools.wxs
new file mode 100644
index 0000000000..06af5b588f
--- /dev/null
+++ b/Tools/msi/bundle/packagegroups/tools.wxs
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+ <PackageGroup Id="tools">
+ <MsiPackage Id="tools_AllUsers"
+ SourceFile="tools.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="yes"
+ InstallCondition="InstallAllUsers and Include_tools">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+
+ <MsiPackage Id="tools_JustForMe"
+ SourceFile="tools.msi"
+ Compressed="$(var.CompressMSI)"
+ DownloadUrl="$(var.DownloadUrl)"
+ ForcePerMachine="no"
+ InstallCondition="not InstallAllUsers and Include_tools">
+ <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+ </MsiPackage>
+ </PackageGroup>
+ </Fragment>
+</Wix> \ No newline at end of file
diff --git a/Tools/msi/bundle/releaselocal.wixproj b/Tools/msi/bundle/releaselocal.wixproj
new file mode 100644
index 0000000000..0c3dee7ad8
--- /dev/null
+++ b/Tools/msi/bundle/releaselocal.wixproj
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <ProjectGuid>{FCD43AC9-969F-49A1-8AC5-EDC27599D1EB}</ProjectGuid>
+ <OutputName>python</OutputName>
+ <OutputSuffix></OutputSuffix>
+ </PropertyGroup>
+
+ <Import Project="..\msi.props" />
+
+ <PropertyGroup>
+ <DefineConstants>
+ $(DefineConstants);
+ CompressMSI=yes;
+ CompressPDB=no;
+ CompressMSI_D=no
+ </DefineConstants>
+ </PropertyGroup>
+
+ <Import Project="bundle.targets" />
+</Project> \ No newline at end of file
diff --git a/Tools/msi/bundle/releaseweb.wixproj b/Tools/msi/bundle/releaseweb.wixproj
new file mode 100644
index 0000000000..350c735878
--- /dev/null
+++ b/Tools/msi/bundle/releaseweb.wixproj
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <ProjectGuid>{71CDE213-CB39-4BD9-B89D-BBB878689144}</ProjectGuid>
+ <OutputName>python</OutputName>
+ <OutputSuffix>webinstall</OutputSuffix>
+ </PropertyGroup>
+
+ <Import Project="..\msi.props" />
+
+ <PropertyGroup>
+ <DefineConstants>
+ $(DefineConstants);
+ CompressMSI=no;
+ CompressPDB=no;
+ CompressMSI_D=no
+ </DefineConstants>
+ </PropertyGroup>
+
+ <Import Project="bundle.targets" />
+</Project> \ No newline at end of file
diff --git a/Tools/msi/bundle/snapshot.wixproj b/Tools/msi/bundle/snapshot.wixproj
new file mode 100644
index 0000000000..8fe95a8d69
--- /dev/null
+++ b/Tools/msi/bundle/snapshot.wixproj
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <ProjectGuid>{8A4A1162-4BF9-4FF6-9A98-315F01E44932}</ProjectGuid>
+ <OutputName>python</OutputName>
+ <OutputSuffix></OutputSuffix>
+ </PropertyGroup>
+
+ <Import Project="..\msi.props" />
+
+ <PropertyGroup>
+ <DefineConstants>
+ $(DefineConstants);
+ CompressMSI=no;
+ CompressPDB=no;
+ CompressMSI_D=no;
+ </DefineConstants>
+ </PropertyGroup>
+
+ <Import Project="bundle.targets" />
+</Project> \ No newline at end of file