Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp
12 views
1
//-------------------------------------------------------------------------------------------------
2
// <copyright file="WixStandardBootstrapperApplication.cpp" company="Outercurve Foundation">
3
// Copyright (c) 2004, Outercurve Foundation.
4
// This software is released under Microsoft Reciprocal License (MS-RL).
5
// The license and further copyright text can be found in the file
6
// LICENSE.TXT at the root directory of the distribution.
7
// </copyright>
8
//-------------------------------------------------------------------------------------------------
9
10
11
#include "pch.h"
12
13
static const LPCWSTR PYBA_WINDOW_CLASS = L"PythonBA";
14
static const DWORD PYBA_ACQUIRE_PERCENTAGE = 30;
15
static const LPCWSTR PYBA_VARIABLE_BUNDLE_FILE_VERSION = L"WixBundleFileVersion";
16
17
enum PYBA_STATE {
18
PYBA_STATE_INITIALIZING,
19
PYBA_STATE_INITIALIZED,
20
PYBA_STATE_HELP,
21
PYBA_STATE_DETECTING,
22
PYBA_STATE_DETECTED,
23
PYBA_STATE_PLANNING,
24
PYBA_STATE_PLANNED,
25
PYBA_STATE_APPLYING,
26
PYBA_STATE_CACHING,
27
PYBA_STATE_CACHED,
28
PYBA_STATE_EXECUTING,
29
PYBA_STATE_EXECUTED,
30
PYBA_STATE_APPLIED,
31
PYBA_STATE_FAILED,
32
};
33
34
static const int WM_PYBA_SHOW_HELP = WM_APP + 100;
35
static const int WM_PYBA_DETECT_PACKAGES = WM_APP + 101;
36
static const int WM_PYBA_PLAN_PACKAGES = WM_APP + 102;
37
static const int WM_PYBA_APPLY_PACKAGES = WM_APP + 103;
38
static const int WM_PYBA_CHANGE_STATE = WM_APP + 104;
39
static const int WM_PYBA_SHOW_FAILURE = WM_APP + 105;
40
41
// This enum must be kept in the same order as the PAGE_NAMES array.
42
enum PAGE {
43
PAGE_LOADING,
44
PAGE_HELP,
45
PAGE_INSTALL,
46
PAGE_UPGRADE,
47
PAGE_SIMPLE_INSTALL,
48
PAGE_CUSTOM1,
49
PAGE_CUSTOM2,
50
PAGE_MODIFY,
51
PAGE_PROGRESS,
52
PAGE_PROGRESS_PASSIVE,
53
PAGE_SUCCESS,
54
PAGE_FAILURE,
55
COUNT_PAGE,
56
};
57
58
// This array must be kept in the same order as the PAGE enum.
59
static LPCWSTR PAGE_NAMES[] = {
60
L"Loading",
61
L"Help",
62
L"Install",
63
L"Upgrade",
64
L"SimpleInstall",
65
L"Custom1",
66
L"Custom2",
67
L"Modify",
68
L"Progress",
69
L"ProgressPassive",
70
L"Success",
71
L"Failure",
72
};
73
74
enum CONTROL_ID {
75
// Non-paged controls
76
ID_CLOSE_BUTTON = THEME_FIRST_ASSIGN_CONTROL_ID,
77
ID_MINIMIZE_BUTTON,
78
79
// Welcome page
80
ID_INSTALL_BUTTON,
81
ID_INSTALL_CUSTOM_BUTTON,
82
ID_INSTALL_SIMPLE_BUTTON,
83
ID_INSTALL_UPGRADE_BUTTON,
84
ID_INSTALL_UPGRADE_CUSTOM_BUTTON,
85
ID_INSTALL_CANCEL_BUTTON,
86
ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX,
87
88
// Customize Page
89
ID_TARGETDIR_EDITBOX,
90
ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX,
91
ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX,
92
ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX,
93
ID_CUSTOM_INCLUDE_LAUNCHER_HELP_LABEL,
94
ID_CUSTOM_COMPILE_ALL_CHECKBOX,
95
ID_CUSTOM_BROWSE_BUTTON,
96
ID_CUSTOM_BROWSE_BUTTON_LABEL,
97
ID_CUSTOM_INSTALL_BUTTON,
98
ID_CUSTOM_NEXT_BUTTON,
99
ID_CUSTOM1_BACK_BUTTON,
100
ID_CUSTOM2_BACK_BUTTON,
101
ID_CUSTOM1_CANCEL_BUTTON,
102
ID_CUSTOM2_CANCEL_BUTTON,
103
104
// Modify page
105
ID_MODIFY_BUTTON,
106
ID_REPAIR_BUTTON,
107
ID_UNINSTALL_BUTTON,
108
ID_MODIFY_CANCEL_BUTTON,
109
110
// Progress page
111
ID_CACHE_PROGRESS_PACKAGE_TEXT,
112
ID_CACHE_PROGRESS_BAR,
113
ID_CACHE_PROGRESS_TEXT,
114
115
ID_EXECUTE_PROGRESS_PACKAGE_TEXT,
116
ID_EXECUTE_PROGRESS_BAR,
117
ID_EXECUTE_PROGRESS_TEXT,
118
ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT,
119
120
ID_OVERALL_PROGRESS_PACKAGE_TEXT,
121
ID_OVERALL_PROGRESS_BAR,
122
ID_OVERALL_CALCULATED_PROGRESS_BAR,
123
ID_OVERALL_PROGRESS_TEXT,
124
125
ID_PROGRESS_CANCEL_BUTTON,
126
127
// Success page
128
ID_SUCCESS_TEXT,
129
ID_SUCCESS_RESTART_TEXT,
130
ID_SUCCESS_RESTART_BUTTON,
131
ID_SUCCESS_CANCEL_BUTTON,
132
ID_SUCCESS_MAX_PATH_BUTTON,
133
134
// Failure page
135
ID_FAILURE_LOGFILE_LINK,
136
ID_FAILURE_MESSAGE_TEXT,
137
ID_FAILURE_RESTART_TEXT,
138
ID_FAILURE_RESTART_BUTTON,
139
ID_FAILURE_CANCEL_BUTTON
140
};
141
142
static THEME_ASSIGN_CONTROL_ID CONTROL_ID_NAMES[] = {
143
{ ID_CLOSE_BUTTON, L"CloseButton" },
144
{ ID_MINIMIZE_BUTTON, L"MinimizeButton" },
145
146
{ ID_INSTALL_BUTTON, L"InstallButton" },
147
{ ID_INSTALL_CUSTOM_BUTTON, L"InstallCustomButton" },
148
{ ID_INSTALL_SIMPLE_BUTTON, L"InstallSimpleButton" },
149
{ ID_INSTALL_UPGRADE_BUTTON, L"InstallUpgradeButton" },
150
{ ID_INSTALL_UPGRADE_CUSTOM_BUTTON, L"InstallUpgradeCustomButton" },
151
{ ID_INSTALL_CANCEL_BUTTON, L"InstallCancelButton" },
152
{ ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, L"InstallLauncherAllUsers" },
153
154
{ ID_TARGETDIR_EDITBOX, L"TargetDir" },
155
{ ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, L"AssociateFiles" },
156
{ ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX, L"InstallAllUsers" },
157
{ ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, L"CustomInstallLauncherAllUsers" },
158
{ ID_CUSTOM_INCLUDE_LAUNCHER_HELP_LABEL, L"Include_launcherHelp" },
159
{ ID_CUSTOM_COMPILE_ALL_CHECKBOX, L"CompileAll" },
160
{ ID_CUSTOM_BROWSE_BUTTON, L"CustomBrowseButton" },
161
{ ID_CUSTOM_BROWSE_BUTTON_LABEL, L"CustomBrowseButtonLabel" },
162
{ ID_CUSTOM_INSTALL_BUTTON, L"CustomInstallButton" },
163
{ ID_CUSTOM_NEXT_BUTTON, L"CustomNextButton" },
164
{ ID_CUSTOM1_BACK_BUTTON, L"Custom1BackButton" },
165
{ ID_CUSTOM2_BACK_BUTTON, L"Custom2BackButton" },
166
{ ID_CUSTOM1_CANCEL_BUTTON, L"Custom1CancelButton" },
167
{ ID_CUSTOM2_CANCEL_BUTTON, L"Custom2CancelButton" },
168
169
{ ID_MODIFY_BUTTON, L"ModifyButton" },
170
{ ID_REPAIR_BUTTON, L"RepairButton" },
171
{ ID_UNINSTALL_BUTTON, L"UninstallButton" },
172
{ ID_MODIFY_CANCEL_BUTTON, L"ModifyCancelButton" },
173
174
{ ID_CACHE_PROGRESS_PACKAGE_TEXT, L"CacheProgressPackageText" },
175
{ ID_CACHE_PROGRESS_BAR, L"CacheProgressbar" },
176
{ ID_CACHE_PROGRESS_TEXT, L"CacheProgressText" },
177
{ ID_EXECUTE_PROGRESS_PACKAGE_TEXT, L"ExecuteProgressPackageText" },
178
{ ID_EXECUTE_PROGRESS_BAR, L"ExecuteProgressbar" },
179
{ ID_EXECUTE_PROGRESS_TEXT, L"ExecuteProgressText" },
180
{ ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, L"ExecuteProgressActionDataText" },
181
{ ID_OVERALL_PROGRESS_PACKAGE_TEXT, L"OverallProgressPackageText" },
182
{ ID_OVERALL_PROGRESS_BAR, L"OverallProgressbar" },
183
{ ID_OVERALL_CALCULATED_PROGRESS_BAR, L"OverallCalculatedProgressbar" },
184
{ ID_OVERALL_PROGRESS_TEXT, L"OverallProgressText" },
185
{ ID_PROGRESS_CANCEL_BUTTON, L"ProgressCancelButton" },
186
187
{ ID_SUCCESS_TEXT, L"SuccessText" },
188
{ ID_SUCCESS_RESTART_TEXT, L"SuccessRestartText" },
189
{ ID_SUCCESS_RESTART_BUTTON, L"SuccessRestartButton" },
190
{ ID_SUCCESS_CANCEL_BUTTON, L"SuccessCancelButton" },
191
{ ID_SUCCESS_MAX_PATH_BUTTON, L"SuccessMaxPathButton" },
192
193
{ ID_FAILURE_LOGFILE_LINK, L"FailureLogFileLink" },
194
{ ID_FAILURE_MESSAGE_TEXT, L"FailureMessageText" },
195
{ ID_FAILURE_RESTART_TEXT, L"FailureRestartText" },
196
{ ID_FAILURE_RESTART_BUTTON, L"FailureRestartButton" },
197
{ ID_FAILURE_CANCEL_BUTTON, L"FailureCancelButton" },
198
};
199
200
static struct { LPCWSTR regName; LPCWSTR variableName; } OPTIONAL_FEATURES[] = {
201
{ L"core_d", L"Include_debug" },
202
{ L"core_pdb", L"Include_symbols" },
203
{ L"dev", L"Include_dev" },
204
{ L"doc", L"Include_doc" },
205
{ L"exe", L"Include_exe" },
206
{ L"lib", L"Include_lib" },
207
{ L"path", L"PrependPath" },
208
{ L"appendpath", L"AppendPath" },
209
{ L"pip", L"Include_pip" },
210
{ L"tcltk", L"Include_tcltk" },
211
{ L"test", L"Include_test" },
212
{ L"tools", L"Include_tools" },
213
{ L"Shortcuts", L"Shortcuts" },
214
// Include_launcher and AssociateFiles are handled separately and so do
215
// not need to be included in this list.
216
{ nullptr, nullptr }
217
};
218
219
220
221
class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication {
222
void ShowPage(DWORD newPageId) {
223
// Process each control for special handling in the new page.
224
ProcessPageControls(ThemeGetPage(_theme, newPageId));
225
226
// Enable disable controls per-page.
227
if (_pageIds[PAGE_INSTALL] == newPageId ||
228
_pageIds[PAGE_SIMPLE_INSTALL] == newPageId ||
229
_pageIds[PAGE_UPGRADE] == newPageId) {
230
InstallPage_Show();
231
} else if (_pageIds[PAGE_CUSTOM1] == newPageId) {
232
Custom1Page_Show();
233
} else if (_pageIds[PAGE_CUSTOM2] == newPageId) {
234
Custom2Page_Show();
235
} else if (_pageIds[PAGE_MODIFY] == newPageId) {
236
ModifyPage_Show();
237
} else if (_pageIds[PAGE_SUCCESS] == newPageId) {
238
SuccessPage_Show();
239
} else if (_pageIds[PAGE_FAILURE] == newPageId) {
240
FailurePage_Show();
241
}
242
243
// Prevent repainting while switching page to avoid ugly flickering
244
_suppressPaint = TRUE;
245
ThemeShowPage(_theme, newPageId, SW_SHOW);
246
ThemeShowPage(_theme, _visiblePageId, SW_HIDE);
247
_suppressPaint = FALSE;
248
InvalidateRect(_theme->hwndParent, nullptr, TRUE);
249
_visiblePageId = newPageId;
250
251
// On the install page set the focus to the install button or
252
// the next enabled control if install is disabled
253
if (_pageIds[PAGE_INSTALL] == newPageId) {
254
ThemeSetFocus(_theme, ID_INSTALL_BUTTON);
255
} else if (_pageIds[PAGE_SIMPLE_INSTALL] == newPageId) {
256
ThemeSetFocus(_theme, ID_INSTALL_SIMPLE_BUTTON);
257
}
258
}
259
260
//
261
// Handles control clicks
262
//
263
void OnCommand(CONTROL_ID id) {
264
LPWSTR defaultDir = nullptr;
265
LPWSTR targetDir = nullptr;
266
LONGLONG elevated, crtInstalled, installAllUsers;
267
BOOL checked, launcherChecked;
268
WCHAR wzPath[MAX_PATH] = { };
269
BROWSEINFOW browseInfo = { };
270
PIDLIST_ABSOLUTE pidl = nullptr;
271
DWORD pageId;
272
HRESULT hr = S_OK;
273
274
switch(id) {
275
case ID_CLOSE_BUTTON:
276
OnClickCloseButton();
277
break;
278
279
// Install commands
280
case ID_INSTALL_SIMPLE_BUTTON: __fallthrough;
281
case ID_INSTALL_UPGRADE_BUTTON: __fallthrough;
282
case ID_INSTALL_BUTTON:
283
SavePageSettings();
284
285
hr = BalGetNumericVariable(L"InstallAllUsers", &installAllUsers);
286
ExitOnFailure(hr, L"Failed to get install scope");
287
288
hr = _engine->SetVariableNumeric(L"CompileAll", installAllUsers);
289
ExitOnFailure(hr, L"Failed to update CompileAll");
290
291
hr = EnsureTargetDir();
292
ExitOnFailure(hr, L"Failed to set TargetDir");
293
294
OnPlan(BOOTSTRAPPER_ACTION_INSTALL);
295
break;
296
297
case ID_CUSTOM1_BACK_BUTTON:
298
SavePageSettings();
299
if (_modifying) {
300
GoToPage(PAGE_MODIFY);
301
} else if (_upgrading) {
302
GoToPage(PAGE_UPGRADE);
303
} else {
304
GoToPage(PAGE_INSTALL);
305
}
306
break;
307
308
case ID_INSTALL_CUSTOM_BUTTON: __fallthrough;
309
case ID_INSTALL_UPGRADE_CUSTOM_BUTTON: __fallthrough;
310
case ID_CUSTOM2_BACK_BUTTON:
311
SavePageSettings();
312
GoToPage(PAGE_CUSTOM1);
313
break;
314
315
case ID_CUSTOM_NEXT_BUTTON:
316
SavePageSettings();
317
GoToPage(PAGE_CUSTOM2);
318
break;
319
320
case ID_CUSTOM_INSTALL_BUTTON:
321
SavePageSettings();
322
323
hr = EnsureTargetDir();
324
ExitOnFailure(hr, L"Failed to set TargetDir");
325
326
hr = BalGetStringVariable(L"TargetDir", &targetDir);
327
if (SUCCEEDED(hr)) {
328
// TODO: Check whether directory exists and contains another installation
329
ReleaseStr(targetDir);
330
}
331
332
OnPlan(_command.action);
333
break;
334
335
case ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX:
336
checked = ThemeIsControlChecked(_theme, ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX);
337
_engine->SetVariableNumeric(L"InstallLauncherAllUsers", checked);
338
339
ThemeControlElevates(_theme, ID_INSTALL_BUTTON, WillElevate());
340
break;
341
342
case ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX:
343
checked = ThemeIsControlChecked(_theme, ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX);
344
_engine->SetVariableNumeric(L"InstallLauncherAllUsers", checked);
345
346
ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, WillElevate());
347
break;
348
349
case ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX:
350
checked = ThemeIsControlChecked(_theme, ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX);
351
_engine->SetVariableNumeric(L"InstallAllUsers", checked);
352
353
ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, WillElevate());
354
ThemeControlEnable(_theme, ID_CUSTOM_BROWSE_BUTTON_LABEL, !checked);
355
if (checked) {
356
_engine->SetVariableNumeric(L"CompileAll", 1);
357
ThemeSendControlMessage(_theme, ID_CUSTOM_COMPILE_ALL_CHECKBOX, BM_SETCHECK, BST_CHECKED, 0);
358
}
359
ThemeGetTextControl(_theme, ID_TARGETDIR_EDITBOX, &targetDir);
360
if (targetDir) {
361
// Check the current value against the default to see
362
// if we should switch it automatically.
363
hr = BalGetStringVariable(
364
checked ? L"DefaultJustForMeTargetDir" : L"DefaultAllUsersTargetDir",
365
&defaultDir
366
);
367
368
if (SUCCEEDED(hr) && defaultDir) {
369
LPWSTR formatted = nullptr;
370
if (defaultDir[0] && SUCCEEDED(BalFormatString(defaultDir, &formatted))) {
371
if (wcscmp(formatted, targetDir) == 0) {
372
ReleaseStr(defaultDir);
373
defaultDir = nullptr;
374
ReleaseStr(formatted);
375
formatted = nullptr;
376
377
hr = BalGetStringVariable(
378
checked ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir",
379
&defaultDir
380
);
381
if (SUCCEEDED(hr) && defaultDir && defaultDir[0] && SUCCEEDED(BalFormatString(defaultDir, &formatted))) {
382
ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, formatted);
383
ReleaseStr(formatted);
384
}
385
} else {
386
ReleaseStr(formatted);
387
}
388
}
389
390
ReleaseStr(defaultDir);
391
}
392
}
393
break;
394
395
case ID_CUSTOM_BROWSE_BUTTON:
396
browseInfo.hwndOwner = _hWnd;
397
browseInfo.pszDisplayName = wzPath;
398
browseInfo.lpszTitle = _theme->sczCaption;
399
browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
400
pidl = ::SHBrowseForFolderW(&browseInfo);
401
if (pidl && ::SHGetPathFromIDListW(pidl, wzPath)) {
402
ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, wzPath);
403
}
404
405
if (pidl) {
406
::CoTaskMemFree(pidl);
407
}
408
break;
409
410
// Modify commands
411
case ID_MODIFY_BUTTON:
412
// Some variables cannot be modified
413
_engine->SetVariableString(L"InstallAllUsersState", L"disable");
414
_engine->SetVariableString(L"InstallLauncherAllUsersState", L"disable");
415
_engine->SetVariableString(L"TargetDirState", L"disable");
416
_engine->SetVariableString(L"CustomBrowseButtonState", L"disable");
417
_modifying = TRUE;
418
GoToPage(PAGE_CUSTOM1);
419
break;
420
421
case ID_REPAIR_BUTTON:
422
OnPlan(BOOTSTRAPPER_ACTION_REPAIR);
423
break;
424
425
case ID_UNINSTALL_BUTTON:
426
OnPlan(BOOTSTRAPPER_ACTION_UNINSTALL);
427
break;
428
429
case ID_SUCCESS_MAX_PATH_BUTTON:
430
EnableMaxPathSupport();
431
ThemeControlEnable(_theme, ID_SUCCESS_MAX_PATH_BUTTON, FALSE);
432
break;
433
}
434
435
LExit:
436
return;
437
}
438
439
void InstallPage_Show() {
440
// Ensure the All Users install button has a UAC shield
441
BOOL elevated = WillElevate();
442
ThemeControlElevates(_theme, ID_INSTALL_BUTTON, elevated);
443
ThemeControlElevates(_theme, ID_INSTALL_SIMPLE_BUTTON, elevated);
444
ThemeControlElevates(_theme, ID_INSTALL_UPGRADE_BUTTON, elevated);
445
}
446
447
void Custom1Page_Show() {
448
LONGLONG installLauncherAllUsers;
449
450
if (FAILED(BalGetNumericVariable(L"InstallLauncherAllUsers", &installLauncherAllUsers))) {
451
installLauncherAllUsers = 0;
452
}
453
454
ThemeSendControlMessage(_theme, ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, BM_SETCHECK,
455
installLauncherAllUsers ? BST_CHECKED : BST_UNCHECKED, 0);
456
457
LOC_STRING *pLocString = nullptr;
458
LPCWSTR locKey = L"#(loc.Include_launcherHelp)";
459
LONGLONG detectedLauncher;
460
461
if (SUCCEEDED(BalGetNumericVariable(L"DetectedLauncher", &detectedLauncher)) && detectedLauncher) {
462
locKey = L"#(loc.Include_launcherRemove)";
463
} else if (SUCCEEDED(BalGetNumericVariable(L"DetectedOldLauncher", &detectedLauncher)) && detectedLauncher) {
464
locKey = L"#(loc.Include_launcherUpgrade)";
465
}
466
467
if (SUCCEEDED(LocGetString(_wixLoc, locKey, &pLocString)) && pLocString) {
468
ThemeSetTextControl(_theme, ID_CUSTOM_INCLUDE_LAUNCHER_HELP_LABEL, pLocString->wzText);
469
}
470
}
471
472
void Custom2Page_Show() {
473
HRESULT hr;
474
LONGLONG installAll, includeLauncher;
475
476
if (FAILED(BalGetNumericVariable(L"InstallAllUsers", &installAll))) {
477
installAll = 0;
478
}
479
480
if (WillElevate()) {
481
ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, TRUE);
482
ThemeShowControl(_theme, ID_CUSTOM_BROWSE_BUTTON_LABEL, SW_HIDE);
483
} else {
484
ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, FALSE);
485
ThemeShowControl(_theme, ID_CUSTOM_BROWSE_BUTTON_LABEL, SW_SHOW);
486
}
487
488
if (SUCCEEDED(BalGetNumericVariable(L"Include_launcher", &includeLauncher)) && includeLauncher) {
489
ThemeControlEnable(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, TRUE);
490
} else {
491
ThemeSendControlMessage(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, BM_SETCHECK, BST_UNCHECKED, 0);
492
ThemeControlEnable(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, FALSE);
493
}
494
495
LPWSTR targetDir = nullptr;
496
hr = BalGetStringVariable(L"TargetDir", &targetDir);
497
if (SUCCEEDED(hr) && targetDir && targetDir[0]) {
498
ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, targetDir);
499
StrFree(targetDir);
500
} else if (SUCCEEDED(hr)) {
501
StrFree(targetDir);
502
targetDir = nullptr;
503
504
LPWSTR defaultTargetDir = nullptr;
505
hr = BalGetStringVariable(L"DefaultCustomTargetDir", &defaultTargetDir);
506
if (SUCCEEDED(hr) && defaultTargetDir && !defaultTargetDir[0]) {
507
StrFree(defaultTargetDir);
508
defaultTargetDir = nullptr;
509
510
hr = BalGetStringVariable(
511
installAll ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir",
512
&defaultTargetDir
513
);
514
}
515
if (SUCCEEDED(hr) && defaultTargetDir) {
516
if (defaultTargetDir[0] && SUCCEEDED(BalFormatString(defaultTargetDir, &targetDir))) {
517
ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, targetDir);
518
StrFree(targetDir);
519
}
520
StrFree(defaultTargetDir);
521
}
522
}
523
}
524
525
void ModifyPage_Show() {
526
ThemeControlEnable(_theme, ID_REPAIR_BUTTON, !_suppressRepair);
527
}
528
529
void SuccessPage_Show() {
530
// on the "Success" page, check if the restart button should be enabled.
531
BOOL showRestartButton = FALSE;
532
LOC_STRING *successText = nullptr;
533
HRESULT hr = S_OK;
534
535
if (_restartRequired) {
536
if (BOOTSTRAPPER_RESTART_PROMPT == _command.restart) {
537
showRestartButton = TRUE;
538
}
539
}
540
541
switch (_plannedAction) {
542
case BOOTSTRAPPER_ACTION_INSTALL:
543
hr = LocGetString(_wixLoc, L"#(loc.SuccessInstallMessage)", &successText);
544
break;
545
case BOOTSTRAPPER_ACTION_MODIFY:
546
hr = LocGetString(_wixLoc, L"#(loc.SuccessModifyMessage)", &successText);
547
break;
548
case BOOTSTRAPPER_ACTION_REPAIR:
549
hr = LocGetString(_wixLoc, L"#(loc.SuccessRepairMessage)", &successText);
550
break;
551
case BOOTSTRAPPER_ACTION_UNINSTALL:
552
hr = LocGetString(_wixLoc, L"#(loc.SuccessRemoveMessage)", &successText);
553
break;
554
}
555
556
if (successText) {
557
LPWSTR formattedString = nullptr;
558
BalFormatString(successText->wzText, &formattedString);
559
if (formattedString) {
560
ThemeSetTextControl(_theme, ID_SUCCESS_TEXT, formattedString);
561
StrFree(formattedString);
562
}
563
}
564
565
ThemeControlEnable(_theme, ID_SUCCESS_RESTART_TEXT, showRestartButton);
566
ThemeControlEnable(_theme, ID_SUCCESS_RESTART_BUTTON, showRestartButton);
567
568
if (_command.action != BOOTSTRAPPER_ACTION_INSTALL ||
569
!IsWindowsVersionOrGreater(10, 0, 0)) {
570
ThemeControlEnable(_theme, ID_SUCCESS_MAX_PATH_BUTTON, FALSE);
571
} else {
572
DWORD dataType = 0, buffer = 0, bufferLen = sizeof(buffer);
573
HKEY hKey;
574
LRESULT res = RegOpenKeyExW(
575
HKEY_LOCAL_MACHINE,
576
L"SYSTEM\\CurrentControlSet\\Control\\FileSystem",
577
0,
578
KEY_READ,
579
&hKey
580
);
581
if (res == ERROR_SUCCESS) {
582
res = RegQueryValueExW(hKey, L"LongPathsEnabled", nullptr, &dataType,
583
(LPBYTE)&buffer, &bufferLen);
584
RegCloseKey(hKey);
585
}
586
else {
587
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Failed to open SYSTEM\\CurrentControlSet\\Control\\FileSystem: error code %d", res);
588
}
589
if (res == ERROR_SUCCESS && dataType == REG_DWORD && buffer == 0) {
590
ThemeControlElevates(_theme, ID_SUCCESS_MAX_PATH_BUTTON, TRUE);
591
}
592
else {
593
if (res == ERROR_SUCCESS)
594
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Failed to read LongPathsEnabled value: error code %d", res);
595
else
596
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Hiding MAX_PATH button because it is already enabled");
597
ThemeControlEnable(_theme, ID_SUCCESS_MAX_PATH_BUTTON, FALSE);
598
}
599
}
600
}
601
602
void FailurePage_Show() {
603
// on the "Failure" page, show error message and check if the restart button should be enabled.
604
605
// if there is a log file variable then we'll assume the log file exists.
606
BOOL showLogLink = (_bundle.sczLogVariable && *_bundle.sczLogVariable);
607
BOOL showErrorMessage = FALSE;
608
BOOL showRestartButton = FALSE;
609
610
if (FAILED(_hrFinal)) {
611
LPWSTR unformattedText = nullptr;
612
LPWSTR text = nullptr;
613
614
// If we know the failure message, use that.
615
if (_failedMessage && *_failedMessage) {
616
StrAllocString(&unformattedText, _failedMessage, 0);
617
} else {
618
// try to get the error message from the error code.
619
StrAllocFromError(&unformattedText, _hrFinal, nullptr);
620
if (!unformattedText || !*unformattedText) {
621
StrAllocFromError(&unformattedText, E_FAIL, nullptr);
622
}
623
}
624
625
if (E_WIXSTDBA_CONDITION_FAILED == _hrFinal) {
626
if (unformattedText) {
627
StrAllocString(&text, unformattedText, 0);
628
}
629
} else {
630
StrAllocFormatted(&text, L"0x%08x - %ls", _hrFinal, unformattedText);
631
}
632
633
if (text) {
634
ThemeSetTextControl(_theme, ID_FAILURE_MESSAGE_TEXT, text);
635
showErrorMessage = TRUE;
636
}
637
638
ReleaseStr(text);
639
ReleaseStr(unformattedText);
640
}
641
642
if (_restartRequired && BOOTSTRAPPER_RESTART_PROMPT == _command.restart) {
643
showRestartButton = TRUE;
644
}
645
646
ThemeControlEnable(_theme, ID_FAILURE_LOGFILE_LINK, showLogLink);
647
ThemeControlEnable(_theme, ID_FAILURE_MESSAGE_TEXT, showErrorMessage);
648
ThemeControlEnable(_theme, ID_FAILURE_RESTART_TEXT, showRestartButton);
649
ThemeControlEnable(_theme, ID_FAILURE_RESTART_BUTTON, showRestartButton);
650
}
651
652
static void EnableMaxPathSupport() {
653
LPWSTR targetDir = nullptr, defaultDir = nullptr;
654
HRESULT hr = BalGetStringVariable(L"TargetDir", &targetDir);
655
if (FAILED(hr) || !targetDir || !targetDir[0]) {
656
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to get TargetDir");
657
return;
658
}
659
660
LPWSTR pythonw = nullptr;
661
StrAllocFormatted(&pythonw, L"%ls\\pythonw.exe", targetDir);
662
if (!pythonw || !pythonw[0]) {
663
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to construct pythonw.exe path");
664
return;
665
}
666
667
LPCWSTR arguments = L"-c \"import winreg; "
668
"winreg.SetValueEx("
669
"winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, "
670
"r'SYSTEM\\CurrentControlSet\\Control\\FileSystem'), "
671
"'LongPathsEnabled', "
672
"None, "
673
"winreg.REG_DWORD, "
674
"1"
675
")\"";
676
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Executing %ls %ls", pythonw, arguments);
677
HINSTANCE res = ShellExecuteW(0, L"runas", pythonw, arguments, NULL, SW_HIDE);
678
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "return code 0x%08x", res);
679
}
680
681
public: // IBootstrapperApplication
682
virtual STDMETHODIMP OnStartup() {
683
HRESULT hr = S_OK;
684
DWORD dwUIThreadId = 0;
685
686
// create UI thread
687
_hUiThread = ::CreateThread(nullptr, 0, UiThreadProc, this, 0, &dwUIThreadId);
688
if (!_hUiThread) {
689
ExitWithLastError(hr, "Failed to create UI thread.");
690
}
691
692
LExit:
693
return hr;
694
}
695
696
697
virtual STDMETHODIMP_(int) OnShutdown() {
698
int nResult = IDNOACTION;
699
700
// wait for UI thread to terminate
701
if (_hUiThread) {
702
::WaitForSingleObject(_hUiThread, INFINITE);
703
ReleaseHandle(_hUiThread);
704
}
705
706
// If a restart was required.
707
if (_restartRequired && _allowRestart) {
708
nResult = IDRESTART;
709
}
710
711
return nResult;
712
}
713
714
virtual STDMETHODIMP_(int) OnDetectRelatedMsiPackage(
715
__in_z LPCWSTR wzPackageId,
716
__in_z LPCWSTR /*wzProductCode*/,
717
__in BOOL fPerMachine,
718
__in DWORD64 /*dw64Version*/,
719
__in BOOTSTRAPPER_RELATED_OPERATION operation
720
) {
721
if (BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE == operation &&
722
(CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, L"launcher_AllUsers", -1) ||
723
CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, L"launcher_JustForMe", -1))) {
724
auto hr = LoadAssociateFilesStateFromKey(_engine, fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER);
725
if (hr == S_OK) {
726
_engine->SetVariableNumeric(L"AssociateFiles", 1);
727
} else if (hr == S_FALSE) {
728
_engine->SetVariableNumeric(L"AssociateFiles", 0);
729
} else if (FAILED(hr)) {
730
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to load AssociateFiles state: error code 0x%08X", hr);
731
}
732
733
LONGLONG includeLauncher;
734
if (FAILED(BalGetNumericVariable(L"Include_launcher", &includeLauncher))
735
|| includeLauncher == -1) {
736
_engine->SetVariableNumeric(L"Include_launcher", 1);
737
_engine->SetVariableNumeric(L"InstallLauncherAllUsers", fPerMachine ? 1 : 0);
738
}
739
_engine->SetVariableNumeric(L"DetectedOldLauncher", 1);
740
}
741
return CheckCanceled() ? IDCANCEL : IDNOACTION;
742
}
743
744
virtual STDMETHODIMP_(int) OnDetectRelatedBundle(
745
__in LPCWSTR wzBundleId,
746
__in BOOTSTRAPPER_RELATION_TYPE relationType,
747
__in LPCWSTR /*wzBundleTag*/,
748
__in BOOL fPerMachine,
749
__in DWORD64 /*dw64Version*/,
750
__in BOOTSTRAPPER_RELATED_OPERATION operation
751
) {
752
BalInfoAddRelatedBundleAsPackage(&_bundle.packages, wzBundleId, relationType, fPerMachine);
753
754
// Remember when our bundle would cause a downgrade.
755
if (BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE == operation) {
756
_downgradingOtherVersion = TRUE;
757
} else if (BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE == operation) {
758
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Detected previous version - planning upgrade");
759
_upgrading = TRUE;
760
761
LoadOptionalFeatureStates(_engine);
762
} else if (BOOTSTRAPPER_RELATED_OPERATION_NONE == operation) {
763
if (_command.action == BOOTSTRAPPER_ACTION_INSTALL) {
764
LOC_STRING *pLocString = nullptr;
765
if (SUCCEEDED(LocGetString(_wixLoc, L"#(loc.FailureExistingInstall)", &pLocString)) && pLocString) {
766
BalFormatString(pLocString->wzText, &_failedMessage);
767
} else {
768
BalFormatString(L"Cannot install [WixBundleName] because it is already installed.", &_failedMessage);
769
}
770
BalLog(
771
BOOTSTRAPPER_LOG_LEVEL_ERROR,
772
"Related bundle %ls is preventing install",
773
wzBundleId
774
);
775
SetState(PYBA_STATE_FAILED, E_WIXSTDBA_CONDITION_FAILED);
776
}
777
}
778
779
return CheckCanceled() ? IDCANCEL : IDOK;
780
}
781
782
783
virtual STDMETHODIMP_(void) OnDetectPackageComplete(
784
__in LPCWSTR wzPackageId,
785
__in HRESULT hrStatus,
786
__in BOOTSTRAPPER_PACKAGE_STATE state
787
) {
788
if (FAILED(hrStatus)) {
789
return;
790
}
791
792
BOOL detectedLauncher = FALSE;
793
HKEY hkey = HKEY_LOCAL_MACHINE;
794
if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, L"launcher_AllUsers", -1)) {
795
if (BOOTSTRAPPER_PACKAGE_STATE_PRESENT == state || BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE == state) {
796
detectedLauncher = TRUE;
797
_engine->SetVariableNumeric(L"InstallLauncherAllUsers", 1);
798
}
799
} else if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, L"launcher_JustForMe", -1)) {
800
if (BOOTSTRAPPER_PACKAGE_STATE_PRESENT == state || BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE == state) {
801
detectedLauncher = TRUE;
802
_engine->SetVariableNumeric(L"InstallLauncherAllUsers", 0);
803
}
804
}
805
806
LONGLONG includeLauncher;
807
if (SUCCEEDED(BalGetNumericVariable(L"Include_launcher", &includeLauncher))
808
&& includeLauncher != -1) {
809
detectedLauncher = FALSE;
810
}
811
812
if (detectedLauncher) {
813
/* When we detect the current version of the launcher. */
814
_engine->SetVariableNumeric(L"Include_launcher", 1);
815
_engine->SetVariableNumeric(L"DetectedLauncher", 1);
816
_engine->SetVariableString(L"Include_launcherState", L"disable");
817
_engine->SetVariableString(L"InstallLauncherAllUsersState", L"disable");
818
819
auto hr = LoadAssociateFilesStateFromKey(_engine, hkey);
820
if (hr == S_OK) {
821
_engine->SetVariableNumeric(L"AssociateFiles", 1);
822
} else if (hr == S_FALSE) {
823
_engine->SetVariableNumeric(L"AssociateFiles", 0);
824
} else if (FAILED(hr)) {
825
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to load AssociateFiles state: error code 0x%08X", hr);
826
}
827
}
828
}
829
830
831
virtual STDMETHODIMP_(void) OnDetectComplete(__in HRESULT hrStatus) {
832
if (SUCCEEDED(hrStatus) && _baFunction) {
833
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running detect complete BA function");
834
_baFunction->OnDetectComplete();
835
}
836
837
if (SUCCEEDED(hrStatus)) {
838
LONGLONG includeLauncher;
839
if (SUCCEEDED(BalGetNumericVariable(L"Include_launcher", &includeLauncher))
840
&& includeLauncher == -1) {
841
if (BOOTSTRAPPER_ACTION_LAYOUT == _command.action ||
842
(BOOTSTRAPPER_ACTION_INSTALL == _command.action && !_upgrading)) {
843
// When installing/downloading, we want to include the launcher
844
// by default.
845
_engine->SetVariableNumeric(L"Include_launcher", 1);
846
} else {
847
// Any other action, if we didn't detect the MSI then we want to
848
// keep it excluded
849
_engine->SetVariableNumeric(L"Include_launcher", 0);
850
_engine->SetVariableNumeric(L"AssociateFiles", 0);
851
}
852
}
853
}
854
855
if (SUCCEEDED(hrStatus)) {
856
hrStatus = EvaluateConditions();
857
}
858
859
if (SUCCEEDED(hrStatus)) {
860
// Ensure the default path has been set
861
hrStatus = EnsureTargetDir();
862
}
863
864
SetState(PYBA_STATE_DETECTED, hrStatus);
865
866
// If we're not interacting with the user or we're doing a layout or we're just after a force restart
867
// then automatically start planning.
868
if (BOOTSTRAPPER_DISPLAY_FULL > _command.display ||
869
BOOTSTRAPPER_ACTION_LAYOUT == _command.action ||
870
BOOTSTRAPPER_ACTION_UNINSTALL == _command.action ||
871
BOOTSTRAPPER_RESUME_TYPE_REBOOT == _command.resumeType) {
872
if (SUCCEEDED(hrStatus)) {
873
::PostMessageW(_hWnd, WM_PYBA_PLAN_PACKAGES, 0, _command.action);
874
}
875
}
876
}
877
878
879
virtual STDMETHODIMP_(int) OnPlanRelatedBundle(
880
__in_z LPCWSTR /*wzBundleId*/,
881
__inout_z BOOTSTRAPPER_REQUEST_STATE* pRequestedState
882
) {
883
return CheckCanceled() ? IDCANCEL : IDOK;
884
}
885
886
887
virtual STDMETHODIMP_(int) OnPlanPackageBegin(
888
__in_z LPCWSTR wzPackageId,
889
__inout BOOTSTRAPPER_REQUEST_STATE *pRequestState
890
) {
891
HRESULT hr = S_OK;
892
BAL_INFO_PACKAGE* pPackage = nullptr;
893
894
if (_nextPackageAfterRestart) {
895
// After restart we need to finish the dependency registration for our package so allow the package
896
// to go present.
897
if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, _nextPackageAfterRestart, -1)) {
898
// Do not allow a repair because that could put us in a perpetual restart loop.
899
if (BOOTSTRAPPER_REQUEST_STATE_REPAIR == *pRequestState) {
900
*pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT;
901
}
902
903
ReleaseNullStr(_nextPackageAfterRestart); // no more skipping now.
904
} else {
905
// not the matching package, so skip it.
906
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Skipping package: %ls, after restart because it was applied before the restart.", wzPackageId);
907
908
*pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE;
909
}
910
} else if ((_plannedAction == BOOTSTRAPPER_ACTION_INSTALL || _plannedAction == BOOTSTRAPPER_ACTION_MODIFY) &&
911
SUCCEEDED(BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage))) {
912
BOOL f = FALSE;
913
if (SUCCEEDED(_engine->EvaluateCondition(pPackage->sczInstallCondition, &f)) && f) {
914
*pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT;
915
}
916
}
917
918
return CheckCanceled() ? IDCANCEL : IDOK;
919
}
920
921
virtual STDMETHODIMP_(int) OnPlanMsiFeature(
922
__in_z LPCWSTR wzPackageId,
923
__in_z LPCWSTR wzFeatureId,
924
__inout BOOTSTRAPPER_FEATURE_STATE* pRequestedState
925
) {
926
LONGLONG install;
927
928
if (wcscmp(wzFeatureId, L"AssociateFiles") == 0 || wcscmp(wzFeatureId, L"Shortcuts") == 0) {
929
if (SUCCEEDED(_engine->GetVariableNumeric(wzFeatureId, &install)) && install) {
930
*pRequestedState = BOOTSTRAPPER_FEATURE_STATE_LOCAL;
931
} else {
932
*pRequestedState = BOOTSTRAPPER_FEATURE_STATE_ABSENT;
933
}
934
} else {
935
*pRequestedState = BOOTSTRAPPER_FEATURE_STATE_LOCAL;
936
}
937
return CheckCanceled() ? IDCANCEL : IDNOACTION;
938
}
939
940
virtual STDMETHODIMP_(void) OnPlanComplete(__in HRESULT hrStatus) {
941
if (SUCCEEDED(hrStatus) && _baFunction) {
942
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running plan complete BA function");
943
_baFunction->OnPlanComplete();
944
}
945
946
SetState(PYBA_STATE_PLANNED, hrStatus);
947
948
if (SUCCEEDED(hrStatus)) {
949
::PostMessageW(_hWnd, WM_PYBA_APPLY_PACKAGES, 0, 0);
950
}
951
952
_startedExecution = FALSE;
953
_calculatedCacheProgress = 0;
954
_calculatedExecuteProgress = 0;
955
}
956
957
958
virtual STDMETHODIMP_(int) OnCachePackageBegin(
959
__in_z LPCWSTR wzPackageId,
960
__in DWORD cCachePayloads,
961
__in DWORD64 dw64PackageCacheSize
962
) {
963
if (wzPackageId && *wzPackageId) {
964
BAL_INFO_PACKAGE* pPackage = nullptr;
965
HRESULT hr = BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage);
966
LPCWSTR wz = (SUCCEEDED(hr) && pPackage->sczDisplayName) ? pPackage->sczDisplayName : wzPackageId;
967
968
ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_PACKAGE_TEXT, wz);
969
970
// If something started executing, leave it in the overall progress text.
971
if (!_startedExecution) {
972
ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, wz);
973
}
974
}
975
976
return __super::OnCachePackageBegin(wzPackageId, cCachePayloads, dw64PackageCacheSize);
977
}
978
979
980
virtual STDMETHODIMP_(int) OnCacheAcquireProgress(
981
__in_z LPCWSTR wzPackageOrContainerId,
982
__in_z_opt LPCWSTR wzPayloadId,
983
__in DWORD64 dw64Progress,
984
__in DWORD64 dw64Total,
985
__in DWORD dwOverallPercentage
986
) {
987
WCHAR wzProgress[5] = { };
988
989
#ifdef DEBUG
990
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnCacheAcquireProgress() - container/package: %ls, payload: %ls, progress: %I64u, total: %I64u, overall progress: %u%%", wzPackageOrContainerId, wzPayloadId, dw64Progress, dw64Total, dwOverallPercentage);
991
#endif
992
993
::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallPercentage);
994
ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_TEXT, wzProgress);
995
996
ThemeSetProgressControl(_theme, ID_CACHE_PROGRESS_BAR, dwOverallPercentage);
997
998
_calculatedCacheProgress = dwOverallPercentage * PYBA_ACQUIRE_PERCENTAGE / 100;
999
ThemeSetProgressControl(_theme, ID_OVERALL_CALCULATED_PROGRESS_BAR, _calculatedCacheProgress + _calculatedExecuteProgress);
1000
1001
SetTaskbarButtonProgress(_calculatedCacheProgress + _calculatedExecuteProgress);
1002
1003
return __super::OnCacheAcquireProgress(wzPackageOrContainerId, wzPayloadId, dw64Progress, dw64Total, dwOverallPercentage);
1004
}
1005
1006
1007
virtual STDMETHODIMP_(int) OnCacheAcquireComplete(
1008
__in_z LPCWSTR wzPackageOrContainerId,
1009
__in_z_opt LPCWSTR wzPayloadId,
1010
__in HRESULT hrStatus,
1011
__in int nRecommendation
1012
) {
1013
SetProgressState(hrStatus);
1014
return __super::OnCacheAcquireComplete(wzPackageOrContainerId, wzPayloadId, hrStatus, nRecommendation);
1015
}
1016
1017
1018
virtual STDMETHODIMP_(int) OnCacheVerifyComplete(
1019
__in_z LPCWSTR wzPackageId,
1020
__in_z LPCWSTR wzPayloadId,
1021
__in HRESULT hrStatus,
1022
__in int nRecommendation
1023
) {
1024
SetProgressState(hrStatus);
1025
return __super::OnCacheVerifyComplete(wzPackageId, wzPayloadId, hrStatus, nRecommendation);
1026
}
1027
1028
1029
virtual STDMETHODIMP_(void) OnCacheComplete(__in HRESULT /*hrStatus*/) {
1030
ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_PACKAGE_TEXT, L"");
1031
SetState(PYBA_STATE_CACHED, S_OK); // we always return success here and let OnApplyComplete() deal with the error.
1032
}
1033
1034
1035
virtual STDMETHODIMP_(int) OnError(
1036
__in BOOTSTRAPPER_ERROR_TYPE errorType,
1037
__in LPCWSTR wzPackageId,
1038
__in DWORD dwCode,
1039
__in_z LPCWSTR wzError,
1040
__in DWORD dwUIHint,
1041
__in DWORD /*cData*/,
1042
__in_ecount_z_opt(cData) LPCWSTR* /*rgwzData*/,
1043
__in int nRecommendation
1044
) {
1045
int nResult = nRecommendation;
1046
LPWSTR sczError = nullptr;
1047
1048
if (BOOTSTRAPPER_DISPLAY_EMBEDDED == _command.display) {
1049
HRESULT hr = _engine->SendEmbeddedError(dwCode, wzError, dwUIHint, &nResult);
1050
if (FAILED(hr)) {
1051
nResult = IDERROR;
1052
}
1053
} else if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) {
1054
// If this is an authentication failure, let the engine try to handle it for us.
1055
if (BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_SERVER == errorType || BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_PROXY == errorType) {
1056
nResult = IDTRYAGAIN;
1057
} else // show a generic error message box.
1058
{
1059
BalRetryErrorOccurred(wzPackageId, dwCode);
1060
1061
if (!_showingInternalUIThisPackage) {
1062
// If no error message was provided, use the error code to try and get an error message.
1063
if (!wzError || !*wzError || BOOTSTRAPPER_ERROR_TYPE_WINDOWS_INSTALLER != errorType) {
1064
HRESULT hr = StrAllocFromError(&sczError, dwCode, nullptr);
1065
if (FAILED(hr) || !sczError || !*sczError) {
1066
StrAllocFormatted(&sczError, L"0x%x", dwCode);
1067
}
1068
}
1069
1070
nResult = ::MessageBoxW(_hWnd, sczError ? sczError : wzError, _theme->sczCaption, dwUIHint);
1071
}
1072
}
1073
1074
SetProgressState(HRESULT_FROM_WIN32(dwCode));
1075
} else {
1076
// just take note of the error code and let things continue.
1077
BalRetryErrorOccurred(wzPackageId, dwCode);
1078
}
1079
1080
ReleaseStr(sczError);
1081
return nResult;
1082
}
1083
1084
1085
virtual STDMETHODIMP_(int) OnExecuteMsiMessage(
1086
__in_z LPCWSTR wzPackageId,
1087
__in INSTALLMESSAGE mt,
1088
__in UINT uiFlags,
1089
__in_z LPCWSTR wzMessage,
1090
__in DWORD cData,
1091
__in_ecount_z_opt(cData) LPCWSTR* rgwzData,
1092
__in int nRecommendation
1093
) {
1094
#ifdef DEBUG
1095
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnExecuteMsiMessage() - package: %ls, message: %ls", wzPackageId, wzMessage);
1096
#endif
1097
if (BOOTSTRAPPER_DISPLAY_FULL == _command.display && (INSTALLMESSAGE_WARNING == mt || INSTALLMESSAGE_USER == mt)) {
1098
int nResult = ::MessageBoxW(_hWnd, wzMessage, _theme->sczCaption, uiFlags);
1099
return nResult;
1100
}
1101
1102
if (INSTALLMESSAGE_ACTIONSTART == mt) {
1103
ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, wzMessage);
1104
}
1105
1106
return __super::OnExecuteMsiMessage(wzPackageId, mt, uiFlags, wzMessage, cData, rgwzData, nRecommendation);
1107
}
1108
1109
1110
virtual STDMETHODIMP_(int) OnProgress(__in DWORD dwProgressPercentage, __in DWORD dwOverallProgressPercentage) {
1111
WCHAR wzProgress[5] = { };
1112
1113
#ifdef DEBUG
1114
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnProgress() - progress: %u%%, overall progress: %u%%", dwProgressPercentage, dwOverallProgressPercentage);
1115
#endif
1116
1117
::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallProgressPercentage);
1118
ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_TEXT, wzProgress);
1119
1120
ThemeSetProgressControl(_theme, ID_OVERALL_PROGRESS_BAR, dwOverallProgressPercentage);
1121
SetTaskbarButtonProgress(dwOverallProgressPercentage);
1122
1123
return __super::OnProgress(dwProgressPercentage, dwOverallProgressPercentage);
1124
}
1125
1126
1127
virtual STDMETHODIMP_(int) OnExecutePackageBegin(__in_z LPCWSTR wzPackageId, __in BOOL fExecute) {
1128
LPWSTR sczFormattedString = nullptr;
1129
1130
_startedExecution = TRUE;
1131
1132
if (wzPackageId && *wzPackageId) {
1133
BAL_INFO_PACKAGE* pPackage = nullptr;
1134
BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage);
1135
1136
LPCWSTR wz = wzPackageId;
1137
if (pPackage) {
1138
LOC_STRING* pLocString = nullptr;
1139
1140
switch (pPackage->type) {
1141
case BAL_INFO_PACKAGE_TYPE_BUNDLE_ADDON:
1142
LocGetString(_wixLoc, L"#(loc.ExecuteAddonRelatedBundleMessage)", &pLocString);
1143
break;
1144
1145
case BAL_INFO_PACKAGE_TYPE_BUNDLE_PATCH:
1146
LocGetString(_wixLoc, L"#(loc.ExecutePatchRelatedBundleMessage)", &pLocString);
1147
break;
1148
1149
case BAL_INFO_PACKAGE_TYPE_BUNDLE_UPGRADE:
1150
LocGetString(_wixLoc, L"#(loc.ExecuteUpgradeRelatedBundleMessage)", &pLocString);
1151
break;
1152
}
1153
1154
if (pLocString) {
1155
// If the wix developer is showing a hidden variable in the UI, then obviously they don't care about keeping it safe
1156
// so don't go down the rabbit hole of making sure that this is securely freed.
1157
BalFormatString(pLocString->wzText, &sczFormattedString);
1158
}
1159
1160
wz = sczFormattedString ? sczFormattedString : pPackage->sczDisplayName ? pPackage->sczDisplayName : wzPackageId;
1161
}
1162
1163
_showingInternalUIThisPackage = pPackage && pPackage->fDisplayInternalUI;
1164
1165
ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_PACKAGE_TEXT, wz);
1166
ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, wz);
1167
} else {
1168
_showingInternalUIThisPackage = FALSE;
1169
}
1170
1171
ReleaseStr(sczFormattedString);
1172
return __super::OnExecutePackageBegin(wzPackageId, fExecute);
1173
}
1174
1175
1176
virtual int __stdcall OnExecuteProgress(
1177
__in_z LPCWSTR wzPackageId,
1178
__in DWORD dwProgressPercentage,
1179
__in DWORD dwOverallProgressPercentage
1180
) {
1181
WCHAR wzProgress[8] = { };
1182
1183
#ifdef DEBUG
1184
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnExecuteProgress() - package: %ls, progress: %u%%, overall progress: %u%%", wzPackageId, dwProgressPercentage, dwOverallProgressPercentage);
1185
#endif
1186
1187
::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallProgressPercentage);
1188
ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_TEXT, wzProgress);
1189
1190
ThemeSetProgressControl(_theme, ID_EXECUTE_PROGRESS_BAR, dwOverallProgressPercentage);
1191
1192
_calculatedExecuteProgress = dwOverallProgressPercentage * (100 - PYBA_ACQUIRE_PERCENTAGE) / 100;
1193
ThemeSetProgressControl(_theme, ID_OVERALL_CALCULATED_PROGRESS_BAR, _calculatedCacheProgress + _calculatedExecuteProgress);
1194
1195
SetTaskbarButtonProgress(_calculatedCacheProgress + _calculatedExecuteProgress);
1196
1197
return __super::OnExecuteProgress(wzPackageId, dwProgressPercentage, dwOverallProgressPercentage);
1198
}
1199
1200
1201
virtual STDMETHODIMP_(int) OnExecutePackageComplete(
1202
__in_z LPCWSTR wzPackageId,
1203
__in HRESULT hrExitCode,
1204
__in BOOTSTRAPPER_APPLY_RESTART restart,
1205
__in int nRecommendation
1206
) {
1207
SetProgressState(hrExitCode);
1208
1209
if (_wcsnicmp(wzPackageId, L"path_", 5) == 0 && SUCCEEDED(hrExitCode)) {
1210
SendMessageTimeoutW(
1211
HWND_BROADCAST,
1212
WM_SETTINGCHANGE,
1213
0,
1214
reinterpret_cast<LPARAM>(L"Environment"),
1215
SMTO_ABORTIFHUNG,
1216
1000,
1217
nullptr
1218
);
1219
}
1220
1221
int nResult = __super::OnExecutePackageComplete(wzPackageId, hrExitCode, restart, nRecommendation);
1222
1223
return nResult;
1224
}
1225
1226
1227
virtual STDMETHODIMP_(void) OnExecuteComplete(__in HRESULT hrStatus) {
1228
ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_PACKAGE_TEXT, L"");
1229
ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, L"");
1230
ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, L"");
1231
ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, FALSE); // no more cancel.
1232
1233
SetState(PYBA_STATE_EXECUTED, S_OK); // we always return success here and let OnApplyComplete() deal with the error.
1234
SetProgressState(hrStatus);
1235
}
1236
1237
1238
virtual STDMETHODIMP_(int) OnResolveSource(
1239
__in_z LPCWSTR wzPackageOrContainerId,
1240
__in_z_opt LPCWSTR wzPayloadId,
1241
__in_z LPCWSTR wzLocalSource,
1242
__in_z_opt LPCWSTR wzDownloadSource
1243
) {
1244
int nResult = IDERROR; // assume we won't resolve source and that is unexpected.
1245
1246
if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) {
1247
if (wzDownloadSource) {
1248
nResult = IDDOWNLOAD;
1249
} else {
1250
// prompt to change the source location.
1251
OPENFILENAMEW ofn = { };
1252
WCHAR wzFile[MAX_PATH] = { };
1253
1254
::StringCchCopyW(wzFile, countof(wzFile), wzLocalSource);
1255
1256
ofn.lStructSize = sizeof(ofn);
1257
ofn.hwndOwner = _hWnd;
1258
ofn.lpstrFile = wzFile;
1259
ofn.nMaxFile = countof(wzFile);
1260
ofn.lpstrFilter = L"All Files\0*.*\0";
1261
ofn.nFilterIndex = 1;
1262
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
1263
ofn.lpstrTitle = _theme->sczCaption;
1264
1265
if (::GetOpenFileNameW(&ofn)) {
1266
HRESULT hr = _engine->SetLocalSource(wzPackageOrContainerId, wzPayloadId, ofn.lpstrFile);
1267
nResult = SUCCEEDED(hr) ? IDRETRY : IDERROR;
1268
} else {
1269
nResult = IDCANCEL;
1270
}
1271
}
1272
} else if (wzDownloadSource) {
1273
// If doing a non-interactive install and download source is available, let's try downloading the package silently
1274
nResult = IDDOWNLOAD;
1275
}
1276
// else there's nothing more we can do in non-interactive mode
1277
1278
return CheckCanceled() ? IDCANCEL : nResult;
1279
}
1280
1281
1282
virtual STDMETHODIMP_(int) OnApplyComplete(__in HRESULT hrStatus, __in BOOTSTRAPPER_APPLY_RESTART restart) {
1283
_restartResult = restart; // remember the restart result so we return the correct error code no matter what the user chooses to do in the UI.
1284
1285
// If a restart was encountered and we are not suppressing restarts, then restart is required.
1286
_restartRequired = (BOOTSTRAPPER_APPLY_RESTART_NONE != restart && BOOTSTRAPPER_RESTART_NEVER < _command.restart);
1287
// 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.
1288
_allowRestart = _restartRequired && (BOOTSTRAPPER_DISPLAY_FULL > _command.display || BOOTSTRAPPER_RESTART_PROMPT < _command.restart);
1289
1290
// If we are showing UI, wait a beat before moving to the final screen.
1291
if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) {
1292
::Sleep(250);
1293
}
1294
1295
SetState(PYBA_STATE_APPLIED, hrStatus);
1296
SetTaskbarButtonProgress(100); // show full progress bar, green, yellow, or red
1297
1298
return IDNOACTION;
1299
}
1300
1301
virtual STDMETHODIMP_(void) OnLaunchApprovedExeComplete(__in HRESULT hrStatus, __in DWORD /*processId*/) {
1302
}
1303
1304
1305
private:
1306
//
1307
// UiThreadProc - entrypoint for UI thread.
1308
//
1309
static DWORD WINAPI UiThreadProc(__in LPVOID pvContext) {
1310
HRESULT hr = S_OK;
1311
PythonBootstrapperApplication* pThis = (PythonBootstrapperApplication*)pvContext;
1312
BOOL comInitialized = FALSE;
1313
BOOL ret = FALSE;
1314
MSG msg = { };
1315
1316
// Initialize COM and theme.
1317
hr = ::CoInitialize(nullptr);
1318
BalExitOnFailure(hr, "Failed to initialize COM.");
1319
comInitialized = TRUE;
1320
1321
hr = ThemeInitialize(pThis->_hModule);
1322
BalExitOnFailure(hr, "Failed to initialize theme manager.");
1323
1324
hr = pThis->InitializeData();
1325
BalExitOnFailure(hr, "Failed to initialize data in bootstrapper application.");
1326
1327
// Create main window.
1328
pThis->InitializeTaskbarButton();
1329
hr = pThis->CreateMainWindow();
1330
BalExitOnFailure(hr, "Failed to create main window.");
1331
1332
pThis->ValidateOperatingSystem();
1333
1334
if (FAILED(pThis->_hrFinal)) {
1335
pThis->SetState(PYBA_STATE_FAILED, hr);
1336
::PostMessageW(pThis->_hWnd, WM_PYBA_SHOW_FAILURE, 0, 0);
1337
} else {
1338
// Okay, we're ready for packages now.
1339
pThis->SetState(PYBA_STATE_INITIALIZED, hr);
1340
::PostMessageW(pThis->_hWnd, BOOTSTRAPPER_ACTION_HELP == pThis->_command.action ? WM_PYBA_SHOW_HELP : WM_PYBA_DETECT_PACKAGES, 0, 0);
1341
}
1342
1343
// message pump
1344
while (0 != (ret = ::GetMessageW(&msg, nullptr, 0, 0))) {
1345
if (-1 == ret) {
1346
hr = E_UNEXPECTED;
1347
BalExitOnFailure(hr, "Unexpected return value from message pump.");
1348
} else if (!ThemeHandleKeyboardMessage(pThis->_theme, msg.hwnd, &msg)) {
1349
::TranslateMessage(&msg);
1350
::DispatchMessageW(&msg);
1351
}
1352
}
1353
1354
// Succeeded thus far, check to see if anything went wrong while actually
1355
// executing changes.
1356
if (FAILED(pThis->_hrFinal)) {
1357
hr = pThis->_hrFinal;
1358
} else if (pThis->CheckCanceled()) {
1359
hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT);
1360
}
1361
1362
LExit:
1363
// destroy main window
1364
pThis->DestroyMainWindow();
1365
1366
// initiate engine shutdown
1367
DWORD dwQuit = HRESULT_CODE(hr);
1368
if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == pThis->_restartResult) {
1369
dwQuit = ERROR_SUCCESS_REBOOT_INITIATED;
1370
} else if (BOOTSTRAPPER_APPLY_RESTART_REQUIRED == pThis->_restartResult) {
1371
dwQuit = ERROR_SUCCESS_REBOOT_REQUIRED;
1372
}
1373
pThis->_engine->Quit(dwQuit);
1374
1375
ReleaseTheme(pThis->_theme);
1376
ThemeUninitialize();
1377
1378
// uninitialize COM
1379
if (comInitialized) {
1380
::CoUninitialize();
1381
}
1382
1383
return hr;
1384
}
1385
1386
//
1387
// ParseVariablesFromUnattendXml - reads options from unattend.xml if it
1388
// exists
1389
//
1390
HRESULT ParseVariablesFromUnattendXml() {
1391
HRESULT hr = S_OK;
1392
LPWSTR sczUnattendXmlPath = nullptr;
1393
IXMLDOMDocument *pixdUnattend = nullptr;
1394
IXMLDOMNodeList *pNodes = nullptr;
1395
IXMLDOMNode *pNode = nullptr;
1396
long cNodes;
1397
DWORD dwAttr;
1398
LPWSTR scz = nullptr;
1399
BOOL bValue;
1400
int iValue;
1401
BOOL tryConvert;
1402
BSTR bstrValue = nullptr;
1403
1404
hr = BalFormatString(L"[WixBundleOriginalSourceFolder]unattend.xml", &sczUnattendXmlPath);
1405
BalExitOnFailure(hr, "Failed to calculate path to unattend.xml");
1406
1407
if (!FileExistsEx(sczUnattendXmlPath, &dwAttr)) {
1408
BalLog(BOOTSTRAPPER_LOG_LEVEL_VERBOSE, "Did not find %ls", sczUnattendXmlPath);
1409
hr = S_FALSE;
1410
goto LExit;
1411
}
1412
1413
hr = XmlLoadDocumentFromFile(sczUnattendXmlPath, &pixdUnattend);
1414
BalExitOnFailure1(hr, "Failed to read %ls", sczUnattendXmlPath);
1415
1416
// get the list of variables users have overridden
1417
hr = XmlSelectNodes(pixdUnattend, L"/Options/Option", &pNodes);
1418
if (S_FALSE == hr) {
1419
ExitFunction1(hr = S_OK);
1420
}
1421
BalExitOnFailure(hr, "Failed to select option nodes.");
1422
1423
hr = pNodes->get_length((long*)&cNodes);
1424
BalExitOnFailure(hr, "Failed to get option node count.");
1425
1426
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Reading settings from %ls", sczUnattendXmlPath);
1427
1428
for (DWORD i = 0; i < cNodes; ++i) {
1429
hr = XmlNextElement(pNodes, &pNode, nullptr);
1430
BalExitOnFailure(hr, "Failed to get next node.");
1431
1432
// @Name
1433
hr = XmlGetAttributeEx(pNode, L"Name", &scz);
1434
BalExitOnFailure(hr, "Failed to get @Name.");
1435
1436
tryConvert = TRUE;
1437
hr = XmlGetAttribute(pNode, L"Value", &bstrValue);
1438
if (FAILED(hr) || !bstrValue || !*bstrValue) {
1439
hr = XmlGetText(pNode, &bstrValue);
1440
tryConvert = FALSE;
1441
}
1442
BalExitOnFailure(hr, "Failed to get @Value.");
1443
1444
if (tryConvert &&
1445
CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, bstrValue, -1, L"yes", -1)) {
1446
_engine->SetVariableNumeric(scz, 1);
1447
} else if (tryConvert &&
1448
CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, bstrValue, -1, L"no", -1)) {
1449
_engine->SetVariableNumeric(scz, 0);
1450
} else if (tryConvert && ::StrToIntExW(bstrValue, STIF_DEFAULT, &iValue)) {
1451
_engine->SetVariableNumeric(scz, iValue);
1452
} else {
1453
_engine->SetVariableString(scz, bstrValue);
1454
}
1455
1456
ReleaseNullBSTR(bstrValue);
1457
ReleaseNullStr(scz);
1458
ReleaseNullObject(pNode);
1459
}
1460
1461
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Finished reading from %ls", sczUnattendXmlPath);
1462
1463
LExit:
1464
ReleaseObject(pNode);
1465
ReleaseObject(pNodes);
1466
ReleaseObject(pixdUnattend);
1467
ReleaseStr(sczUnattendXmlPath);
1468
1469
return hr;
1470
}
1471
1472
1473
//
1474
// InitializeData - initializes all the package information.
1475
//
1476
HRESULT InitializeData() {
1477
HRESULT hr = S_OK;
1478
LPWSTR sczModulePath = nullptr;
1479
IXMLDOMDocument *pixdManifest = nullptr;
1480
1481
hr = BalManifestLoad(_hModule, &pixdManifest);
1482
BalExitOnFailure(hr, "Failed to load bootstrapper application manifest.");
1483
1484
hr = ParseOverridableVariablesFromXml(pixdManifest);
1485
BalExitOnFailure(hr, "Failed to read overridable variables.");
1486
1487
if (_command.action == BOOTSTRAPPER_ACTION_MODIFY) {
1488
LoadOptionalFeatureStates(_engine);
1489
}
1490
1491
hr = ParseVariablesFromUnattendXml();
1492
ExitOnFailure(hr, "Failed to read unattend.ini file.");
1493
1494
hr = ProcessCommandLine(&_language);
1495
ExitOnFailure(hr, "Unknown commandline parameters.");
1496
1497
hr = PathRelativeToModule(&sczModulePath, nullptr, _hModule);
1498
BalExitOnFailure(hr, "Failed to get module path.");
1499
1500
hr = LoadLocalization(sczModulePath, _language);
1501
ExitOnFailure(hr, "Failed to load localization.");
1502
1503
hr = LoadTheme(sczModulePath, _language);
1504
ExitOnFailure(hr, "Failed to load theme.");
1505
1506
hr = BalInfoParseFromXml(&_bundle, pixdManifest);
1507
BalExitOnFailure(hr, "Failed to load bundle information.");
1508
1509
hr = BalConditionsParseFromXml(&_conditions, pixdManifest, _wixLoc);
1510
BalExitOnFailure(hr, "Failed to load conditions from XML.");
1511
1512
hr = LoadBootstrapperBAFunctions();
1513
BalExitOnFailure(hr, "Failed to load bootstrapper functions.");
1514
1515
hr = UpdateUIStrings(_command.action);
1516
BalExitOnFailure(hr, "Failed to load UI strings.");
1517
1518
GetBundleFileVersion();
1519
// don't fail if we couldn't get the version info; best-effort only
1520
LExit:
1521
ReleaseObject(pixdManifest);
1522
ReleaseStr(sczModulePath);
1523
1524
return hr;
1525
}
1526
1527
1528
//
1529
// ProcessCommandLine - process the provided command line arguments.
1530
//
1531
HRESULT ProcessCommandLine(__inout LPWSTR* psczLanguage) {
1532
HRESULT hr = S_OK;
1533
int argc = 0;
1534
LPWSTR* argv = nullptr;
1535
LPWSTR sczVariableName = nullptr;
1536
LPWSTR sczVariableValue = nullptr;
1537
1538
if (_command.wzCommandLine && *_command.wzCommandLine) {
1539
argv = ::CommandLineToArgvW(_command.wzCommandLine, &argc);
1540
ExitOnNullWithLastError(argv, hr, "Failed to get command line.");
1541
1542
for (int i = 0; i < argc; ++i) {
1543
if (argv[i][0] == L'-' || argv[i][0] == L'/') {
1544
if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"lang", -1)) {
1545
if (i + 1 >= argc) {
1546
hr = E_INVALIDARG;
1547
BalExitOnFailure(hr, "Must specify a language.");
1548
}
1549
1550
++i;
1551
1552
hr = StrAllocString(psczLanguage, &argv[i][0], 0);
1553
BalExitOnFailure(hr, "Failed to copy language.");
1554
} else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"simple", -1)) {
1555
_engine->SetVariableNumeric(L"SimpleInstall", 1);
1556
}
1557
} else if (_overridableVariables) {
1558
int value;
1559
const wchar_t* pwc = wcschr(argv[i], L'=');
1560
if (pwc) {
1561
hr = StrAllocString(&sczVariableName, argv[i], pwc - argv[i]);
1562
BalExitOnFailure(hr, "Failed to copy variable name.");
1563
1564
hr = DictKeyExists(_overridableVariables, sczVariableName);
1565
if (E_NOTFOUND == hr) {
1566
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Ignoring attempt to set non-overridable variable: '%ls'.", sczVariableName);
1567
hr = S_OK;
1568
continue;
1569
}
1570
ExitOnFailure(hr, "Failed to check the dictionary of overridable variables.");
1571
1572
hr = StrAllocString(&sczVariableValue, ++pwc, 0);
1573
BalExitOnFailure(hr, "Failed to copy variable value.");
1574
1575
if (::StrToIntEx(sczVariableValue, STIF_DEFAULT, &value)) {
1576
hr = _engine->SetVariableNumeric(sczVariableName, value);
1577
} else {
1578
hr = _engine->SetVariableString(sczVariableName, sczVariableValue);
1579
}
1580
BalExitOnFailure(hr, "Failed to set variable.");
1581
} else {
1582
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Ignoring unknown argument: %ls", argv[i]);
1583
}
1584
}
1585
}
1586
}
1587
1588
LExit:
1589
if (argv) {
1590
::LocalFree(argv);
1591
}
1592
1593
ReleaseStr(sczVariableName);
1594
ReleaseStr(sczVariableValue);
1595
1596
return hr;
1597
}
1598
1599
HRESULT LoadLocalization(__in_z LPCWSTR wzModulePath, __in_z_opt LPCWSTR wzLanguage) {
1600
HRESULT hr = S_OK;
1601
LPWSTR sczLocPath = nullptr;
1602
LPCWSTR wzLocFileName = L"Default.wxl";
1603
1604
hr = LocProbeForFile(wzModulePath, wzLocFileName, wzLanguage, &sczLocPath);
1605
BalExitOnFailure2(hr, "Failed to probe for loc file: %ls in path: %ls", wzLocFileName, wzModulePath);
1606
1607
hr = LocLoadFromFile(sczLocPath, &_wixLoc);
1608
BalExitOnFailure1(hr, "Failed to load loc file from path: %ls", sczLocPath);
1609
1610
if (WIX_LOCALIZATION_LANGUAGE_NOT_SET != _wixLoc->dwLangId) {
1611
::SetThreadLocale(_wixLoc->dwLangId);
1612
}
1613
1614
hr = StrAllocString(&_confirmCloseMessage, L"#(loc.ConfirmCancelMessage)", 0);
1615
ExitOnFailure(hr, "Failed to initialize confirm message loc identifier.");
1616
1617
hr = LocLocalizeString(_wixLoc, &_confirmCloseMessage);
1618
BalExitOnFailure1(hr, "Failed to localize confirm close message: %ls", _confirmCloseMessage);
1619
1620
LExit:
1621
ReleaseStr(sczLocPath);
1622
1623
return hr;
1624
}
1625
1626
1627
HRESULT LoadTheme(__in_z LPCWSTR wzModulePath, __in_z_opt LPCWSTR wzLanguage) {
1628
HRESULT hr = S_OK;
1629
LPWSTR sczThemePath = nullptr;
1630
LPCWSTR wzThemeFileName = L"Default.thm";
1631
LPWSTR sczCaption = nullptr;
1632
1633
hr = LocProbeForFile(wzModulePath, wzThemeFileName, wzLanguage, &sczThemePath);
1634
BalExitOnFailure2(hr, "Failed to probe for theme file: %ls in path: %ls", wzThemeFileName, wzModulePath);
1635
1636
hr = ThemeLoadFromFile(sczThemePath, &_theme);
1637
BalExitOnFailure1(hr, "Failed to load theme from path: %ls", sczThemePath);
1638
1639
hr = ThemeLocalize(_theme, _wixLoc);
1640
BalExitOnFailure1(hr, "Failed to localize theme: %ls", sczThemePath);
1641
1642
// Update the caption if there are any formatted strings in it.
1643
// If the wix developer is showing a hidden variable in the UI, then
1644
// obviously they don't care about keeping it safe so don't go down the
1645
// rabbit hole of making sure that this is securely freed.
1646
hr = BalFormatString(_theme->sczCaption, &sczCaption);
1647
if (SUCCEEDED(hr)) {
1648
ThemeUpdateCaption(_theme, sczCaption);
1649
}
1650
1651
LExit:
1652
ReleaseStr(sczCaption);
1653
ReleaseStr(sczThemePath);
1654
1655
return hr;
1656
}
1657
1658
1659
HRESULT ParseOverridableVariablesFromXml(__in IXMLDOMDocument* pixdManifest) {
1660
HRESULT hr = S_OK;
1661
IXMLDOMNode* pNode = nullptr;
1662
IXMLDOMNodeList* pNodes = nullptr;
1663
DWORD cNodes = 0;
1664
LPWSTR scz = nullptr;
1665
BOOL hidden = FALSE;
1666
1667
// get the list of variables users can override on the command line
1668
hr = XmlSelectNodes(pixdManifest, L"/BootstrapperApplicationData/WixStdbaOverridableVariable", &pNodes);
1669
if (S_FALSE == hr) {
1670
ExitFunction1(hr = S_OK);
1671
}
1672
ExitOnFailure(hr, "Failed to select overridable variable nodes.");
1673
1674
hr = pNodes->get_length((long*)&cNodes);
1675
ExitOnFailure(hr, "Failed to get overridable variable node count.");
1676
1677
if (cNodes) {
1678
hr = DictCreateStringList(&_overridableVariables, 32, DICT_FLAG_NONE);
1679
ExitOnFailure(hr, "Failed to create the string dictionary.");
1680
1681
for (DWORD i = 0; i < cNodes; ++i) {
1682
hr = XmlNextElement(pNodes, &pNode, nullptr);
1683
ExitOnFailure(hr, "Failed to get next node.");
1684
1685
// @Name
1686
hr = XmlGetAttributeEx(pNode, L"Name", &scz);
1687
ExitOnFailure(hr, "Failed to get @Name.");
1688
1689
hr = XmlGetYesNoAttribute(pNode, L"Hidden", &hidden);
1690
1691
if (!hidden) {
1692
hr = DictAddKey(_overridableVariables, scz);
1693
ExitOnFailure1(hr, "Failed to add \"%ls\" to the string dictionary.", scz);
1694
}
1695
1696
// prepare next iteration
1697
ReleaseNullObject(pNode);
1698
}
1699
}
1700
1701
LExit:
1702
ReleaseObject(pNode);
1703
ReleaseObject(pNodes);
1704
ReleaseStr(scz);
1705
return hr;
1706
}
1707
1708
1709
//
1710
// Get the file version of the bootstrapper and record in bootstrapper log file
1711
//
1712
HRESULT GetBundleFileVersion() {
1713
HRESULT hr = S_OK;
1714
ULARGE_INTEGER uliVersion = { };
1715
LPWSTR sczCurrentPath = nullptr;
1716
1717
hr = PathForCurrentProcess(&sczCurrentPath, nullptr);
1718
BalExitOnFailure(hr, "Failed to get bundle path.");
1719
1720
hr = FileVersion(sczCurrentPath, &uliVersion.HighPart, &uliVersion.LowPart);
1721
BalExitOnFailure(hr, "Failed to get bundle file version.");
1722
1723
hr = _engine->SetVariableVersion(PYBA_VARIABLE_BUNDLE_FILE_VERSION, uliVersion.QuadPart);
1724
BalExitOnFailure(hr, "Failed to set WixBundleFileVersion variable.");
1725
1726
LExit:
1727
ReleaseStr(sczCurrentPath);
1728
1729
return hr;
1730
}
1731
1732
1733
//
1734
// CreateMainWindow - creates the main install window.
1735
//
1736
HRESULT CreateMainWindow() {
1737
HRESULT hr = S_OK;
1738
HICON hIcon = reinterpret_cast<HICON>(_theme->hIcon);
1739
WNDCLASSW wc = { };
1740
DWORD dwWindowStyle = 0;
1741
int x = CW_USEDEFAULT;
1742
int y = CW_USEDEFAULT;
1743
POINT ptCursor = { };
1744
HMONITOR hMonitor = nullptr;
1745
MONITORINFO mi = { };
1746
COLORREF fg, bg;
1747
HBRUSH bgBrush;
1748
1749
// If the theme did not provide an icon, try using the icon from the bundle engine.
1750
if (!hIcon) {
1751
HMODULE hBootstrapperEngine = ::GetModuleHandleW(nullptr);
1752
if (hBootstrapperEngine) {
1753
hIcon = ::LoadIconW(hBootstrapperEngine, MAKEINTRESOURCEW(1));
1754
}
1755
}
1756
1757
fg = RGB(0, 0, 0);
1758
bg = RGB(255, 255, 255);
1759
bgBrush = (HBRUSH)(COLOR_WINDOW+1);
1760
if (_theme->dwFontId < _theme->cFonts) {
1761
THEME_FONT *font = &_theme->rgFonts[_theme->dwFontId];
1762
fg = font->crForeground;
1763
bg = font->crBackground;
1764
bgBrush = font->hBackground;
1765
RemapColor(&fg, &bg, &bgBrush);
1766
}
1767
1768
// Register the window class and create the window.
1769
wc.lpfnWndProc = PythonBootstrapperApplication::WndProc;
1770
wc.hInstance = _hModule;
1771
wc.hIcon = hIcon;
1772
wc.hCursor = ::LoadCursorW(nullptr, (LPCWSTR)IDC_ARROW);
1773
wc.hbrBackground = bgBrush;
1774
wc.lpszMenuName = nullptr;
1775
wc.lpszClassName = PYBA_WINDOW_CLASS;
1776
if (!::RegisterClassW(&wc)) {
1777
ExitWithLastError(hr, "Failed to register window.");
1778
}
1779
1780
_registered = TRUE;
1781
1782
// Calculate the window style based on the theme style and command display value.
1783
dwWindowStyle = _theme->dwStyle;
1784
if (BOOTSTRAPPER_DISPLAY_NONE >= _command.display) {
1785
dwWindowStyle &= ~WS_VISIBLE;
1786
}
1787
1788
// Don't show the window if there is a splash screen (it will be made visible when the splash screen is hidden)
1789
if (::IsWindow(_command.hwndSplashScreen)) {
1790
dwWindowStyle &= ~WS_VISIBLE;
1791
}
1792
1793
// Center the window on the monitor with the mouse.
1794
if (::GetCursorPos(&ptCursor)) {
1795
hMonitor = ::MonitorFromPoint(ptCursor, MONITOR_DEFAULTTONEAREST);
1796
if (hMonitor) {
1797
mi.cbSize = sizeof(mi);
1798
if (::GetMonitorInfoW(hMonitor, &mi)) {
1799
x = mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - _theme->nWidth) / 2;
1800
y = mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - _theme->nHeight) / 2;
1801
}
1802
}
1803
}
1804
1805
_hWnd = ::CreateWindowExW(
1806
0,
1807
wc.lpszClassName,
1808
_theme->sczCaption,
1809
dwWindowStyle,
1810
x,
1811
y,
1812
_theme->nWidth,
1813
_theme->nHeight,
1814
HWND_DESKTOP,
1815
nullptr,
1816
_hModule,
1817
this
1818
);
1819
ExitOnNullWithLastError(_hWnd, hr, "Failed to create window.");
1820
1821
hr = S_OK;
1822
1823
LExit:
1824
return hr;
1825
}
1826
1827
1828
//
1829
// InitializeTaskbarButton - initializes taskbar button for progress.
1830
//
1831
void InitializeTaskbarButton() {
1832
HRESULT hr = S_OK;
1833
1834
hr = ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), reinterpret_cast<LPVOID*>(&_taskbarList));
1835
if (REGDB_E_CLASSNOTREG == hr) {
1836
// not supported before Windows 7
1837
ExitFunction1(hr = S_OK);
1838
}
1839
BalExitOnFailure(hr, "Failed to create ITaskbarList3. Continuing.");
1840
1841
_taskbarButtonCreatedMessage = ::RegisterWindowMessageW(L"TaskbarButtonCreated");
1842
BalExitOnNullWithLastError(_taskbarButtonCreatedMessage, hr, "Failed to get TaskbarButtonCreated message. Continuing.");
1843
1844
LExit:
1845
return;
1846
}
1847
1848
//
1849
// DestroyMainWindow - clean up all the window registration.
1850
//
1851
void DestroyMainWindow() {
1852
if (::IsWindow(_hWnd)) {
1853
::DestroyWindow(_hWnd);
1854
_hWnd = nullptr;
1855
_taskbarButtonOK = FALSE;
1856
}
1857
1858
if (_registered) {
1859
::UnregisterClassW(PYBA_WINDOW_CLASS, _hModule);
1860
_registered = FALSE;
1861
}
1862
}
1863
1864
1865
//
1866
// WndProc - standard windows message handler.
1867
//
1868
static LRESULT CALLBACK WndProc(
1869
__in HWND hWnd,
1870
__in UINT uMsg,
1871
__in WPARAM wParam,
1872
__in LPARAM lParam
1873
) {
1874
#pragma warning(suppress:4312)
1875
auto pBA = reinterpret_cast<PythonBootstrapperApplication*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
1876
1877
switch (uMsg) {
1878
case WM_NCCREATE: {
1879
LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
1880
pBA = reinterpret_cast<PythonBootstrapperApplication*>(lpcs->lpCreateParams);
1881
#pragma warning(suppress:4244)
1882
::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pBA));
1883
break;
1884
}
1885
1886
case WM_NCDESTROY: {
1887
LRESULT lres = ThemeDefWindowProc(pBA ? pBA->_theme : nullptr, hWnd, uMsg, wParam, lParam);
1888
::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
1889
return lres;
1890
}
1891
1892
case WM_CREATE:
1893
if (!pBA->OnCreate(hWnd)) {
1894
return -1;
1895
}
1896
break;
1897
1898
case WM_QUERYENDSESSION:
1899
return IDCANCEL != pBA->OnSystemShutdown(static_cast<DWORD>(lParam), IDCANCEL);
1900
1901
case WM_CLOSE:
1902
// If the user chose not to close, do *not* let the default window proc handle the message.
1903
if (!pBA->OnClose()) {
1904
return 0;
1905
}
1906
break;
1907
1908
case WM_DESTROY:
1909
::PostQuitMessage(0);
1910
break;
1911
1912
case WM_PAINT: __fallthrough;
1913
case WM_ERASEBKGND:
1914
if (pBA && pBA->_suppressPaint) {
1915
return TRUE;
1916
}
1917
break;
1918
1919
case WM_PYBA_SHOW_HELP:
1920
pBA->OnShowHelp();
1921
return 0;
1922
1923
case WM_PYBA_DETECT_PACKAGES:
1924
pBA->OnDetect();
1925
return 0;
1926
1927
case WM_PYBA_PLAN_PACKAGES:
1928
pBA->OnPlan(static_cast<BOOTSTRAPPER_ACTION>(lParam));
1929
return 0;
1930
1931
case WM_PYBA_APPLY_PACKAGES:
1932
pBA->OnApply();
1933
return 0;
1934
1935
case WM_PYBA_CHANGE_STATE:
1936
pBA->OnChangeState(static_cast<PYBA_STATE>(lParam));
1937
return 0;
1938
1939
case WM_PYBA_SHOW_FAILURE:
1940
pBA->OnShowFailure();
1941
return 0;
1942
1943
case WM_COMMAND:
1944
switch (LOWORD(wParam)) {
1945
// Customize commands
1946
// Success/failure commands
1947
case ID_SUCCESS_RESTART_BUTTON: __fallthrough;
1948
case ID_FAILURE_RESTART_BUTTON:
1949
pBA->OnClickRestartButton();
1950
return 0;
1951
1952
case IDCANCEL: __fallthrough;
1953
case ID_INSTALL_CANCEL_BUTTON: __fallthrough;
1954
case ID_CUSTOM1_CANCEL_BUTTON: __fallthrough;
1955
case ID_CUSTOM2_CANCEL_BUTTON: __fallthrough;
1956
case ID_MODIFY_CANCEL_BUTTON: __fallthrough;
1957
case ID_PROGRESS_CANCEL_BUTTON: __fallthrough;
1958
case ID_SUCCESS_CANCEL_BUTTON: __fallthrough;
1959
case ID_FAILURE_CANCEL_BUTTON: __fallthrough;
1960
case ID_CLOSE_BUTTON:
1961
pBA->OnCommand(ID_CLOSE_BUTTON);
1962
return 0;
1963
1964
default:
1965
pBA->OnCommand((CONTROL_ID)LOWORD(wParam));
1966
}
1967
break;
1968
1969
case WM_NOTIFY:
1970
if (lParam) {
1971
LPNMHDR pnmhdr = reinterpret_cast<LPNMHDR>(lParam);
1972
switch (pnmhdr->code) {
1973
case NM_CLICK: __fallthrough;
1974
case NM_RETURN:
1975
switch (static_cast<DWORD>(pnmhdr->idFrom)) {
1976
case ID_FAILURE_LOGFILE_LINK:
1977
pBA->OnClickLogFileLink();
1978
return 1;
1979
}
1980
}
1981
}
1982
break;
1983
1984
case WM_CTLCOLORSTATIC:
1985
case WM_CTLCOLORBTN:
1986
if (pBA) {
1987
HBRUSH brush = nullptr;
1988
if (pBA->SetControlColor((HWND)lParam, (HDC)wParam, &brush)) {
1989
return (LRESULT)brush;
1990
}
1991
}
1992
break;
1993
}
1994
1995
if (pBA && pBA->_taskbarList && uMsg == pBA->_taskbarButtonCreatedMessage) {
1996
pBA->_taskbarButtonOK = TRUE;
1997
return 0;
1998
}
1999
2000
return ThemeDefWindowProc(pBA ? pBA->_theme : nullptr, hWnd, uMsg, wParam, lParam);
2001
}
2002
2003
//
2004
// OnCreate - finishes loading the theme.
2005
//
2006
BOOL OnCreate(__in HWND hWnd) {
2007
HRESULT hr = S_OK;
2008
2009
hr = ThemeLoadControls(_theme, hWnd, CONTROL_ID_NAMES, countof(CONTROL_ID_NAMES));
2010
BalExitOnFailure(hr, "Failed to load theme controls.");
2011
2012
C_ASSERT(COUNT_PAGE == countof(PAGE_NAMES));
2013
C_ASSERT(countof(_pageIds) == countof(PAGE_NAMES));
2014
2015
ThemeGetPageIds(_theme, PAGE_NAMES, _pageIds, countof(_pageIds));
2016
2017
// Initialize the text on all "application" (non-page) controls.
2018
for (DWORD i = 0; i < _theme->cControls; ++i) {
2019
THEME_CONTROL* pControl = _theme->rgControls + i;
2020
LPWSTR text = nullptr;
2021
2022
if (!pControl->wPageId && pControl->sczText && *pControl->sczText) {
2023
HRESULT hrFormat;
2024
2025
// If the wix developer is showing a hidden variable in the UI,
2026
// then obviously they don't care about keeping it safe so don't
2027
// go down the rabbit hole of making sure that this is securely
2028
// freed.
2029
hrFormat = BalFormatString(pControl->sczText, &text);
2030
if (SUCCEEDED(hrFormat)) {
2031
ThemeSetTextControl(_theme, pControl->wId, text);
2032
ReleaseStr(text);
2033
}
2034
}
2035
}
2036
2037
LExit:
2038
return SUCCEEDED(hr);
2039
}
2040
2041
void RemapColor(COLORREF *fg, COLORREF *bg, HBRUSH *bgBrush) {
2042
if (*fg == RGB(0, 0, 0)) {
2043
*fg = GetSysColor(COLOR_WINDOWTEXT);
2044
} else if (*fg == RGB(128, 128, 128)) {
2045
*fg = GetSysColor(COLOR_GRAYTEXT);
2046
}
2047
if (*bgBrush && *bg == RGB(255, 255, 255)) {
2048
*bg = GetSysColor(COLOR_WINDOW);
2049
*bgBrush = GetSysColorBrush(COLOR_WINDOW);
2050
}
2051
}
2052
2053
BOOL SetControlColor(HWND hWnd, HDC hDC, HBRUSH *brush) {
2054
for (int i = 0; i < _theme->cControls; ++i) {
2055
if (_theme->rgControls[i].hWnd != hWnd) {
2056
continue;
2057
}
2058
2059
DWORD fontId = _theme->rgControls[i].dwFontId;
2060
if (fontId > _theme->cFonts) {
2061
fontId = 0;
2062
}
2063
THEME_FONT *fnt = &_theme->rgFonts[fontId];
2064
2065
COLORREF fg = fnt->crForeground, bg = fnt->crBackground;
2066
*brush = fnt->hBackground;
2067
RemapColor(&fg, &bg, brush);
2068
::SetTextColor(hDC, fg);
2069
::SetBkColor(hDC, bg);
2070
2071
return TRUE;
2072
}
2073
return FALSE;
2074
}
2075
2076
//
2077
// OnShowFailure - display the failure page.
2078
//
2079
void OnShowFailure() {
2080
SetState(PYBA_STATE_FAILED, S_OK);
2081
2082
// If the UI should be visible, display it now and hide the splash screen
2083
if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) {
2084
::ShowWindow(_theme->hwndParent, SW_SHOW);
2085
}
2086
2087
_engine->CloseSplashScreen();
2088
2089
return;
2090
}
2091
2092
2093
//
2094
// OnShowHelp - display the help page.
2095
//
2096
void OnShowHelp() {
2097
SetState(PYBA_STATE_HELP, S_OK);
2098
2099
// If the UI should be visible, display it now and hide the splash screen
2100
if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) {
2101
::ShowWindow(_theme->hwndParent, SW_SHOW);
2102
}
2103
2104
_engine->CloseSplashScreen();
2105
2106
return;
2107
}
2108
2109
2110
//
2111
// OnDetect - start the processing of packages.
2112
//
2113
void OnDetect() {
2114
HRESULT hr = S_OK;
2115
2116
if (_baFunction) {
2117
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running detect BA function");
2118
hr = _baFunction->OnDetect();
2119
BalExitOnFailure(hr, "Failed calling detect BA function.");
2120
}
2121
2122
SetState(PYBA_STATE_DETECTING, hr);
2123
2124
// If the UI should be visible, display it now and hide the splash screen
2125
if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) {
2126
::ShowWindow(_theme->hwndParent, SW_SHOW);
2127
}
2128
2129
_engine->CloseSplashScreen();
2130
2131
// Tell the core we're ready for the packages to be processed now.
2132
hr = _engine->Detect();
2133
BalExitOnFailure(hr, "Failed to start detecting chain.");
2134
2135
LExit:
2136
if (FAILED(hr)) {
2137
SetState(PYBA_STATE_DETECTING, hr);
2138
}
2139
2140
return;
2141
}
2142
2143
HRESULT UpdateUIStrings(__in BOOTSTRAPPER_ACTION action) {
2144
HRESULT hr = S_OK;
2145
LPCWSTR likeInstalling = nullptr;
2146
LPCWSTR likeInstallation = nullptr;
2147
switch (action) {
2148
case BOOTSTRAPPER_ACTION_INSTALL:
2149
likeInstalling = L"Installing";
2150
likeInstallation = L"Installation";
2151
break;
2152
case BOOTSTRAPPER_ACTION_MODIFY:
2153
// For modify, we actually want to pass INSTALL
2154
action = BOOTSTRAPPER_ACTION_INSTALL;
2155
likeInstalling = L"Modifying";
2156
likeInstallation = L"Modification";
2157
break;
2158
case BOOTSTRAPPER_ACTION_REPAIR:
2159
likeInstalling = L"Repairing";
2160
likeInstallation = L"Repair";
2161
break;
2162
case BOOTSTRAPPER_ACTION_UNINSTALL:
2163
likeInstalling = L"Uninstalling";
2164
likeInstallation = L"Uninstallation";
2165
break;
2166
}
2167
2168
if (likeInstalling) {
2169
LPWSTR locName = nullptr;
2170
LOC_STRING *locText = nullptr;
2171
hr = StrAllocFormatted(&locName, L"#(loc.%ls)", likeInstalling);
2172
if (SUCCEEDED(hr)) {
2173
hr = LocGetString(_wixLoc, locName, &locText);
2174
ReleaseStr(locName);
2175
}
2176
_engine->SetVariableString(
2177
L"ActionLikeInstalling",
2178
SUCCEEDED(hr) && locText ? locText->wzText : likeInstalling
2179
);
2180
}
2181
2182
if (likeInstallation) {
2183
LPWSTR locName = nullptr;
2184
LOC_STRING *locText = nullptr;
2185
hr = StrAllocFormatted(&locName, L"#(loc.%ls)", likeInstallation);
2186
if (SUCCEEDED(hr)) {
2187
hr = LocGetString(_wixLoc, locName, &locText);
2188
ReleaseStr(locName);
2189
}
2190
_engine->SetVariableString(
2191
L"ActionLikeInstallation",
2192
SUCCEEDED(hr) && locText ? locText->wzText : likeInstallation
2193
);
2194
}
2195
return hr;
2196
}
2197
2198
//
2199
// OnPlan - plan the detected changes.
2200
//
2201
void OnPlan(__in BOOTSTRAPPER_ACTION action) {
2202
HRESULT hr = S_OK;
2203
2204
_plannedAction = action;
2205
2206
hr = UpdateUIStrings(action);
2207
BalExitOnFailure(hr, "Failed to update strings");
2208
2209
// If we are going to apply a downgrade, bail.
2210
if (_downgradingOtherVersion && BOOTSTRAPPER_ACTION_UNINSTALL < action) {
2211
if (_suppressDowngradeFailure) {
2212
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "A newer version of this product is installed but downgrade failure has been suppressed; continuing...");
2213
} else {
2214
hr = HRESULT_FROM_WIN32(ERROR_PRODUCT_VERSION);
2215
BalExitOnFailure(hr, "Cannot install a product when a newer version is installed.");
2216
}
2217
}
2218
2219
SetState(PYBA_STATE_PLANNING, hr);
2220
2221
if (_baFunction) {
2222
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running plan BA function");
2223
_baFunction->OnPlan();
2224
}
2225
2226
hr = _engine->Plan(action);
2227
BalExitOnFailure(hr, "Failed to start planning packages.");
2228
2229
LExit:
2230
if (FAILED(hr)) {
2231
SetState(PYBA_STATE_PLANNING, hr);
2232
}
2233
2234
return;
2235
}
2236
2237
2238
//
2239
// OnApply - apply the packages.
2240
//
2241
void OnApply() {
2242
HRESULT hr = S_OK;
2243
2244
SetState(PYBA_STATE_APPLYING, hr);
2245
SetProgressState(hr);
2246
SetTaskbarButtonProgress(0);
2247
2248
hr = _engine->Apply(_hWnd);
2249
BalExitOnFailure(hr, "Failed to start applying packages.");
2250
2251
ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, TRUE); // ensure the cancel button is enabled before starting.
2252
2253
LExit:
2254
if (FAILED(hr)) {
2255
SetState(PYBA_STATE_APPLYING, hr);
2256
}
2257
2258
return;
2259
}
2260
2261
2262
//
2263
// OnChangeState - change state.
2264
//
2265
void OnChangeState(__in PYBA_STATE state) {
2266
LPWSTR unformattedText = nullptr;
2267
2268
_state = state;
2269
2270
// If our install is at the end (success or failure) and we're not showing full UI
2271
// then exit (prompt for restart if required).
2272
if ((PYBA_STATE_APPLIED <= _state && BOOTSTRAPPER_DISPLAY_FULL > _command.display)) {
2273
// If a restart was required but we were not automatically allowed to
2274
// accept the reboot then do the prompt.
2275
if (_restartRequired && !_allowRestart) {
2276
StrAllocFromError(&unformattedText, HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED), nullptr);
2277
2278
_allowRestart = IDOK == ::MessageBoxW(
2279
_hWnd,
2280
unformattedText ? unformattedText : L"The requested operation is successful. Changes will not be effective until the system is rebooted.",
2281
_theme->sczCaption,
2282
MB_ICONEXCLAMATION | MB_OKCANCEL
2283
);
2284
}
2285
2286
// Quietly exit.
2287
::PostMessageW(_hWnd, WM_CLOSE, 0, 0);
2288
} else { // try to change the pages.
2289
DWORD newPageId = 0;
2290
DeterminePageId(_state, &newPageId);
2291
2292
if (_visiblePageId != newPageId) {
2293
ShowPage(newPageId);
2294
}
2295
}
2296
2297
ReleaseStr(unformattedText);
2298
}
2299
2300
//
2301
// Called before showing a page to handle all controls.
2302
//
2303
void ProcessPageControls(THEME_PAGE *pPage) {
2304
if (!pPage) {
2305
return;
2306
}
2307
2308
for (DWORD i = 0; i < pPage->cControlIndices; ++i) {
2309
THEME_CONTROL* pControl = _theme->rgControls + pPage->rgdwControlIndices[i];
2310
BOOL enableControl = TRUE;
2311
2312
// If this is a named control, try to set its default state.
2313
if (pControl->sczName && *pControl->sczName) {
2314
// If this is a checkable control, try to set its default state
2315
// to the state of a matching named Burn variable.
2316
if (IsCheckable(pControl)) {
2317
LONGLONG llValue = 0;
2318
HRESULT hr = BalGetNumericVariable(pControl->sczName, &llValue);
2319
2320
// If the control value isn't set then disable it.
2321
if (!SUCCEEDED(hr)) {
2322
enableControl = FALSE;
2323
} else {
2324
ThemeSendControlMessage(
2325
_theme,
2326
pControl->wId,
2327
BM_SETCHECK,
2328
SUCCEEDED(hr) && llValue ? BST_CHECKED : BST_UNCHECKED,
2329
0
2330
);
2331
}
2332
}
2333
2334
// Hide or disable controls based on the control name with 'State' appended
2335
LPWSTR controlName = nullptr;
2336
HRESULT hr = StrAllocFormatted(&controlName, L"%lsState", pControl->sczName);
2337
if (SUCCEEDED(hr)) {
2338
LPWSTR controlState = nullptr;
2339
hr = BalGetStringVariable(controlName, &controlState);
2340
if (SUCCEEDED(hr) && controlState && *controlState) {
2341
if (controlState[0] == '[') {
2342
LPWSTR formatted = nullptr;
2343
if (SUCCEEDED(BalFormatString(controlState, &formatted))) {
2344
StrFree(controlState);
2345
controlState = formatted;
2346
}
2347
}
2348
2349
if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, controlState, -1, L"disable", -1)) {
2350
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Disable control %ls", pControl->sczName);
2351
enableControl = FALSE;
2352
} else if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, controlState, -1, L"hide", -1)) {
2353
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Hide control %ls", pControl->sczName);
2354
// TODO: This doesn't work
2355
ThemeShowControl(_theme, pControl->wId, SW_HIDE);
2356
} else {
2357
// An explicit state can override the lack of a
2358
// backing variable.
2359
enableControl = TRUE;
2360
}
2361
}
2362
StrFree(controlState);
2363
}
2364
StrFree(controlName);
2365
controlName = nullptr;
2366
2367
2368
// If a command link has a note, then add it.
2369
if ((pControl->dwStyle & BS_TYPEMASK) == BS_COMMANDLINK ||
2370
(pControl->dwStyle & BS_TYPEMASK) == BS_DEFCOMMANDLINK) {
2371
hr = StrAllocFormatted(&controlName, L"#(loc.%lsNote)", pControl->sczName);
2372
if (SUCCEEDED(hr)) {
2373
LOC_STRING *locText = nullptr;
2374
hr = LocGetString(_wixLoc, controlName, &locText);
2375
if (SUCCEEDED(hr) && locText && locText->wzText && locText->wzText[0]) {
2376
LPWSTR text = nullptr;
2377
hr = BalFormatString(locText->wzText, &text);
2378
if (SUCCEEDED(hr) && text && text[0]) {
2379
ThemeSendControlMessage(_theme, pControl->wId, BCM_SETNOTE, 0, (LPARAM)text);
2380
ReleaseStr(text);
2381
text = nullptr;
2382
}
2383
}
2384
ReleaseStr(controlName);
2385
controlName = nullptr;
2386
}
2387
hr = S_OK;
2388
}
2389
}
2390
2391
ThemeControlEnable(_theme, pControl->wId, enableControl);
2392
2393
// Format the text in each of the new page's controls
2394
if (pControl->sczText && *pControl->sczText) {
2395
// If the wix developer is showing a hidden variable
2396
// in the UI, then obviously they don't care about
2397
// keeping it safe so don't go down the rabbit hole
2398
// of making sure that this is securely freed.
2399
LPWSTR text = nullptr;
2400
HRESULT hr = BalFormatString(pControl->sczText, &text);
2401
if (SUCCEEDED(hr)) {
2402
ThemeSetTextControl(_theme, pControl->wId, text);
2403
}
2404
}
2405
}
2406
}
2407
2408
//
2409
// OnClose - called when the window is trying to be closed.
2410
//
2411
BOOL OnClose() {
2412
BOOL close = FALSE;
2413
2414
// If we've already succeeded or failed or showing the help page, just close (prompts are annoying if the bootstrapper is done).
2415
if (PYBA_STATE_APPLIED <= _state || PYBA_STATE_HELP == _state) {
2416
close = TRUE;
2417
} else {
2418
// prompt the user or force the cancel if there is no UI.
2419
close = PromptCancel(
2420
_hWnd,
2421
BOOTSTRAPPER_DISPLAY_FULL != _command.display,
2422
_confirmCloseMessage ? _confirmCloseMessage : L"Are you sure you want to cancel?",
2423
_theme->sczCaption
2424
);
2425
}
2426
2427
// If we're doing progress then we never close, we just cancel to let rollback occur.
2428
if (PYBA_STATE_APPLYING <= _state && PYBA_STATE_APPLIED > _state) {
2429
// If we canceled disable cancel button since clicking it again is silly.
2430
if (close) {
2431
ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, FALSE);
2432
}
2433
2434
close = FALSE;
2435
}
2436
2437
return close;
2438
}
2439
2440
//
2441
// OnClickCloseButton - close the application.
2442
//
2443
void OnClickCloseButton() {
2444
::SendMessageW(_hWnd, WM_CLOSE, 0, 0);
2445
}
2446
2447
2448
2449
//
2450
// OnClickRestartButton - allows the restart and closes the app.
2451
//
2452
void OnClickRestartButton() {
2453
AssertSz(_restartRequired, "Restart must be requested to be able to click on the restart button.");
2454
2455
_allowRestart = TRUE;
2456
::SendMessageW(_hWnd, WM_CLOSE, 0, 0);
2457
2458
return;
2459
}
2460
2461
2462
//
2463
// OnClickLogFileLink - show the log file.
2464
//
2465
void OnClickLogFileLink() {
2466
HRESULT hr = S_OK;
2467
LPWSTR sczLogFile = nullptr;
2468
2469
hr = BalGetStringVariable(_bundle.sczLogVariable, &sczLogFile);
2470
BalExitOnFailure1(hr, "Failed to get log file variable '%ls'.", _bundle.sczLogVariable);
2471
2472
hr = ShelExec(L"notepad.exe", sczLogFile, L"open", nullptr, SW_SHOWDEFAULT, _hWnd, nullptr);
2473
BalExitOnFailure1(hr, "Failed to open log file target: %ls", sczLogFile);
2474
2475
LExit:
2476
ReleaseStr(sczLogFile);
2477
2478
return;
2479
}
2480
2481
2482
//
2483
// SetState
2484
//
2485
void SetState(__in PYBA_STATE state, __in HRESULT hrStatus) {
2486
if (FAILED(hrStatus)) {
2487
_hrFinal = hrStatus;
2488
}
2489
2490
if (FAILED(_hrFinal)) {
2491
state = PYBA_STATE_FAILED;
2492
}
2493
2494
if (_state != state) {
2495
::PostMessageW(_hWnd, WM_PYBA_CHANGE_STATE, 0, state);
2496
}
2497
}
2498
2499
//
2500
// GoToPage
2501
//
2502
void GoToPage(__in PAGE page) {
2503
_installPage = page;
2504
::PostMessageW(_hWnd, WM_PYBA_CHANGE_STATE, 0, _state);
2505
}
2506
2507
void DeterminePageId(__in PYBA_STATE state, __out DWORD* pdwPageId) {
2508
LONGLONG simple;
2509
2510
if (BOOTSTRAPPER_DISPLAY_PASSIVE == _command.display) {
2511
switch (state) {
2512
case PYBA_STATE_INITIALIZED:
2513
*pdwPageId = BOOTSTRAPPER_ACTION_HELP == _command.action
2514
? _pageIds[PAGE_HELP]
2515
: _pageIds[PAGE_LOADING];
2516
break;
2517
2518
case PYBA_STATE_HELP:
2519
*pdwPageId = _pageIds[PAGE_HELP];
2520
break;
2521
2522
case PYBA_STATE_DETECTING:
2523
*pdwPageId = _pageIds[PAGE_LOADING]
2524
? _pageIds[PAGE_LOADING]
2525
: _pageIds[PAGE_PROGRESS_PASSIVE]
2526
? _pageIds[PAGE_PROGRESS_PASSIVE]
2527
: _pageIds[PAGE_PROGRESS];
2528
break;
2529
2530
case PYBA_STATE_DETECTED: __fallthrough;
2531
case PYBA_STATE_PLANNING: __fallthrough;
2532
case PYBA_STATE_PLANNED: __fallthrough;
2533
case PYBA_STATE_APPLYING: __fallthrough;
2534
case PYBA_STATE_CACHING: __fallthrough;
2535
case PYBA_STATE_CACHED: __fallthrough;
2536
case PYBA_STATE_EXECUTING: __fallthrough;
2537
case PYBA_STATE_EXECUTED:
2538
*pdwPageId = _pageIds[PAGE_PROGRESS_PASSIVE]
2539
? _pageIds[PAGE_PROGRESS_PASSIVE]
2540
: _pageIds[PAGE_PROGRESS];
2541
break;
2542
2543
default:
2544
*pdwPageId = 0;
2545
break;
2546
}
2547
} else if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) {
2548
switch (state) {
2549
case PYBA_STATE_INITIALIZING:
2550
*pdwPageId = 0;
2551
break;
2552
2553
case PYBA_STATE_INITIALIZED:
2554
*pdwPageId = BOOTSTRAPPER_ACTION_HELP == _command.action
2555
? _pageIds[PAGE_HELP]
2556
: _pageIds[PAGE_LOADING];
2557
break;
2558
2559
case PYBA_STATE_HELP:
2560
*pdwPageId = _pageIds[PAGE_HELP];
2561
break;
2562
2563
case PYBA_STATE_DETECTING:
2564
*pdwPageId = _pageIds[PAGE_LOADING];
2565
break;
2566
2567
case PYBA_STATE_DETECTED:
2568
if (_installPage == PAGE_LOADING) {
2569
switch (_command.action) {
2570
case BOOTSTRAPPER_ACTION_INSTALL:
2571
if (_upgrading) {
2572
_installPage = PAGE_UPGRADE;
2573
} else if (SUCCEEDED(BalGetNumericVariable(L"SimpleInstall", &simple)) && simple) {
2574
_installPage = PAGE_SIMPLE_INSTALL;
2575
} else {
2576
_installPage = PAGE_INSTALL;
2577
}
2578
break;
2579
2580
case BOOTSTRAPPER_ACTION_MODIFY: __fallthrough;
2581
case BOOTSTRAPPER_ACTION_REPAIR: __fallthrough;
2582
case BOOTSTRAPPER_ACTION_UNINSTALL:
2583
_installPage = PAGE_MODIFY;
2584
break;
2585
}
2586
}
2587
*pdwPageId = _pageIds[_installPage];
2588
break;
2589
2590
case PYBA_STATE_PLANNING: __fallthrough;
2591
case PYBA_STATE_PLANNED: __fallthrough;
2592
case PYBA_STATE_APPLYING: __fallthrough;
2593
case PYBA_STATE_CACHING: __fallthrough;
2594
case PYBA_STATE_CACHED: __fallthrough;
2595
case PYBA_STATE_EXECUTING: __fallthrough;
2596
case PYBA_STATE_EXECUTED:
2597
*pdwPageId = _pageIds[PAGE_PROGRESS];
2598
break;
2599
2600
case PYBA_STATE_APPLIED:
2601
*pdwPageId = _pageIds[PAGE_SUCCESS];
2602
break;
2603
2604
case PYBA_STATE_FAILED:
2605
*pdwPageId = _pageIds[PAGE_FAILURE];
2606
break;
2607
}
2608
}
2609
}
2610
2611
BOOL WillElevate() {
2612
static BAL_CONDITION WILL_ELEVATE_CONDITION = {
2613
L"not WixBundleElevated and ("
2614
/*Elevate when installing for all users*/
2615
L"InstallAllUsers or "
2616
/*Elevate when installing the launcher for all users and it was not detected*/
2617
L"(Include_launcher and InstallLauncherAllUsers and not DetectedLauncher)"
2618
L")",
2619
L""
2620
};
2621
BOOL result;
2622
2623
return SUCCEEDED(BalConditionEvaluate(&WILL_ELEVATE_CONDITION, _engine, &result, nullptr)) && result;
2624
}
2625
2626
BOOL IsCrtInstalled() {
2627
if (_crtInstalledToken > 0) {
2628
return TRUE;
2629
} else if (_crtInstalledToken == 0) {
2630
return FALSE;
2631
}
2632
2633
// Check whether at least CRT v10.0.10137.0 is available.
2634
// It should only be installed as a Windows Update package, which means
2635
// we don't need to worry about 32-bit/64-bit.
2636
LPCWSTR crtFile = L"ucrtbase.dll";
2637
2638
DWORD cbVer = GetFileVersionInfoSizeW(crtFile, nullptr);
2639
if (!cbVer) {
2640
_crtInstalledToken = 0;
2641
return FALSE;
2642
}
2643
2644
void *pData = malloc(cbVer);
2645
if (!pData) {
2646
_crtInstalledToken = 0;
2647
return FALSE;
2648
}
2649
2650
if (!GetFileVersionInfoW(crtFile, 0, cbVer, pData)) {
2651
free(pData);
2652
_crtInstalledToken = 0;
2653
return FALSE;
2654
}
2655
2656
VS_FIXEDFILEINFO *ffi;
2657
UINT cb;
2658
BOOL result = FALSE;
2659
2660
if (VerQueryValueW(pData, L"\\", (LPVOID*)&ffi, &cb) &&
2661
ffi->dwFileVersionMS == 0x000A0000 && ffi->dwFileVersionLS >= 0x27990000) {
2662
result = TRUE;
2663
}
2664
2665
free(pData);
2666
_crtInstalledToken = result ? 1 : 0;
2667
return result;
2668
}
2669
2670
HRESULT EvaluateConditions() {
2671
HRESULT hr = S_OK;
2672
BOOL result = FALSE;
2673
2674
for (DWORD i = 0; i < _conditions.cConditions; ++i) {
2675
BAL_CONDITION* pCondition = _conditions.rgConditions + i;
2676
2677
hr = BalConditionEvaluate(pCondition, _engine, &result, &_failedMessage);
2678
BalExitOnFailure(hr, "Failed to evaluate condition.");
2679
2680
if (!result) {
2681
// Hope they didn't have hidden variables in their message, because it's going in the log in plaintext.
2682
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "%ls", _failedMessage);
2683
2684
hr = E_WIXSTDBA_CONDITION_FAILED;
2685
// todo: remove in WiX v4, in case people are relying on v3.x logging behavior
2686
BalExitOnFailure1(hr, "Bundle condition evaluated to false: %ls", pCondition->sczCondition);
2687
}
2688
}
2689
2690
ReleaseNullStrSecure(_failedMessage);
2691
2692
LExit:
2693
return hr;
2694
}
2695
2696
2697
void SetTaskbarButtonProgress(__in DWORD dwOverallPercentage) {
2698
HRESULT hr = S_OK;
2699
2700
if (_taskbarButtonOK) {
2701
hr = _taskbarList->SetProgressValue(_hWnd, dwOverallPercentage, 100UL);
2702
BalExitOnFailure1(hr, "Failed to set taskbar button progress to: %d%%.", dwOverallPercentage);
2703
}
2704
2705
LExit:
2706
return;
2707
}
2708
2709
2710
void SetTaskbarButtonState(__in TBPFLAG tbpFlags) {
2711
HRESULT hr = S_OK;
2712
2713
if (_taskbarButtonOK) {
2714
hr = _taskbarList->SetProgressState(_hWnd, tbpFlags);
2715
BalExitOnFailure1(hr, "Failed to set taskbar button state.", tbpFlags);
2716
}
2717
2718
LExit:
2719
return;
2720
}
2721
2722
2723
void SetProgressState(__in HRESULT hrStatus) {
2724
TBPFLAG flag = TBPF_NORMAL;
2725
2726
if (IsCanceled() || HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) == hrStatus) {
2727
flag = TBPF_PAUSED;
2728
} else if (IsRollingBack() || FAILED(hrStatus)) {
2729
flag = TBPF_ERROR;
2730
}
2731
2732
SetTaskbarButtonState(flag);
2733
}
2734
2735
2736
HRESULT LoadBootstrapperBAFunctions() {
2737
HRESULT hr = S_OK;
2738
LPWSTR sczBafPath = nullptr;
2739
2740
hr = PathRelativeToModule(&sczBafPath, L"bafunctions.dll", _hModule);
2741
BalExitOnFailure(hr, "Failed to get path to BA function DLL.");
2742
2743
#ifdef DEBUG
2744
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: LoadBootstrapperBAFunctions() - BA function DLL %ls", sczBafPath);
2745
#endif
2746
2747
_hBAFModule = ::LoadLibraryW(sczBafPath);
2748
if (_hBAFModule) {
2749
auto pfnBAFunctionCreate = reinterpret_cast<PFN_BOOTSTRAPPER_BA_FUNCTION_CREATE>(::GetProcAddress(_hBAFModule, "CreateBootstrapperBAFunction"));
2750
BalExitOnNullWithLastError1(pfnBAFunctionCreate, hr, "Failed to get CreateBootstrapperBAFunction entry-point from: %ls", sczBafPath);
2751
2752
hr = pfnBAFunctionCreate(_engine, _hBAFModule, &_baFunction);
2753
BalExitOnFailure(hr, "Failed to create BA function.");
2754
}
2755
#ifdef DEBUG
2756
else {
2757
BalLogError(HRESULT_FROM_WIN32(::GetLastError()), "PYBA: LoadBootstrapperBAFunctions() - Failed to load DLL %ls", sczBafPath);
2758
}
2759
#endif
2760
2761
LExit:
2762
if (_hBAFModule && !_baFunction) {
2763
::FreeLibrary(_hBAFModule);
2764
_hBAFModule = nullptr;
2765
}
2766
ReleaseStr(sczBafPath);
2767
2768
return hr;
2769
}
2770
2771
BOOL IsCheckable(THEME_CONTROL* pControl) {
2772
if (!pControl->sczName || !pControl->sczName[0]) {
2773
return FALSE;
2774
}
2775
2776
if (pControl->type == THEME_CONTROL_TYPE_CHECKBOX) {
2777
return TRUE;
2778
}
2779
2780
if (pControl->type == THEME_CONTROL_TYPE_BUTTON) {
2781
if ((pControl->dwStyle & BS_TYPEMASK) == BS_AUTORADIOBUTTON) {
2782
return TRUE;
2783
}
2784
}
2785
2786
return FALSE;
2787
}
2788
2789
void SavePageSettings() {
2790
DWORD pageId = 0;
2791
THEME_PAGE* pPage = nullptr;
2792
2793
DeterminePageId(_state, &pageId);
2794
pPage = ThemeGetPage(_theme, pageId);
2795
if (!pPage) {
2796
return;
2797
}
2798
2799
for (DWORD i = 0; i < pPage->cControlIndices; ++i) {
2800
// Loop through all the checkable controls and set a Burn variable
2801
// with that name to true or false.
2802
THEME_CONTROL* pControl = _theme->rgControls + pPage->rgdwControlIndices[i];
2803
if (IsCheckable(pControl) && ThemeControlEnabled(_theme, pControl->wId)) {
2804
BOOL checked = ThemeIsControlChecked(_theme, pControl->wId);
2805
_engine->SetVariableNumeric(pControl->sczName, checked ? 1 : 0);
2806
}
2807
2808
// Loop through all the editbox controls with names and set a
2809
// Burn variable with that name to the contents.
2810
if (THEME_CONTROL_TYPE_EDITBOX == pControl->type && pControl->sczName && *pControl->sczName) {
2811
LPWSTR sczValue = nullptr;
2812
ThemeGetTextControl(_theme, pControl->wId, &sczValue);
2813
_engine->SetVariableString(pControl->sczName, sczValue);
2814
}
2815
}
2816
}
2817
2818
static bool IsTargetPlatformx64(__in IBootstrapperEngine* pEngine) {
2819
WCHAR platform[8];
2820
DWORD platformLen = 8;
2821
2822
if (FAILED(pEngine->GetVariableString(L"TargetPlatform", platform, &platformLen))) {
2823
return S_FALSE;
2824
}
2825
2826
return ::CompareStringW(LOCALE_NEUTRAL, 0, platform, -1, L"x64", -1) == CSTR_EQUAL;
2827
}
2828
2829
static bool IsTargetPlatformARM64(__in IBootstrapperEngine* pEngine) {
2830
WCHAR platform[8];
2831
DWORD platformLen = 8;
2832
2833
if (FAILED(pEngine->GetVariableString(L"TargetPlatform", platform, &platformLen))) {
2834
return S_FALSE;
2835
}
2836
2837
return ::CompareStringW(LOCALE_NEUTRAL, 0, platform, -1, L"ARM64", -1) == CSTR_EQUAL;
2838
}
2839
2840
static HRESULT LoadOptionalFeatureStatesFromKey(
2841
__in IBootstrapperEngine* pEngine,
2842
__in HKEY hkHive,
2843
__in LPCWSTR subkey
2844
) {
2845
HKEY hKey;
2846
LRESULT res;
2847
2848
if (IsTargetPlatformx64(pEngine) || IsTargetPlatformARM64(pEngine)) {
2849
res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
2850
} else {
2851
res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_32KEY, &hKey);
2852
}
2853
if (res == ERROR_FILE_NOT_FOUND) {
2854
return S_FALSE;
2855
}
2856
if (res != ERROR_SUCCESS) {
2857
return HRESULT_FROM_WIN32(res);
2858
}
2859
2860
for (auto p = OPTIONAL_FEATURES; p->regName; ++p) {
2861
res = RegQueryValueExW(hKey, p->regName, nullptr, nullptr, nullptr, nullptr);
2862
if (res == ERROR_FILE_NOT_FOUND) {
2863
pEngine->SetVariableNumeric(p->variableName, 0);
2864
} else if (res == ERROR_SUCCESS) {
2865
pEngine->SetVariableNumeric(p->variableName, 1);
2866
} else {
2867
RegCloseKey(hKey);
2868
return HRESULT_FROM_WIN32(res);
2869
}
2870
}
2871
2872
RegCloseKey(hKey);
2873
return S_OK;
2874
}
2875
2876
static HRESULT LoadTargetDirFromKey(
2877
__in IBootstrapperEngine* pEngine,
2878
__in HKEY hkHive,
2879
__in LPCWSTR subkey
2880
) {
2881
HKEY hKey;
2882
LRESULT res;
2883
DWORD dataType;
2884
BYTE buffer[1024];
2885
DWORD bufferLen = sizeof(buffer);
2886
2887
if (IsTargetPlatformx64(pEngine) || IsTargetPlatformARM64(pEngine)) {
2888
res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
2889
} else {
2890
res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_32KEY, &hKey);
2891
}
2892
if (res == ERROR_FILE_NOT_FOUND) {
2893
return S_FALSE;
2894
}
2895
if (res != ERROR_SUCCESS) {
2896
return HRESULT_FROM_WIN32(res);
2897
}
2898
2899
res = RegQueryValueExW(hKey, nullptr, nullptr, &dataType, buffer, &bufferLen);
2900
if (res == ERROR_SUCCESS && dataType == REG_SZ && bufferLen < sizeof(buffer)) {
2901
pEngine->SetVariableString(L"TargetDir", reinterpret_cast<wchar_t*>(buffer));
2902
}
2903
RegCloseKey(hKey);
2904
return HRESULT_FROM_WIN32(res);
2905
}
2906
2907
static HRESULT LoadAssociateFilesStateFromKey(
2908
__in IBootstrapperEngine* pEngine,
2909
__in HKEY hkHive
2910
) {
2911
const LPCWSTR subkey = L"Software\\Python\\PyLauncher";
2912
HKEY hKey;
2913
LRESULT res;
2914
HRESULT hr;
2915
2916
res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_32KEY, &hKey);
2917
2918
if (res == ERROR_FILE_NOT_FOUND) {
2919
return S_FALSE;
2920
}
2921
if (res != ERROR_SUCCESS) {
2922
return HRESULT_FROM_WIN32(res);
2923
}
2924
2925
res = RegQueryValueExW(hKey, L"AssociateFiles", nullptr, nullptr, nullptr, nullptr);
2926
if (res == ERROR_FILE_NOT_FOUND) {
2927
hr = S_FALSE;
2928
} else if (res == ERROR_SUCCESS) {
2929
hr = S_OK;
2930
} else {
2931
hr = HRESULT_FROM_WIN32(res);
2932
}
2933
2934
RegCloseKey(hKey);
2935
return hr;
2936
}
2937
2938
static void LoadOptionalFeatureStates(__in IBootstrapperEngine* pEngine) {
2939
WCHAR subkeyFmt[256];
2940
WCHAR subkey[256];
2941
DWORD subkeyLen;
2942
HRESULT hr;
2943
HKEY hkHive;
2944
2945
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading state of optional features");
2946
2947
// Get the registry key from the bundle, to save having to duplicate it
2948
// in multiple places.
2949
subkeyLen = sizeof(subkeyFmt) / sizeof(subkeyFmt[0]);
2950
hr = pEngine->GetVariableString(L"OptionalFeaturesRegistryKey", subkeyFmt, &subkeyLen);
2951
BalExitOnFailure(hr, "Failed to locate registry key");
2952
subkeyLen = sizeof(subkey) / sizeof(subkey[0]);
2953
hr = pEngine->FormatString(subkeyFmt, subkey, &subkeyLen);
2954
BalExitOnFailure1(hr, "Failed to format %ls", subkeyFmt);
2955
2956
// Check the current user's registry for existing features
2957
hkHive = HKEY_CURRENT_USER;
2958
hr = LoadOptionalFeatureStatesFromKey(pEngine, hkHive, subkey);
2959
BalExitOnFailure1(hr, "Failed to read from HKCU\\%ls", subkey);
2960
if (hr == S_FALSE) {
2961
// Now check the local machine registry
2962
hkHive = HKEY_LOCAL_MACHINE;
2963
hr = LoadOptionalFeatureStatesFromKey(pEngine, hkHive, subkey);
2964
BalExitOnFailure1(hr, "Failed to read from HKLM\\%ls", subkey);
2965
if (hr == S_OK) {
2966
// Found a system-wide install, so enable these settings.
2967
pEngine->SetVariableNumeric(L"InstallAllUsers", 1);
2968
pEngine->SetVariableNumeric(L"CompileAll", 1);
2969
}
2970
}
2971
2972
if (hr == S_OK) {
2973
// Cannot change InstallAllUsersState when upgrading. While there's
2974
// no good reason to not allow installing a per-user and an all-user
2975
// version simultaneously, Burn can't handle the state management
2976
// and will need to uninstall the old one.
2977
pEngine->SetVariableString(L"InstallAllUsersState", L"disable");
2978
2979
// Get the previous install directory. This can be changed by the
2980
// user.
2981
subkeyLen = sizeof(subkeyFmt) / sizeof(subkeyFmt[0]);
2982
hr = pEngine->GetVariableString(L"TargetDirRegistryKey", subkeyFmt, &subkeyLen);
2983
BalExitOnFailure(hr, "Failed to locate registry key");
2984
subkeyLen = sizeof(subkey) / sizeof(subkey[0]);
2985
hr = pEngine->FormatString(subkeyFmt, subkey, &subkeyLen);
2986
BalExitOnFailure1(hr, "Failed to format %ls", subkeyFmt);
2987
LoadTargetDirFromKey(pEngine, hkHive, subkey);
2988
}
2989
2990
LExit:
2991
return;
2992
}
2993
2994
HRESULT EnsureTargetDir() {
2995
LONGLONG installAllUsers;
2996
LPWSTR targetDir = nullptr, defaultDir = nullptr;
2997
HRESULT hr = BalGetStringVariable(L"TargetDir", &targetDir);
2998
if (FAILED(hr) || !targetDir || !targetDir[0]) {
2999
ReleaseStr(targetDir);
3000
targetDir = nullptr;
3001
3002
hr = BalGetNumericVariable(L"InstallAllUsers", &installAllUsers);
3003
ExitOnFailure(hr, L"Failed to get install scope");
3004
3005
hr = BalGetStringVariable(
3006
installAllUsers ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir",
3007
&defaultDir
3008
);
3009
BalExitOnFailure(hr, "Failed to get the default install directory");
3010
3011
if (!defaultDir || !defaultDir[0]) {
3012
BalLogError(E_INVALIDARG, "Default install directory is blank");
3013
}
3014
3015
hr = BalFormatString(defaultDir, &targetDir);
3016
BalExitOnFailure1(hr, "Failed to format '%ls'", defaultDir);
3017
3018
hr = _engine->SetVariableString(L"TargetDir", targetDir);
3019
BalExitOnFailure(hr, "Failed to set install target directory");
3020
}
3021
LExit:
3022
ReleaseStr(defaultDir);
3023
ReleaseStr(targetDir);
3024
return hr;
3025
}
3026
3027
void ValidateOperatingSystem() {
3028
LOC_STRING *pLocString = nullptr;
3029
3030
if (IsWindowsServer()) {
3031
if (IsWindowsVersionOrGreater(6, 2, 0)) {
3032
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Target OS is Windows Server 2012 or later");
3033
return;
3034
} else if (IsWindowsVersionOrGreater(6, 1, 1)) {
3035
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Detected Windows Server 2008 R2");
3036
} else if (IsWindowsVersionOrGreater(6, 1, 0)) {
3037
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Server 2008 R2");
3038
} else if (IsWindowsVersionOrGreater(6, 0, 0)) {
3039
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Server 2008");
3040
} else {
3041
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Server 2003 or earlier");
3042
}
3043
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Windows Server 2012 or later is required to continue installation");
3044
} else {
3045
if (IsWindows10OrGreater()) {
3046
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Target OS is Windows 10 or later");
3047
return;
3048
} else if (IsWindows8Point1OrGreater()) {
3049
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Target OS is Windows 8.1");
3050
return;
3051
} else if (IsWindows8OrGreater()) {
3052
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows 8");
3053
} else if (IsWindows7OrGreater()) {
3054
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows 7");
3055
} else if (IsWindowsVistaOrGreater()) {
3056
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Vista");
3057
} else {
3058
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows XP or earlier");
3059
}
3060
BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Windows 8.1 or later is required to continue installation");
3061
}
3062
3063
LocGetString(_wixLoc, L"#(loc.FailureOldOS)", &pLocString);
3064
if (pLocString && pLocString->wzText) {
3065
BalFormatString(pLocString->wzText, &_failedMessage);
3066
}
3067
3068
_hrFinal = E_WIXSTDBA_CONDITION_FAILED;
3069
}
3070
3071
public:
3072
//
3073
// Constructor - initialize member variables.
3074
//
3075
PythonBootstrapperApplication(
3076
__in HMODULE hModule,
3077
__in BOOL fPrereq,
3078
__in HRESULT hrHostInitialization,
3079
__in IBootstrapperEngine* pEngine,
3080
__in const BOOTSTRAPPER_COMMAND* pCommand
3081
) : CBalBaseBootstrapperApplication(pEngine, pCommand, 3, 3000) {
3082
_hModule = hModule;
3083
memcpy_s(&_command, sizeof(_command), pCommand, sizeof(BOOTSTRAPPER_COMMAND));
3084
3085
LONGLONG llInstalled = 0;
3086
HRESULT hr = BalGetNumericVariable(L"WixBundleInstalled", &llInstalled);
3087
if (SUCCEEDED(hr) && BOOTSTRAPPER_RESUME_TYPE_REBOOT != _command.resumeType && 0 < llInstalled && BOOTSTRAPPER_ACTION_INSTALL == _command.action) {
3088
_command.action = BOOTSTRAPPER_ACTION_MODIFY;
3089
} else if (0 == llInstalled && (BOOTSTRAPPER_ACTION_MODIFY == _command.action || BOOTSTRAPPER_ACTION_REPAIR == _command.action)) {
3090
_command.action = BOOTSTRAPPER_ACTION_INSTALL;
3091
}
3092
3093
_plannedAction = BOOTSTRAPPER_ACTION_UNKNOWN;
3094
3095
3096
// When resuming from restart doing some install-like operation, try to find the package that forced the
3097
// restart. We'll use this information during planning.
3098
_nextPackageAfterRestart = nullptr;
3099
3100
if (BOOTSTRAPPER_RESUME_TYPE_REBOOT == _command.resumeType && BOOTSTRAPPER_ACTION_UNINSTALL < _command.action) {
3101
// Ensure the forced restart package variable is null when it is an empty string.
3102
HRESULT hr = BalGetStringVariable(L"WixBundleForcedRestartPackage", &_nextPackageAfterRestart);
3103
if (FAILED(hr) || !_nextPackageAfterRestart || !*_nextPackageAfterRestart) {
3104
ReleaseNullStr(_nextPackageAfterRestart);
3105
}
3106
}
3107
3108
_crtInstalledToken = -1;
3109
pEngine->SetVariableNumeric(L"CRTInstalled", IsCrtInstalled() ? 1 : 0);
3110
3111
_wixLoc = nullptr;
3112
memset(&_bundle, 0, sizeof(_bundle));
3113
memset(&_conditions, 0, sizeof(_conditions));
3114
_confirmCloseMessage = nullptr;
3115
_failedMessage = nullptr;
3116
3117
_language = nullptr;
3118
_theme = nullptr;
3119
memset(_pageIds, 0, sizeof(_pageIds));
3120
_hUiThread = nullptr;
3121
_registered = FALSE;
3122
_hWnd = nullptr;
3123
3124
_state = PYBA_STATE_INITIALIZING;
3125
_visiblePageId = 0;
3126
_installPage = PAGE_LOADING;
3127
_hrFinal = hrHostInitialization;
3128
3129
_downgradingOtherVersion = FALSE;
3130
_restartResult = BOOTSTRAPPER_APPLY_RESTART_NONE;
3131
_restartRequired = FALSE;
3132
_allowRestart = FALSE;
3133
3134
_suppressDowngradeFailure = FALSE;
3135
_suppressRepair = FALSE;
3136
_modifying = FALSE;
3137
_upgrading = FALSE;
3138
3139
_overridableVariables = nullptr;
3140
_taskbarList = nullptr;
3141
_taskbarButtonCreatedMessage = UINT_MAX;
3142
_taskbarButtonOK = FALSE;
3143
_showingInternalUIThisPackage = FALSE;
3144
3145
_suppressPaint = FALSE;
3146
3147
pEngine->AddRef();
3148
_engine = pEngine;
3149
3150
_hBAFModule = nullptr;
3151
_baFunction = nullptr;
3152
}
3153
3154
3155
//
3156
// Destructor - release member variables.
3157
//
3158
~PythonBootstrapperApplication() {
3159
AssertSz(!::IsWindow(_hWnd), "Window should have been destroyed before destructor.");
3160
AssertSz(!_theme, "Theme should have been released before destructor.");
3161
3162
ReleaseObject(_taskbarList);
3163
ReleaseDict(_overridableVariables);
3164
ReleaseStr(_failedMessage);
3165
ReleaseStr(_confirmCloseMessage);
3166
BalConditionsUninitialize(&_conditions);
3167
BalInfoUninitialize(&_bundle);
3168
LocFree(_wixLoc);
3169
3170
ReleaseStr(_language);
3171
ReleaseStr(_nextPackageAfterRestart);
3172
ReleaseNullObject(_engine);
3173
3174
if (_hBAFModule) {
3175
::FreeLibrary(_hBAFModule);
3176
_hBAFModule = nullptr;
3177
}
3178
}
3179
3180
private:
3181
HMODULE _hModule;
3182
BOOTSTRAPPER_COMMAND _command;
3183
IBootstrapperEngine* _engine;
3184
BOOTSTRAPPER_ACTION _plannedAction;
3185
3186
LPWSTR _nextPackageAfterRestart;
3187
3188
WIX_LOCALIZATION* _wixLoc;
3189
BAL_INFO_BUNDLE _bundle;
3190
BAL_CONDITIONS _conditions;
3191
LPWSTR _failedMessage;
3192
LPWSTR _confirmCloseMessage;
3193
3194
LPWSTR _language;
3195
THEME* _theme;
3196
DWORD _pageIds[countof(PAGE_NAMES)];
3197
HANDLE _hUiThread;
3198
BOOL _registered;
3199
HWND _hWnd;
3200
3201
PYBA_STATE _state;
3202
HRESULT _hrFinal;
3203
DWORD _visiblePageId;
3204
PAGE _installPage;
3205
3206
BOOL _startedExecution;
3207
DWORD _calculatedCacheProgress;
3208
DWORD _calculatedExecuteProgress;
3209
3210
BOOL _downgradingOtherVersion;
3211
BOOTSTRAPPER_APPLY_RESTART _restartResult;
3212
BOOL _restartRequired;
3213
BOOL _allowRestart;
3214
3215
BOOL _suppressDowngradeFailure;
3216
BOOL _suppressRepair;
3217
BOOL _modifying;
3218
BOOL _upgrading;
3219
3220
int _crtInstalledToken;
3221
3222
STRINGDICT_HANDLE _overridableVariables;
3223
3224
ITaskbarList3* _taskbarList;
3225
UINT _taskbarButtonCreatedMessage;
3226
BOOL _taskbarButtonOK;
3227
BOOL _showingInternalUIThisPackage;
3228
3229
BOOL _suppressPaint;
3230
3231
HMODULE _hBAFModule;
3232
IBootstrapperBAFunction* _baFunction;
3233
};
3234
3235
//
3236
// CreateBootstrapperApplication - creates a new IBootstrapperApplication object.
3237
//
3238
HRESULT CreateBootstrapperApplication(
3239
__in HMODULE hModule,
3240
__in BOOL fPrereq,
3241
__in HRESULT hrHostInitialization,
3242
__in IBootstrapperEngine* pEngine,
3243
__in const BOOTSTRAPPER_COMMAND* pCommand,
3244
__out IBootstrapperApplication** ppApplication
3245
) {
3246
HRESULT hr = S_OK;
3247
3248
if (fPrereq) {
3249
hr = E_INVALIDARG;
3250
ExitWithLastError(hr, "Failed to create UI thread.");
3251
}
3252
3253
PythonBootstrapperApplication* pApplication = nullptr;
3254
3255
pApplication = new PythonBootstrapperApplication(hModule, fPrereq, hrHostInitialization, pEngine, pCommand);
3256
ExitOnNull(pApplication, hr, E_OUTOFMEMORY, "Failed to create new standard bootstrapper application object.");
3257
3258
*ppApplication = pApplication;
3259
pApplication = nullptr;
3260
3261
LExit:
3262
ReleaseObject(pApplication);
3263
return hr;
3264
}
3265
3266