aboutsummaryrefslogtreecommitdiff
path: root/src/engine/msuengine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/msuengine.cpp')
-rw-r--r--src/engine/msuengine.cpp513
1 files changed, 513 insertions, 0 deletions
diff --git a/src/engine/msuengine.cpp b/src/engine/msuengine.cpp
new file mode 100644
index 00000000..3818e932
--- /dev/null
+++ b/src/engine/msuengine.cpp
@@ -0,0 +1,513 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// constants
7
8#define WU_S_REBOOT_REQUIRED 0x00240005L
9#define WU_S_ALREADY_INSTALLED 0x00240006L
10
11
12// function definitions
13static HRESULT EnsureWUServiceEnabled(
14 __in BOOL fStopWusaService,
15 __out SC_HANDLE* pschWu,
16 __out BOOL* pfPreviouslyDisabled
17 );
18static HRESULT SetServiceStartType(
19 __in SC_HANDLE sch,
20 __in DWORD stratType
21 );
22static HRESULT StopWUService(
23 __in SC_HANDLE schWu
24 );
25
26
27extern "C" HRESULT MsuEngineParsePackageFromXml(
28 __in IXMLDOMNode* pixnMsuPackage,
29 __in BURN_PACKAGE* pPackage
30 )
31{
32 HRESULT hr = S_OK;
33
34 // @KB
35 hr = XmlGetAttributeEx(pixnMsuPackage, L"KB", &pPackage->Msu.sczKB);
36 ExitOnFailure(hr, "Failed to get @KB.");
37
38 // @DetectCondition
39 hr = XmlGetAttributeEx(pixnMsuPackage, L"DetectCondition", &pPackage->Msu.sczDetectCondition);
40 ExitOnFailure(hr, "Failed to get @DetectCondition.");
41
42LExit:
43 return hr;
44}
45
46extern "C" void MsuEnginePackageUninitialize(
47 __in BURN_PACKAGE* pPackage
48 )
49{
50 ReleaseNullStr(pPackage->Msu.sczKB);
51 ReleaseNullStr(pPackage->Msu.sczDetectCondition);
52}
53
54extern "C" HRESULT MsuEngineDetectPackage(
55 __in BURN_PACKAGE* pPackage,
56 __in BURN_VARIABLES* pVariables
57 )
58{
59 HRESULT hr = S_OK;
60 BOOL fDetected = FALSE;
61
62 // evaluate detect condition
63 if (pPackage->Msu.sczDetectCondition && *pPackage->Msu.sczDetectCondition)
64 {
65 hr = ConditionEvaluate(pVariables, pPackage->Msu.sczDetectCondition, &fDetected);
66 ExitOnFailure(hr, "Failed to evaluate MSU package detect condition.");
67 }
68
69 // update detect state
70 pPackage->currentState = fDetected ? BOOTSTRAPPER_PACKAGE_STATE_PRESENT : BOOTSTRAPPER_PACKAGE_STATE_ABSENT;
71
72LExit:
73 return hr;
74}
75
76//
77// PlanCalculate - calculates the execute and rollback state for the requested package state.
78//
79extern "C" HRESULT MsuEnginePlanCalculatePackage(
80 __in BURN_PACKAGE* pPackage,
81 __out_opt BOOL* pfBARequestedCache
82 )
83{
84 HRESULT hr = S_OK;
85 BOOTSTRAPPER_ACTION_STATE execute = BOOTSTRAPPER_ACTION_STATE_NONE;
86 BOOTSTRAPPER_ACTION_STATE rollback = BOOTSTRAPPER_ACTION_STATE_NONE;
87 BOOL fBARequestedCache = FALSE;
88
89 BOOL fAllowUninstall = FALSE;
90 OS_VERSION osVersion = OS_VERSION_UNKNOWN;
91 DWORD dwServicePack = 0;
92
93 // We can only uninstall MSU packages if they have a KB and we are on Win7 or newer.
94 OsGetVersion(&osVersion, &dwServicePack);
95 fAllowUninstall = (pPackage->Msu.sczKB && *pPackage->Msu.sczKB) && OS_VERSION_WIN7 <= osVersion;
96
97 // execute action
98 switch (pPackage->currentState)
99 {
100 case BOOTSTRAPPER_PACKAGE_STATE_PRESENT:
101 switch (pPackage->requested)
102 {
103 case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough;
104 case BOOTSTRAPPER_REQUEST_STATE_REPAIR:
105 execute = BOOTSTRAPPER_ACTION_STATE_NONE;
106 break;
107
108 case BOOTSTRAPPER_REQUEST_STATE_ABSENT: __fallthrough;
109 case BOOTSTRAPPER_REQUEST_STATE_CACHE:
110 execute = fAllowUninstall && pPackage->fUninstallable ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE;
111 break;
112
113 case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT:
114 execute = fAllowUninstall ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE;
115 break;
116
117 default:
118 execute = BOOTSTRAPPER_ACTION_STATE_NONE;
119 break;
120 }
121 break;
122
123 case BOOTSTRAPPER_PACKAGE_STATE_ABSENT:
124 switch (pPackage->requested)
125 {
126 case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough;
127 case BOOTSTRAPPER_REQUEST_STATE_REPAIR:
128 execute = BOOTSTRAPPER_ACTION_STATE_INSTALL;
129 break;
130
131 case BOOTSTRAPPER_REQUEST_STATE_CACHE:
132 execute = BOOTSTRAPPER_ACTION_STATE_NONE;
133 fBARequestedCache = TRUE;
134
135 default:
136 execute = BOOTSTRAPPER_ACTION_STATE_NONE;
137 break;
138 }
139 break;
140
141 default:
142 hr = E_INVALIDARG;
143 ExitOnRootFailure(hr, "Invalid package state.");
144 }
145
146 // Calculate the rollback action if there is an execute action.
147 if (BOOTSTRAPPER_ACTION_STATE_NONE != execute)
148 {
149 switch (BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN != pPackage->expected ? pPackage->expected : pPackage->currentState)
150 {
151 case BOOTSTRAPPER_PACKAGE_STATE_PRESENT:
152 switch (pPackage->requested)
153 {
154 case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: __fallthrough;
155 case BOOTSTRAPPER_REQUEST_STATE_ABSENT:
156 rollback = BOOTSTRAPPER_ACTION_STATE_INSTALL;
157 break;
158
159 default:
160 rollback = BOOTSTRAPPER_ACTION_STATE_NONE;
161 break;
162 }
163 break;
164
165 case BOOTSTRAPPER_PACKAGE_STATE_ABSENT:
166 switch (pPackage->requested)
167 {
168 case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough;
169 case BOOTSTRAPPER_REQUEST_STATE_REPAIR:
170 rollback = fAllowUninstall ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE;
171 break;
172
173 default:
174 rollback = BOOTSTRAPPER_ACTION_STATE_NONE;
175 break;
176 }
177 break;
178
179 default:
180 hr = E_INVALIDARG;
181 ExitOnRootFailure(hr, "Invalid package expected state.");
182 }
183 }
184
185 // return values
186 pPackage->execute = execute;
187 pPackage->rollback = rollback;
188
189 if (pfBARequestedCache)
190 {
191 *pfBARequestedCache = fBARequestedCache;
192 }
193
194LExit:
195 return hr;
196}
197
198//
199// PlanAdd - adds the calculated execute and rollback actions for the package.
200//
201extern "C" HRESULT MsuEnginePlanAddPackage(
202 __in BURN_PACKAGE* pPackage,
203 __in BURN_PLAN* pPlan,
204 __in BURN_LOGGING* pLog,
205 __in BURN_VARIABLES* pVariables,
206 __in HANDLE hCacheEvent,
207 __in BOOL fPlanPackageCacheRollback
208 )
209{
210 HRESULT hr = S_OK;
211 BURN_EXECUTE_ACTION* pAction = NULL;
212
213 // add wait for cache
214 if (hCacheEvent)
215 {
216 hr = PlanExecuteCacheSyncAndRollback(pPlan, pPackage, hCacheEvent, fPlanPackageCacheRollback);
217 ExitOnFailure(hr, "Failed to plan package cache syncpoint");
218 }
219
220 hr = DependencyPlanPackage(NULL, pPackage, pPlan);
221 ExitOnFailure(hr, "Failed to plan package dependency actions.");
222
223 // add execute action
224 if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->execute)
225 {
226 hr = PlanAppendExecuteAction(pPlan, &pAction);
227 ExitOnFailure(hr, "Failed to append execute action.");
228
229 pAction->type = BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE;
230 pAction->msuPackage.pPackage = pPackage;
231 pAction->msuPackage.action = pPackage->execute;
232
233 LoggingSetPackageVariable(pPackage, NULL, FALSE, pLog, pVariables, &pAction->msuPackage.sczLogPath); // ignore errors.
234 }
235
236 // add rollback action
237 if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->rollback)
238 {
239 hr = PlanAppendRollbackAction(pPlan, &pAction);
240 ExitOnFailure(hr, "Failed to append rollback action.");
241
242 pAction->type = BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE;
243 pAction->msuPackage.pPackage = pPackage;
244 pAction->msuPackage.action = pPackage->rollback;
245
246 LoggingSetPackageVariable(pPackage, NULL, TRUE, pLog, pVariables, &pAction->msuPackage.sczLogPath); // ignore errors.
247 }
248
249LExit:
250 return hr;
251}
252
253extern "C" HRESULT MsuEngineExecutePackage(
254 __in BURN_EXECUTE_ACTION* pExecuteAction,
255 __in BURN_VARIABLES* pVariables,
256 __in BOOL fRollback,
257 __in BOOL fStopWusaService,
258 __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler,
259 __in LPVOID pvContext,
260 __out BOOTSTRAPPER_APPLY_RESTART* pRestart
261 )
262{
263 HRESULT hr = S_OK;
264 int nResult = IDNOACTION;
265 LPWSTR sczCachedDirectory = NULL;
266 LPWSTR sczMsuPath = NULL;
267 LPWSTR sczWindowsPath = NULL;
268 LPWSTR sczSystemPath = NULL;
269 LPWSTR sczWusaPath = NULL;
270 LPWSTR sczCommand = NULL;
271 SC_HANDLE schWu = NULL;
272 BOOL fWuWasDisabled = FALSE;
273 STARTUPINFOW si = { };
274 PROCESS_INFORMATION pi = { };
275 GENERIC_EXECUTE_MESSAGE message = { };
276 DWORD dwExitCode = 0;
277 BOOL fUseSysNativePath = FALSE;
278
279#if !defined(_WIN64)
280 hr = ProcWow64(::GetCurrentProcess(), &fUseSysNativePath);
281 ExitOnFailure(hr, "Failed to determine WOW64 status.");
282#endif
283
284 *pRestart = BOOTSTRAPPER_APPLY_RESTART_NONE;
285
286 // get wusa.exe path
287 if (fUseSysNativePath)
288 {
289 hr = PathGetKnownFolder(CSIDL_WINDOWS, &sczWindowsPath);
290 ExitOnFailure(hr, "Failed to find Windows directory.");
291
292 hr = PathConcat(sczWindowsPath, L"SysNative\\", &sczSystemPath);
293 ExitOnFailure(hr, "Failed to append SysNative directory.");
294 }
295 else
296 {
297 hr = PathGetKnownFolder(CSIDL_SYSTEM, &sczSystemPath);
298 ExitOnFailure(hr, "Failed to find System32 directory.");
299 }
300
301 hr = PathConcat(sczSystemPath, L"wusa.exe", &sczWusaPath);
302 ExitOnFailure(hr, "Failed to allocate WUSA.exe path.");
303
304 // build command
305 switch (pExecuteAction->msuPackage.action)
306 {
307 case BOOTSTRAPPER_ACTION_STATE_INSTALL:
308 // get cached MSU path
309 hr = CacheGetCompletedPath(TRUE, pExecuteAction->msuPackage.pPackage->sczCacheId, &sczCachedDirectory);
310 ExitOnFailure(hr, "Failed to get cached path for package: %ls", pExecuteAction->msuPackage.pPackage->sczId);
311
312 // Best effort to set the execute package cache folder variable.
313 VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, sczCachedDirectory, TRUE);
314
315 hr = PathConcat(sczCachedDirectory, pExecuteAction->msuPackage.pPackage->rgPayloads[0].pPayload->sczFilePath, &sczMsuPath);
316 ExitOnFailure(hr, "Failed to build MSU path.");
317
318 // format command
319 hr = StrAllocFormatted(&sczCommand, L"\"%ls\" \"%ls\" /quiet /norestart", sczWusaPath, sczMsuPath);
320 ExitOnFailure(hr, "Failed to format MSU install command.");
321 break;
322
323 case BOOTSTRAPPER_ACTION_STATE_UNINSTALL:
324 // format command
325 hr = StrAllocFormatted(&sczCommand, L"\"%ls\" /uninstall /kb:%ls /quiet /norestart", sczWusaPath, pExecuteAction->msuPackage.pPackage->Msu.sczKB);
326 ExitOnFailure(hr, "Failed to format MSU uninstall command.");
327 break;
328
329 default:
330 hr = E_UNEXPECTED;
331 ExitOnFailure(hr, "Failed to get action arguments for MSU package.");
332 }
333
334 if (pExecuteAction->msuPackage.sczLogPath && *pExecuteAction->msuPackage.sczLogPath)
335 {
336 hr = StrAllocConcat(&sczCommand, L" /log:", 0);
337 ExitOnFailure(hr, "Failed to append log switch to MSU command-line.");
338
339 hr = StrAllocConcat(&sczCommand, pExecuteAction->msuPackage.sczLogPath, 0);
340 ExitOnFailure(hr, "Failed to append log path to MSU command-line.");
341 }
342
343 LogId(REPORT_STANDARD, MSG_APPLYING_PACKAGE, LoggingRollbackOrExecute(fRollback), pExecuteAction->msuPackage.pPackage->sczId, LoggingActionStateToString(pExecuteAction->msuPackage.action), sczMsuPath ? sczMsuPath : pExecuteAction->msuPackage.pPackage->Msu.sczKB, sczCommand);
344
345 hr = EnsureWUServiceEnabled(fStopWusaService, &schWu, &fWuWasDisabled);
346 ExitOnFailure(hr, "Failed to ensure WU service was enabled to install MSU package.");
347
348 // create process
349 si.cb = sizeof(si);
350 if (!::CreateProcessW(sczWusaPath, sczCommand, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
351 {
352 ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", sczWusaPath);
353 }
354
355 do
356 {
357 message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS;
358 message.dwAllowedResults = MB_OKCANCEL;
359 message.progress.dwPercentage = 50;
360 nResult = pfnGenericMessageHandler(&message, pvContext);
361 hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE);
362 ExitOnRootFailure(hr, "Bootstrapper application aborted during MSU progress.");
363
364 // wait for process to terminate
365 hr = ProcWaitForCompletion(pi.hProcess, 500, &dwExitCode);
366 if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr)
367 {
368 ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", sczWusaPath);
369 }
370 } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr);
371
372 // get process exit code
373 if (!::GetExitCodeProcess(pi.hProcess, &dwExitCode))
374 {
375 ExitWithLastError(hr, "Failed to get process exit code.");
376 }
377
378 // We'll normalize the restart required error code from wusa.exe just in case. Most likely
379 // that on reboot we'll actually get WU_S_REBOOT_REQUIRED.
380 if (HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED) == static_cast<HRESULT>(dwExitCode))
381 {
382 dwExitCode = ERROR_SUCCESS_REBOOT_REQUIRED;
383 }
384
385 // handle exit code
386 switch (dwExitCode)
387 {
388 case S_OK: __fallthrough;
389 case S_FALSE: __fallthrough;
390 case WU_S_ALREADY_INSTALLED:
391 hr = S_OK;
392 break;
393
394 case ERROR_SUCCESS_REBOOT_REQUIRED: __fallthrough;
395 case WU_S_REBOOT_REQUIRED:
396 *pRestart = BOOTSTRAPPER_APPLY_RESTART_REQUIRED;
397 hr = S_OK;
398 break;
399
400 default:
401 hr = static_cast<HRESULT>(dwExitCode);
402 break;
403 }
404
405LExit:
406 ReleaseStr(sczCachedDirectory);
407 ReleaseStr(sczMsuPath);
408 ReleaseStr(sczSystemPath);
409 ReleaseStr(sczWindowsPath);
410 ReleaseStr(sczWusaPath);
411 ReleaseStr(sczCommand);
412
413 ReleaseHandle(pi.hProcess);
414 ReleaseHandle(pi.hThread);
415
416 if (fWuWasDisabled)
417 {
418 SetServiceStartType(schWu, SERVICE_DISABLED);
419 }
420
421 // Best effort to clear the execute package cache folder variable.
422 VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, NULL, TRUE);
423
424 return hr;
425}
426
427static HRESULT EnsureWUServiceEnabled(
428 __in BOOL fStopWusaService,
429 __out SC_HANDLE* pschWu,
430 __out BOOL* pfPreviouslyDisabled
431 )
432{
433 HRESULT hr = S_OK;
434 SC_HANDLE schSCM = NULL;
435 SC_HANDLE schWu = NULL;
436 SERVICE_STATUS serviceStatus = { };
437 QUERY_SERVICE_CONFIGW* pConfig = NULL;
438
439 schSCM = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
440 ExitOnNullWithLastError(schSCM, hr, "Failed to open service control manager.");
441
442 schWu = ::OpenServiceW(schSCM, L"wuauserv", SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_STOP );
443 ExitOnNullWithLastError(schWu, hr, "Failed to open WU service.");
444
445 if (!::QueryServiceStatus(schWu, &serviceStatus) )
446 {
447 ExitWithLastError(hr, "Failed to query status of WU service.");
448 }
449
450 // Stop service if requested to.
451 if (SERVICE_STOPPED != serviceStatus.dwCurrentState && fStopWusaService)
452 {
453 hr = StopWUService(schWu);
454 }
455
456 // If the service is not running then it might be disabled so let's check.
457 if (SERVICE_RUNNING != serviceStatus.dwCurrentState)
458 {
459 hr = SvcQueryConfig(schWu, &pConfig);
460 ExitOnFailure(hr, "Failed to read configuration for WU service.");
461
462 // If WU is disabled, change it to a demand start service (but touch nothing else).
463 if (SERVICE_DISABLED == pConfig->dwStartType)
464 {
465 hr = SetServiceStartType(schWu, SERVICE_DEMAND_START);
466 ExitOnFailure(hr, "Failed to mark WU service to start on demand.");
467
468 *pfPreviouslyDisabled = TRUE;
469 }
470 }
471
472 *pschWu = schWu;
473 schWu = NULL;
474
475LExit:
476 ReleaseMem(pConfig);
477 ReleaseServiceHandle(schWu);
478 ReleaseServiceHandle(schSCM);
479
480 return hr;
481}
482
483static HRESULT SetServiceStartType(
484 __in SC_HANDLE sch,
485 __in DWORD startType
486 )
487{
488 HRESULT hr = S_OK;
489
490 if (!::ChangeServiceConfigW(sch, SERVICE_NO_CHANGE, startType, SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
491 {
492 ExitWithLastError(hr, "Failed to set service start type.");
493 }
494
495LExit:
496 return hr;
497}
498
499static HRESULT StopWUService(
500 __in SC_HANDLE schWu
501 )
502{
503 HRESULT hr = S_OK;
504 SERVICE_STATUS serviceStatus = { };
505
506 if(!::ControlService(schWu, SERVICE_CONTROL_STOP, &serviceStatus))
507 {
508 ExitWithLastError(hr, "Failed to stop wusa service.");
509 }
510
511LExit:
512 return hr;
513}