diff --git a/Doc/using/win_installer.png b/Doc/using/win_installer.png
index 8e258927c3..e5d5e07a14 100644
Binary files a/Doc/using/win_installer.png and b/Doc/using/win_installer.png differ
diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst
index 8e3c1103dd..213912feb3 100644
--- a/Doc/using/windows.rst
+++ b/Doc/using/windows.rst
@@ -130,6 +130,9 @@ of available options is shown below.
+---------------------------+--------------------------------------+--------------------------+
| Include_launcher | Install :ref:`launcher`. | 1 |
+---------------------------+--------------------------------------+--------------------------+
+| InstallLauncherAllUsers | Installs :ref:`launcher` for all | 1 |
+| | users. | |
++---------------------------+--------------------------------------+--------------------------+
| Include_lib | Install standard library and | 1 |
| | extension modules | |
+---------------------------+--------------------------------------+--------------------------+
@@ -145,6 +148,9 @@ of available options is shown below.
+---------------------------+--------------------------------------+--------------------------+
| SimpleInstall | Disable most install UI | 0 |
+---------------------------+--------------------------------------+--------------------------+
+| SimpleInstallDescription | A custom message to display when the | (empty) |
+| | simplified install UI is used. | |
++---------------------------+--------------------------------------+--------------------------+
For example, to silently install a default, system-wide Python installation,
you could use the following command (from an elevated command prompt)::
@@ -152,9 +158,11 @@ you could use the following command (from an elevated command prompt)::
python-3.5.0.exe /quiet InstallAllUsers=1 PrependPath=1 Include_test=0
To allow users to easily install a personal copy of Python without the test
-suite, you could provide a shortcut with the following command::
+suite, you could provide a shortcut with the following command. This will
+display a simplified initial page and disallow customization::
- python-3.5.0.exe /passive InstallAllUsers=0 Include_launcher=0 Include_test=0 SimpleInstall=1
+ python-3.5.0.exe InstallAllUsers=0 Include_launcher=0 Include_test=0
+ SimpleInstall=1 SimpleInstallDescription="Just for me, no test suite."
(Note that omitting the launcher also omits file associations, and is only
recommended for per-user installs when there is also a system-wide installation
diff --git a/Tools/msi/build.bat b/Tools/msi/build.bat
index c135b63d21..5ae512fbbe 100644
--- a/Tools/msi/build.bat
+++ b/Tools/msi/build.bat
@@ -6,12 +6,14 @@ set PCBUILD=%D%..\..\PCBuild\
set BUILDX86=
set BUILDX64=
set BUILDDOC=
+set BUILDPX=
:CheckOpts
if "%~1" EQU "-h" goto Help
if "%~1" EQU "-x86" (set BUILDX86=1) && shift && goto CheckOpts
if "%~1" EQU "-x64" (set BUILDX64=1) && shift && goto CheckOpts
if "%~1" EQU "--doc" (set BUILDDOC=1) && shift && goto CheckOpts
+if "%~1" EQU "--test-marker" (set BUILDPX=1) && shift && goto CheckOpts
if not defined BUILDX86 if not defined BUILDX64 (set BUILDX86=1) && (set BUILDX64=1)
@@ -35,22 +37,28 @@ if defined BUILDDOC (
if errorlevel 1 goto :eof
)
+set BUILD_CMD="%D%bundle\snapshot.wixproj"
+if defined BUILDPX (
+ set BUILD_CMD=%BUILD_CMD% /p:UseTestMarker=true
+)
+
if defined BUILDX86 (
"%PCBUILD%win32\python.exe" "%D%get_wix.py"
- msbuild "%D%bundle\snapshot.wixproj"
+ msbuild %BUILD_CMD%
if errorlevel 1 goto :eof
)
if defined BUILDX64 (
"%PCBUILD%amd64\python.exe" "%D%get_wix.py"
- msbuild "%D%bundle\snapshot.wixproj" /p:Platform=x64
+ msbuild /p:Platform=x64 %BUILD_CMD%
if errorlevel 1 goto :eof
)
exit /B 0
:Help
-echo build.bat [-x86] [-x64] [--doc] [-h]
+echo build.bat [-x86] [-x64] [--doc] [-h] [--test-marker]
echo.
echo -x86 Build x86 installers
echo -x64 Build x64 installers
echo --doc Build CHM documentation
+echo --test-marker Build installers with 'x' markers
diff --git a/Tools/msi/bundle/Default.thm b/Tools/msi/bundle/Default.thm
index 0c79dd50c0..4ba817642a 100644
--- a/Tools/msi/bundle/Default.thm
+++ b/Tools/msi/bundle/Default.thm
@@ -21,10 +21,11 @@
#(loc.InstallMessage)
-
-
+
+
#(loc.ShortPrependPathLabel)
+ #(loc.ShortInstallLauncherAllUsersLabel)
@@ -43,7 +44,7 @@
#(loc.InstallHeader)
-
+
@@ -63,7 +64,8 @@
#(loc.Include_testLabel)
#(loc.Include_testHelpLabel)
- #(loc.Include_launcherLabel)
+ #(loc.Include_launcherLabel)
+ #(loc.InstallLauncherAllUsersLabel)
#(loc.Include_launcherHelpLabel)
diff --git a/Tools/msi/bundle/Default.wxl b/Tools/msi/bundle/Default.wxl
index 19ba7eb14b..c12fb65a2e 100644
--- a/Tools/msi/bundle/Default.wxl
+++ b/Tools/msi/bundle/Default.wxl
@@ -50,7 +50,9 @@ Creates shortcuts and file associations
C&ustomize installation
Choose location and features
&Install
- Uses setting preselected by your administrator
+ Use settings preselected by your administrator
+
+[SimpleInstallDescription]
&Upgrade Now
[TargetDir]
@@ -81,7 +83,9 @@ Select Customize to review current options.
Create shortcuts for installed applications
Add Python to &environment variables
Add &Python [ShortVersion] to PATH
- Install as &Administrator
+ Install for &all users
+ for &all users (requires elevation)
+ Install &launcher for all users (recommended)
&Precompile standard library
Install debugging &symbols
Install debu&g binaries (requires VS 2015 or later)
diff --git a/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp b/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp
index 2166304137..2d8f04d9d5 100644
--- a/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp
+++ b/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp
@@ -87,11 +87,13 @@ enum CONTROL_ID {
ID_INSTALL_UPGRADE_BUTTON,
ID_INSTALL_UPGRADE_CUSTOM_BUTTON,
ID_INSTALL_CANCEL_BUTTON,
-
+ ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX,
+
// Customize Page
ID_TARGETDIR_EDITBOX,
ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX,
ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX,
+ ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX,
ID_CUSTOM_COMPILE_ALL_CHECKBOX,
ID_CUSTOM_BROWSE_BUTTON,
ID_CUSTOM_BROWSE_BUTTON_LABEL,
@@ -150,10 +152,12 @@ static THEME_ASSIGN_CONTROL_ID CONTROL_ID_NAMES[] = {
{ ID_INSTALL_UPGRADE_BUTTON, L"InstallUpgradeButton" },
{ ID_INSTALL_UPGRADE_CUSTOM_BUTTON, L"InstallUpgradeCustomButton" },
{ ID_INSTALL_CANCEL_BUTTON, L"InstallCancelButton" },
+ { ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, L"InstallLauncherAllUsers" },
{ ID_TARGETDIR_EDITBOX, L"TargetDir" },
{ ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, L"AssociateFiles" },
{ ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX, L"InstallAllUsers" },
+ { ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, L"CustomInstallLauncherAllUsers" },
{ ID_CUSTOM_COMPILE_ALL_CHECKBOX, L"CompileAll" },
{ ID_CUSTOM_BROWSE_BUTTON, L"CustomBrowseButton" },
{ ID_CUSTOM_BROWSE_BUTTON_LABEL, L"CustomBrowseButtonLabel" },
@@ -261,10 +265,11 @@ class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication {
LPWSTR defaultDir = nullptr;
LPWSTR targetDir = nullptr;
LONGLONG elevated, crtInstalled, installAllUsers;
- BOOL checked;
+ BOOL checked, launcherChecked;
WCHAR wzPath[MAX_PATH] = { };
BROWSEINFOW browseInfo = { };
PIDLIST_ABSOLUTE pidl = nullptr;
+ DWORD pageId;
HRESULT hr = S_OK;
switch(id) {
@@ -278,7 +283,7 @@ class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication {
case ID_INSTALL_BUTTON:
SavePageSettings();
- if (!QueryElevateForCrtInstall()) {
+ if (!WillElevate() && !QueryElevateForCrtInstall()) {
break;
}
@@ -344,18 +349,32 @@ class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication {
ReleaseStr(targetDir);
}
- checked = ThemeIsControlChecked(_theme, ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX);
- if (!checked && !QueryElevateForCrtInstall()) {
+ if (!WillElevate() && !QueryElevateForCrtInstall()) {
break;
}
OnPlan(_command.action);
break;
+ case ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX:
+ checked = ThemeIsControlChecked(_theme, ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX);
+ _engine->SetVariableNumeric(L"InstallLauncherAllUsers", checked);
+
+ ThemeControlElevates(_theme, ID_INSTALL_BUTTON, WillElevate());
+ break;
+
+ case ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX:
+ checked = ThemeIsControlChecked(_theme, ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX);
+ _engine->SetVariableNumeric(L"InstallLauncherAllUsers", checked);
+
+ ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, WillElevate());
+ 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));
+ _engine->SetVariableNumeric(L"InstallAllUsers", checked);
+
+ ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, WillElevate());
ThemeControlEnable(_theme, ID_CUSTOM_BROWSE_BUTTON_LABEL, !checked);
if (checked) {
_engine->SetVariableNumeric(L"CompileAll", 1);
@@ -416,6 +435,7 @@ class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication {
case ID_MODIFY_BUTTON:
// Some variables cannot be modified
_engine->SetVariableString(L"InstallAllUsersState", L"disable");
+ _engine->SetVariableString(L"InstallLauncherAllUsersState", L"disable");
_engine->SetVariableString(L"TargetDirState", L"disable");
_engine->SetVariableString(L"CustomBrowseButtonState", L"disable");
_modifying = TRUE;
@@ -437,35 +457,36 @@ class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication {
void InstallPage_Show() {
// Ensure the All Users install button has a UAC shield
- LONGLONG elevated, installAll;
-
- if (FAILED(BalGetNumericVariable(L"WixBundleElevated", &elevated))) {
- elevated = 0;
- }
-
-
- if (SUCCEEDED(BalGetNumericVariable(L"InstallAllUsers", &installAll)) && installAll && !elevated) {
- ThemeControlElevates(_theme, ID_INSTALL_BUTTON, TRUE);
- ThemeControlElevates(_theme, ID_INSTALL_SIMPLE_BUTTON, TRUE);
- ThemeControlElevates(_theme, ID_INSTALL_UPGRADE_BUTTON, TRUE);
- }
+ BOOL elevated = WillElevate();
+ ThemeControlElevates(_theme, ID_INSTALL_BUTTON, elevated);
+ ThemeControlElevates(_theme, ID_INSTALL_SIMPLE_BUTTON, elevated);
+ ThemeControlElevates(_theme, ID_INSTALL_UPGRADE_BUTTON, elevated);
}
void Custom1Page_Show() {
+ LONGLONG installLauncherAllUsers;
+
+ if (FAILED(BalGetNumericVariable(L"InstallLauncherAllUsers", &installLauncherAllUsers))) {
+ installLauncherAllUsers = 0;
+ }
+
+ ThemeSendControlMessage(_theme, ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, BM_SETCHECK,
+ installLauncherAllUsers ? BST_CHECKED : BST_UNCHECKED, 0);
}
void Custom2Page_Show() {
HRESULT hr;
- LONGLONG installAll, elevated, includeLauncher;
+ LONGLONG installAll, includeLauncher;
- if (FAILED(BalGetNumericVariable(L"WixBundleElevated", &elevated))) {
- elevated = 0;
+ if (FAILED(BalGetNumericVariable(L"InstallAllUsers", &installAll))) {
+ installAll = 0;
}
- if (SUCCEEDED(BalGetNumericVariable(L"InstallAllUsers", &installAll))) {
- ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, installAll && !elevated);
+
+ if (WillElevate()) {
+ ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, TRUE);
ThemeShowControl(_theme, ID_CUSTOM_BROWSE_BUTTON_LABEL, SW_HIDE);
} else {
- installAll = 0;
+ ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, FALSE);
ThemeShowControl(_theme, ID_CUSTOM_BROWSE_BUTTON_LABEL, SW_SHOW);
}
@@ -1839,8 +1860,8 @@ private:
COLORREF fg = fnt->crForeground, bg = fnt->crBackground;
*brush = fnt->hBackground;
RemapColor(&fg, &bg, brush);
- SetTextColor(hDC, fg);
- SetBkColor(hDC, bg);
+ ::SetTextColor(hDC, fg);
+ ::SetBkColor(hDC, bg);
return TRUE;
}
@@ -2080,6 +2101,7 @@ private:
for (DWORD i = 0; i < pPage->cControlIndices; ++i) {
THEME_CONTROL* pControl = _theme->rgControls + pPage->rgdwControlIndices[i];
+ BOOL enableControl = TRUE;
// If this is a named control, try to set its default state.
if (pControl->sczName && *pControl->sczName) {
@@ -2091,7 +2113,7 @@ private:
// If the control value isn't set then disable it.
if (!SUCCEEDED(hr)) {
- ThemeControlEnable(_theme, pControl->wId, FALSE);
+ enableControl = FALSE;
} else {
ThemeSendControlMessage(
_theme,
@@ -2110,13 +2132,25 @@ private:
LPWSTR controlState = nullptr;
hr = BalGetStringVariable(controlName, &controlState);
if (SUCCEEDED(hr) && controlState && *controlState) {
+ if (controlState[0] == '[') {
+ LPWSTR formatted = nullptr;
+ if (SUCCEEDED(BalFormatString(controlState, &formatted))) {
+ StrFree(controlState);
+ controlState = formatted;
+ }
+ }
+
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);
+ enableControl = 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);
+ } else {
+ // An explicit state can override the lack of a
+ // backing variable.
+ enableControl = TRUE;
}
}
StrFree(controlState);
@@ -2124,6 +2158,8 @@ private:
StrFree(controlName);
}
+ ThemeControlEnable(_theme, pControl->wId, enableControl);
+
// 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
@@ -2405,6 +2441,16 @@ private:
}
}
+ BOOL WillElevate() {
+ static BAL_CONDITION WILL_ELEVATE_CONDITION = {
+ L"not WixBundleElevated and (InstallAllUsers or (InstallLauncherAllUsers and Include_launcher))",
+ L""
+ };
+ BOOL result;
+
+ return SUCCEEDED(BalConditionEvaluate(&WILL_ELEVATE_CONDITION, _engine, &result, nullptr)) && result;
+ }
+
BOOL IsCrtInstalled() {
if (_crtInstalledToken > 0) {
return TRUE;
diff --git a/Tools/msi/bundle/bundle.targets b/Tools/msi/bundle/bundle.targets
index 1ff00015b9..b77646bec4 100644
--- a/Tools/msi/bundle/bundle.targets
+++ b/Tools/msi/bundle/bundle.targets
@@ -88,7 +88,7 @@
-
diff --git a/Tools/msi/bundle/bundle.wxs b/Tools/msi/bundle/bundle.wxs
index 678dac4e87..761384e7f2 100644
--- a/Tools/msi/bundle/bundle.wxs
+++ b/Tools/msi/bundle/bundle.wxs
@@ -24,15 +24,16 @@
+
-
+
-
+
-
+
@@ -45,6 +46,8 @@
+
+
@@ -67,6 +70,7 @@
+
diff --git a/Tools/msi/bundle/packagegroups/launcher.wxs b/Tools/msi/bundle/packagegroups/launcher.wxs
index d09175f7dc..77c6ac5dde 100644
--- a/Tools/msi/bundle/packagegroups/launcher.wxs
+++ b/Tools/msi/bundle/packagegroups/launcher.wxs
@@ -9,7 +9,7 @@
DownloadUrl="$(var.DownloadUrl)"
ForcePerMachine="yes"
EnableFeatureSelection="yes"
- InstallCondition="InstallAllUsers and Include_launcher" />
+ InstallCondition="(InstallAllUsers or InstallLauncherAllUsers) and Include_launcher" />
+ InstallCondition="not (InstallAllUsers or InstallLauncherAllUsers) and Include_launcher" />
\ No newline at end of file
diff --git a/Tools/msi/common.wxs b/Tools/msi/common.wxs
index cc39540a2b..9f96bd3f4c 100644
--- a/Tools/msi/common.wxs
+++ b/Tools/msi/common.wxs
@@ -103,7 +103,7 @@
diff --git a/Tools/msi/launcher/launcher_files.wxs b/Tools/msi/launcher/launcher_files.wxs
index 589dee5654..7148258a00 100644
--- a/Tools/msi/launcher/launcher_files.wxs
+++ b/Tools/msi/launcher/launcher_files.wxs
@@ -3,22 +3,22 @@
-
+
-
+
NOT ALLUSERS=1
-
+
ALLUSERS=1
-
+
diff --git a/Tools/msi/launcher/launcher_reg.wxs b/Tools/msi/launcher/launcher_reg.wxs
index eef71c8d39..d00f442fd0 100644
--- a/Tools/msi/launcher/launcher_reg.wxs
+++ b/Tools/msi/launcher/launcher_reg.wxs
@@ -3,7 +3,7 @@
-
+
diff --git a/Tools/msi/msi.props b/Tools/msi/msi.props
index 2d13aef698..29be129328 100644
--- a/Tools/msi/msi.props
+++ b/Tools/msi/msi.props
@@ -81,10 +81,10 @@
$(DefineConstants);CRTRedist=$(CRTRedist);
-
+
$(DefineConstants);TestPrefix=;FileExtension=py;
-
+
$(DefineConstants);TestPrefix=x;FileExtension=px;