diff options
Diffstat (limited to 'src/ext/Util/ca/serviceconfig.cpp')
-rw-r--r-- | src/ext/Util/ca/serviceconfig.cpp | 821 |
1 files changed, 821 insertions, 0 deletions
diff --git a/src/ext/Util/ca/serviceconfig.cpp b/src/ext/Util/ca/serviceconfig.cpp new file mode 100644 index 00000000..04b25ffa --- /dev/null +++ b/src/ext/Util/ca/serviceconfig.cpp | |||
@@ -0,0 +1,821 @@ | |||
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 | // structs | ||
6 | LPCWSTR wzQUERY_SERVICECONFIG = L"SELECT `ServiceName`, `Component_`, `NewService`, `FirstFailureActionType`, `SecondFailureActionType`, `ThirdFailureActionType`, `ResetPeriodInDays`, `RestartServiceDelayInSeconds`, `ProgramCommandLine`, `RebootMessage` FROM `Wix4ServiceConfig`"; | ||
7 | enum eQUERY_SERVICECONFIG { QSC_SERVICENAME = 1, QSC_COMPONENT, QSC_NEWSERVICE, QSC_FIRSTFAILUREACTIONTYPE, QSC_SECONDFAILUREACTIONTYPE, QSC_THIRDFAILUREACTIONTYPE, QSC_RESETPERIODINDAYS, QSC_RESTARTSERVICEDELAYINSECONDS, QSC_PROGRAMCOMMANDLINE, QSC_REBOOTMESSAGE }; | ||
8 | |||
9 | // consts | ||
10 | LPCWSTR c_wzActionTypeNone = L"none"; | ||
11 | LPCWSTR c_wzActionTypeReboot = L"reboot"; | ||
12 | LPCWSTR c_wzActionTypeRestart = L"restart"; | ||
13 | LPCWSTR c_wzActionTypeRunCommand = L"runCommand"; | ||
14 | |||
15 | // prototypes | ||
16 | static SC_ACTION_TYPE GetSCActionType( | ||
17 | __in LPCWSTR pwzActionTypeName | ||
18 | ); | ||
19 | |||
20 | static HRESULT GetSCActionTypeString( | ||
21 | __in SC_ACTION_TYPE type, | ||
22 | __out_ecount(cchActionTypeString) LPWSTR wzActionTypeString, | ||
23 | __in DWORD cchActionTypeString | ||
24 | ); | ||
25 | |||
26 | static HRESULT GetService( | ||
27 | __in SC_HANDLE hSCM, | ||
28 | __in LPCWSTR wzService, | ||
29 | __in DWORD dwOpenServiceAccess, | ||
30 | __out SC_HANDLE* phService | ||
31 | ); | ||
32 | |||
33 | static HRESULT ConfigureService( | ||
34 | __in SC_HANDLE hSCM, | ||
35 | __in SC_HANDLE hService, | ||
36 | __in LPCWSTR wzServiceName, | ||
37 | __in DWORD dwRestartServiceDelayInSeconds, | ||
38 | __in LPCWSTR wzFirstFailureActionType, | ||
39 | __in LPCWSTR wzSecondFailureActionType, | ||
40 | __in LPCWSTR wzThirdFailureActionType, | ||
41 | __in DWORD dwResetPeriodInDays, | ||
42 | __in LPWSTR wzRebootMessage, | ||
43 | __in LPWSTR wzProgramCommandLine | ||
44 | ); | ||
45 | |||
46 | |||
47 | /****************************************************************** | ||
48 | SchedServiceConfig - entry point for SchedServiceConfig Custom Action | ||
49 | |||
50 | called as Type 1 CustomAction (binary DLL) from Windows Installer | ||
51 | in InstallExecuteSequence before CaExecServiceConfig | ||
52 | ********************************************************************/ | ||
53 | extern "C" UINT __stdcall SchedServiceConfig( | ||
54 | __in MSIHANDLE hInstall | ||
55 | ) | ||
56 | { | ||
57 | //AssertSz(FALSE, "debug SchedServiceConfig"); | ||
58 | HRESULT hr = S_OK; | ||
59 | UINT er = ERROR_SUCCESS; | ||
60 | |||
61 | LPWSTR pwzScriptKey = NULL; | ||
62 | LPWSTR pwzCustomActionData = NULL; | ||
63 | |||
64 | PMSIHANDLE hView = NULL; | ||
65 | PMSIHANDLE hRec = NULL; | ||
66 | LPWSTR pwzData = NULL; | ||
67 | int iData = 0; | ||
68 | DWORD cServices = 0; | ||
69 | |||
70 | // initialize | ||
71 | hr = WcaInitialize(hInstall, "SchedServiceConfig"); | ||
72 | ExitOnFailure(hr, "Failed to initialize."); | ||
73 | |||
74 | // Get the script key for this CustomAction and put it on the front of the | ||
75 | // CustomActionData of the install action. | ||
76 | hr = WcaCaScriptCreateKey(&pwzScriptKey); | ||
77 | ExitOnFailure(hr, "Failed to get encoding key."); | ||
78 | |||
79 | hr = WcaWriteStringToCaData(pwzScriptKey, &pwzCustomActionData); | ||
80 | ExitOnFailure(hr, "Failed to add encoding key to CustomActionData."); | ||
81 | |||
82 | // Loop through all the services to be configured. | ||
83 | hr = WcaOpenExecuteView(wzQUERY_SERVICECONFIG, &hView); | ||
84 | ExitOnFailure(hr, "Failed to open view on Wix4ServiceConfig table."); | ||
85 | |||
86 | while (S_OK == (hr = WcaFetchRecord(hView, &hRec))) | ||
87 | { | ||
88 | INSTALLSTATE isInstalled = INSTALLSTATE_UNKNOWN; | ||
89 | INSTALLSTATE isAction = INSTALLSTATE_UNKNOWN; | ||
90 | |||
91 | // Get component name to check if we are installing it. If so | ||
92 | // then add the table data to the CustomActionData, otherwise | ||
93 | // skip it. | ||
94 | hr = WcaGetRecordString(hRec, QSC_COMPONENT, &pwzData); | ||
95 | ExitOnFailure(hr, "Failed to get component name"); | ||
96 | |||
97 | hr = ::MsiGetComponentStateW(hInstall, pwzData, &isInstalled, &isAction); | ||
98 | ExitOnFailure(hr = HRESULT_FROM_WIN32(hr), "Failed to get install state for Component: %ls", pwzData); | ||
99 | |||
100 | if (WcaIsInstalling(isInstalled, isAction)) | ||
101 | { | ||
102 | // Add the data to the CustomActionData (for install). | ||
103 | hr = WcaGetRecordFormattedString(hRec, QSC_SERVICENAME, &pwzData); | ||
104 | ExitOnFailure(hr, "Failed to get name of service."); | ||
105 | hr = WcaWriteStringToCaData(pwzData, &pwzCustomActionData); | ||
106 | ExitOnFailure(hr, "Failed to add name to CustomActionData."); | ||
107 | |||
108 | hr = WcaGetRecordInteger(hRec, QSC_NEWSERVICE, &iData); | ||
109 | ExitOnFailure(hr, "Failed to get Wix4ServiceConfig.NewService."); | ||
110 | hr = WcaWriteIntegerToCaData(0 != iData, &pwzCustomActionData); | ||
111 | ExitOnFailure(hr, "Failed to add NewService data to CustomActionData"); | ||
112 | |||
113 | hr = WcaGetRecordString(hRec, QSC_FIRSTFAILUREACTIONTYPE, &pwzData); | ||
114 | ExitOnFailure(hr, "failed to get first failure action type"); | ||
115 | hr = WcaWriteStringToCaData(pwzData, &pwzCustomActionData); | ||
116 | ExitOnFailure(hr, "failed to add data to CustomActionData"); | ||
117 | |||
118 | hr = WcaGetRecordString(hRec, QSC_SECONDFAILUREACTIONTYPE, &pwzData); | ||
119 | ExitOnFailure(hr, "failed to get second failure action type"); | ||
120 | hr = WcaWriteStringToCaData(pwzData, &pwzCustomActionData); | ||
121 | ExitOnFailure(hr, "failed to add data to CustomActionData"); | ||
122 | |||
123 | hr = WcaGetRecordString(hRec, QSC_THIRDFAILUREACTIONTYPE, &pwzData); | ||
124 | ExitOnFailure(hr, "failed to get third failure action type"); | ||
125 | hr = WcaWriteStringToCaData(pwzData, &pwzCustomActionData); | ||
126 | ExitOnFailure(hr, "failed to add data to CustomActionData"); | ||
127 | |||
128 | hr = WcaGetRecordInteger(hRec, QSC_RESETPERIODINDAYS, &iData); | ||
129 | if (S_FALSE == hr) // deal w/ possible null value | ||
130 | { | ||
131 | iData = 0; | ||
132 | } | ||
133 | ExitOnFailure(hr, "failed to get reset period in days between service restart attempts."); | ||
134 | hr = WcaWriteIntegerToCaData(iData, &pwzCustomActionData); | ||
135 | ExitOnFailure(hr, "failed to add data to CustomActionData"); | ||
136 | |||
137 | hr = WcaGetRecordInteger(hRec, QSC_RESTARTSERVICEDELAYINSECONDS, &iData); | ||
138 | if (S_FALSE == hr) // deal w/ possible null value | ||
139 | { | ||
140 | iData = 0; | ||
141 | } | ||
142 | ExitOnFailure(hr, "failed to get server restart delay value."); | ||
143 | hr = WcaWriteIntegerToCaData(iData, &pwzCustomActionData); | ||
144 | ExitOnFailure(hr, "failed to add data to CustomActionData"); | ||
145 | |||
146 | hr = WcaGetRecordFormattedString(hRec, QSC_PROGRAMCOMMANDLINE, &pwzData); // null value already dealt w/ properly | ||
147 | ExitOnFailure(hr, "failed to get command line to run on service failure."); | ||
148 | hr = WcaWriteStringToCaData(pwzData, &pwzCustomActionData); | ||
149 | ExitOnFailure(hr, "failed to add data to CustomActionData"); | ||
150 | |||
151 | hr = WcaGetRecordString(hRec, QSC_REBOOTMESSAGE, &pwzData); // null value already dealt w/ properly | ||
152 | ExitOnFailure(hr, "failed to get message to send to users when server reboots due to service failure."); | ||
153 | hr = WcaWriteStringToCaData(pwzData, &pwzCustomActionData); | ||
154 | ExitOnFailure(hr, "failed to add data to CustomActionData"); | ||
155 | |||
156 | ++cServices; | ||
157 | } | ||
158 | } | ||
159 | |||
160 | // if we looped through all records all is well | ||
161 | if (E_NOMOREITEMS == hr) | ||
162 | { | ||
163 | hr = S_OK; | ||
164 | } | ||
165 | ExitOnFailure(hr, "failed while looping through all objects to secure"); | ||
166 | |||
167 | // setup CustomActionData and add to progress bar for download | ||
168 | if (0 < cServices) | ||
169 | { | ||
170 | hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RollbackServiceConfig"), pwzScriptKey, cServices * COST_SERVICECONFIG); | ||
171 | ExitOnFailure(hr, "failed to schedule RollbackServiceConfig action"); | ||
172 | |||
173 | hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"ExecServiceConfig"), pwzCustomActionData, cServices * COST_SERVICECONFIG); | ||
174 | ExitOnFailure(hr, "failed to schedule ExecServiceConfig action"); | ||
175 | } | ||
176 | |||
177 | LExit: | ||
178 | ReleaseStr(pwzData); | ||
179 | ReleaseStr(pwzCustomActionData); | ||
180 | ReleaseStr(pwzScriptKey); | ||
181 | |||
182 | er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; | ||
183 | return WcaFinalize(er); | ||
184 | } | ||
185 | |||
186 | |||
187 | /****************************************************************** | ||
188 | CaExecServiceConfig - entry point for ServiceConfig Custom Action. | ||
189 | |||
190 | NOTE: deferred CustomAction since it modifies the machine | ||
191 | NOTE: CustomActionData == wzServiceName\tfNewService\twzFirstFailureActionType\twzSecondFailureActionType\twzThirdFailureActionType\tdwResetPeriodInDays\tdwRestartServiceDelayInSeconds\twzProgramCommandLine\twzRebootMessage\twzServiceName\tfNewService\t... | ||
192 | *******************************************************************/ | ||
193 | extern "C" UINT __stdcall ExecServiceConfig( | ||
194 | __in MSIHANDLE hInstall | ||
195 | ) | ||
196 | { | ||
197 | //AssertSz(FALSE, "debug ExecServiceConfig"); | ||
198 | HRESULT hr = S_OK; | ||
199 | DWORD er = 0; | ||
200 | |||
201 | LPWSTR pwzCustomActionData = NULL; | ||
202 | LPWSTR pwz = NULL; | ||
203 | |||
204 | LPWSTR pwzScriptKey = NULL; | ||
205 | WCA_CASCRIPT_HANDLE hRollbackScript = NULL; | ||
206 | |||
207 | LPWSTR pwzServiceName = NULL; | ||
208 | BOOL fNewService = FALSE; | ||
209 | LPWSTR pwzFirstFailureActionType = NULL; | ||
210 | LPWSTR pwzSecondFailureActionType = NULL; | ||
211 | LPWSTR pwzThirdFailureActionType = NULL; | ||
212 | LPWSTR pwzProgramCommandLine = NULL; | ||
213 | LPWSTR pwzRebootMessage = NULL; | ||
214 | DWORD dwResetPeriodInDays = 0; | ||
215 | DWORD dwRestartServiceDelayInSeconds = 0; | ||
216 | |||
217 | LPVOID lpMsgBuf = NULL; | ||
218 | SC_HANDLE hSCM = NULL; | ||
219 | SC_HANDLE hService = NULL; | ||
220 | |||
221 | DWORD dwRestartDelay = 0; | ||
222 | WCHAR wzActionName[32] = { 0 }; | ||
223 | |||
224 | DWORD cbExistingServiceConfig = 0; | ||
225 | |||
226 | SERVICE_FAILURE_ACTIONSW* psfa = NULL; | ||
227 | |||
228 | // initialize | ||
229 | hr = WcaInitialize(hInstall, "ExecServiceConfig"); | ||
230 | ExitOnFailure(hr, "failed to initialize"); | ||
231 | |||
232 | // Open the Services Control Manager up front. | ||
233 | hSCM = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT); | ||
234 | if (NULL == hSCM) | ||
235 | { | ||
236 | er = ::GetLastError(); | ||
237 | hr = HRESULT_FROM_WIN32(er); | ||
238 | |||
239 | #pragma prefast(push) | ||
240 | #pragma prefast(disable:25028) | ||
241 | ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, er, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&lpMsgBuf, 0, NULL); | ||
242 | #pragma prefast(pop) | ||
243 | |||
244 | ExitOnFailure(hr, "Failed to get handle to SCM. Error: %ls", (LPWSTR)lpMsgBuf); | ||
245 | } | ||
246 | |||
247 | // First, get the script key out of the CustomActionData and | ||
248 | // use that to create the rollback script for this action. | ||
249 | hr = WcaGetProperty( L"CustomActionData", &pwzCustomActionData); | ||
250 | ExitOnFailure(hr, "failed to get CustomActionData"); | ||
251 | |||
252 | WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzCustomActionData); | ||
253 | |||
254 | pwz = pwzCustomActionData; | ||
255 | |||
256 | hr = WcaReadStringFromCaData(&pwz, &pwzScriptKey); | ||
257 | if (!pwzScriptKey) | ||
258 | { | ||
259 | hr = E_UNEXPECTED; | ||
260 | ExitOnFailure(hr, "Failed due to unexpected CustomActionData passed."); | ||
261 | } | ||
262 | ExitOnFailure(hr, "Failed to read encoding key from CustomActionData."); | ||
263 | |||
264 | hr = WcaCaScriptCreate(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, FALSE, &hRollbackScript); | ||
265 | ExitOnFailure(hr, "Failed to open rollback CustomAction script."); | ||
266 | |||
267 | // Next, loop through the rest of the CustomActionData, processing | ||
268 | // each service config row in turn. | ||
269 | while (pwz && *pwz) | ||
270 | { | ||
271 | hr = WcaReadStringFromCaData(&pwz, &pwzServiceName); | ||
272 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
273 | hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast<int*>(&fNewService)); | ||
274 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
275 | hr = WcaReadStringFromCaData(&pwz, &pwzFirstFailureActionType); | ||
276 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
277 | hr = WcaReadStringFromCaData(&pwz, &pwzSecondFailureActionType); | ||
278 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
279 | hr = WcaReadStringFromCaData(&pwz, &pwzThirdFailureActionType); | ||
280 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
281 | hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast<int*>(&dwResetPeriodInDays)); | ||
282 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
283 | hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast<int*>(&dwRestartServiceDelayInSeconds)); | ||
284 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
285 | hr = WcaReadStringFromCaData(&pwz, &pwzProgramCommandLine); | ||
286 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
287 | hr = WcaReadStringFromCaData(&pwz, &pwzRebootMessage); | ||
288 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
289 | |||
290 | WcaLog(LOGMSG_VERBOSE, "Configuring Service: %ls", pwzServiceName); | ||
291 | |||
292 | // Open the handle with all the permissions we might need: | ||
293 | // SERVICE_QUERY_CONFIG is needed for QueryServiceConfig2(). | ||
294 | // SERVICE_CHANGE_CONFIG is needed for ChangeServiceConfig2(). | ||
295 | // SERVICE_START is required in order to handle SC_ACTION_RESTART action. | ||
296 | hr = GetService(hSCM, pwzServiceName, SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_START, &hService); | ||
297 | ExitOnFailure(hr, "Failed to get service: %ls", pwzServiceName); | ||
298 | |||
299 | // If we are configuring a service that existed on the machine, we need to | ||
300 | // read the existing service configuration and write it out to the rollback | ||
301 | // log so rollback can put it back if anything goes wrong. | ||
302 | if (!fNewService) | ||
303 | { | ||
304 | // First, read the existing service config. | ||
305 | if (!::QueryServiceConfig2W(hService, SERVICE_CONFIG_FAILURE_ACTIONS, NULL, 0, &cbExistingServiceConfig) && ERROR_INSUFFICIENT_BUFFER != ::GetLastError()) | ||
306 | { | ||
307 | ExitWithLastError(hr, "Failed to get current service config info."); | ||
308 | } | ||
309 | |||
310 | psfa = static_cast<LPSERVICE_FAILURE_ACTIONSW>(MemAlloc(cbExistingServiceConfig, TRUE)); | ||
311 | ExitOnNull(psfa, hr, E_OUTOFMEMORY, "failed to allocate memory for service failure actions."); | ||
312 | |||
313 | if (!::QueryServiceConfig2W(hService, SERVICE_CONFIG_FAILURE_ACTIONS, (LPBYTE)psfa, cbExistingServiceConfig, &cbExistingServiceConfig)) | ||
314 | { | ||
315 | ExitOnLastError(hr, "failed to Query Service."); | ||
316 | } | ||
317 | |||
318 | // Build up rollback log so we can restore service state if necessary | ||
319 | hr = WcaCaScriptWriteString(hRollbackScript, pwzServiceName); | ||
320 | ExitOnFailure(hr, "Failed to add service name to Rollback Log"); | ||
321 | |||
322 | // If this service struct is empty, fill in default values | ||
323 | if (3 > psfa->cActions) | ||
324 | { | ||
325 | hr = WcaCaScriptWriteString(hRollbackScript, c_wzActionTypeNone); | ||
326 | ExitOnFailure(hr, "failed to add data to Rollback CustomActionData"); | ||
327 | |||
328 | hr = WcaCaScriptWriteString(hRollbackScript, c_wzActionTypeNone); | ||
329 | ExitOnFailure(hr, "failed to add data to Rollback CustomActionData"); | ||
330 | |||
331 | hr = WcaCaScriptWriteString(hRollbackScript, c_wzActionTypeNone); | ||
332 | ExitOnFailure(hr, "failed to add data to Rollback CustomActionData"); | ||
333 | } | ||
334 | else | ||
335 | { | ||
336 | // psfa actually had actions defined, so use the first three. | ||
337 | for (int i = 0; i < 3; ++i) | ||
338 | { | ||
339 | hr = GetSCActionTypeString(psfa->lpsaActions[i].Type, wzActionName, countof(wzActionName)); | ||
340 | ExitOnFailure(hr, "failed to query SFA object"); | ||
341 | |||
342 | if (SC_ACTION_RESTART == psfa->lpsaActions[i].Type) | ||
343 | { | ||
344 | dwRestartDelay = psfa->lpsaActions[i].Delay / 1000; | ||
345 | } | ||
346 | |||
347 | hr = WcaCaScriptWriteString(hRollbackScript, wzActionName); | ||
348 | ExitOnFailure(hr, "failed to add data to Rollback CustomActionData"); | ||
349 | } | ||
350 | } | ||
351 | |||
352 | hr = WcaCaScriptWriteNumber(hRollbackScript, psfa->dwResetPeriod / (24 * 60 * 60)); | ||
353 | ExitOnFailure(hr, "failed to add data to CustomActionData"); | ||
354 | |||
355 | hr = WcaCaScriptWriteNumber(hRollbackScript, dwRestartDelay); | ||
356 | ExitOnFailure(hr, "failed to add data to CustomActionData"); | ||
357 | |||
358 | // Handle the null cases. | ||
359 | if (!psfa->lpCommand) | ||
360 | { | ||
361 | psfa->lpCommand = L""; | ||
362 | } | ||
363 | hr = WcaCaScriptWriteString(hRollbackScript, psfa->lpCommand); | ||
364 | ExitOnFailure(hr, "failed to add data to Rollback CustomActionData"); | ||
365 | |||
366 | // Handle the null cases. | ||
367 | if (!psfa->lpRebootMsg) | ||
368 | { | ||
369 | psfa->lpRebootMsg = L""; | ||
370 | } | ||
371 | hr = WcaCaScriptWriteString(hRollbackScript, psfa->lpRebootMsg); | ||
372 | ExitOnFailure(hr, "failed to add data to Rollback CustomActionData"); | ||
373 | |||
374 | // Nudge the system to get all our rollback data written to disk. | ||
375 | WcaCaScriptFlush(hRollbackScript); | ||
376 | |||
377 | ReleaseNullMem(psfa); | ||
378 | } | ||
379 | |||
380 | hr = ConfigureService(hSCM, hService, pwzServiceName, dwRestartServiceDelayInSeconds, pwzFirstFailureActionType, | ||
381 | pwzSecondFailureActionType, pwzThirdFailureActionType, dwResetPeriodInDays, pwzRebootMessage, pwzProgramCommandLine); | ||
382 | ExitOnFailure(hr, "Failed to configure service: %ls", pwzServiceName); | ||
383 | |||
384 | hr = WcaProgressMessage(COST_SERVICECONFIG, FALSE); | ||
385 | ExitOnFailure(hr, "failed to send progress message"); | ||
386 | |||
387 | // Per-service cleanup | ||
388 | ::CloseServiceHandle(hService); | ||
389 | hService = NULL; | ||
390 | dwResetPeriodInDays = 0; | ||
391 | dwRestartServiceDelayInSeconds = 0; | ||
392 | } | ||
393 | |||
394 | LExit: | ||
395 | WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE); | ||
396 | |||
397 | if (lpMsgBuf) | ||
398 | { | ||
399 | ::LocalFree(lpMsgBuf); | ||
400 | } | ||
401 | |||
402 | if (hService) | ||
403 | { | ||
404 | ::CloseServiceHandle(hService); | ||
405 | } | ||
406 | |||
407 | if (hSCM) | ||
408 | { | ||
409 | ::CloseServiceHandle(hSCM); | ||
410 | } | ||
411 | |||
412 | ReleaseMem(psfa); | ||
413 | |||
414 | ReleaseStr(pwzRebootMessage); | ||
415 | ReleaseStr(pwzProgramCommandLine); | ||
416 | ReleaseStr(pwzThirdFailureActionType); | ||
417 | ReleaseStr(pwzSecondFailureActionType); | ||
418 | ReleaseStr(pwzFirstFailureActionType); | ||
419 | ReleaseStr(pwzServiceName); | ||
420 | ReleaseStr(pwzScriptKey); | ||
421 | ReleaseStr(pwzCustomActionData); | ||
422 | |||
423 | er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; | ||
424 | return WcaFinalize(er); | ||
425 | } | ||
426 | |||
427 | |||
428 | /****************************************************************** | ||
429 | RollbackServiceConfig - entry point for ServiceConfig rollback | ||
430 | Custom Action. | ||
431 | |||
432 | NOTE: CustomActionScript Data == wzServiceName\twzFirstFailureActionType\twzSecondFailureActionType\twzThirdFailureActionType\tdwResetPeriodInDays\tdwRestartServiceDelayInSeconds\twzProgramCommandLine\twzRebootMessage\twzServiceName\t... | ||
433 | *******************************************************************/ | ||
434 | extern "C" UINT __stdcall RollbackServiceConfig( | ||
435 | __in MSIHANDLE hInstall | ||
436 | ) | ||
437 | { | ||
438 | //AssertSz(FALSE, "debug RollbackServiceConfig"); | ||
439 | HRESULT hr = S_OK; | ||
440 | DWORD er = 0; | ||
441 | |||
442 | LPWSTR pwzCustomActionData = NULL; | ||
443 | LPWSTR pwz = NULL; | ||
444 | |||
445 | LPWSTR pwzScriptKey = NULL; | ||
446 | WCA_CASCRIPT_HANDLE hRollbackScript = NULL; | ||
447 | |||
448 | LPWSTR pwzServiceName = NULL; | ||
449 | LPWSTR pwzFirstFailureActionType = NULL; | ||
450 | LPWSTR pwzSecondFailureActionType = NULL; | ||
451 | LPWSTR pwzThirdFailureActionType = NULL; | ||
452 | LPWSTR pwzProgramCommandLine = NULL; | ||
453 | LPWSTR pwzRebootMessage = NULL; | ||
454 | DWORD dwResetPeriodInDays = 0; | ||
455 | DWORD dwRestartServiceDelayInSeconds = 0; | ||
456 | |||
457 | LPVOID lpMsgBuf = NULL; | ||
458 | SC_HANDLE hSCM = NULL; | ||
459 | SC_HANDLE hService = NULL; | ||
460 | |||
461 | // initialize | ||
462 | hr = WcaInitialize(hInstall, "RollbackServiceConfig"); | ||
463 | ExitOnFailure(hr, "Failed to initialize 'RollbackServiceConfig'."); | ||
464 | |||
465 | // Open the Services Control Manager up front. | ||
466 | hSCM = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT); | ||
467 | if (NULL == hSCM) | ||
468 | { | ||
469 | er = ::GetLastError(); | ||
470 | hr = HRESULT_FROM_WIN32(er); | ||
471 | |||
472 | #pragma prefast(push) | ||
473 | #pragma prefast(disable:25028) | ||
474 | ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, er, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&lpMsgBuf, 0, NULL); | ||
475 | #pragma prefast(pop) | ||
476 | |||
477 | ExitOnFailure(hr, "Failed to get handle to SCM. Error: %ls", (LPWSTR)lpMsgBuf); | ||
478 | |||
479 | // Make sure we still abort, in case hSCM was NULL but no error was returned from GetLastError | ||
480 | ExitOnNull(hSCM, hr, E_POINTER, "Getting handle to SCM reported success, but no handle was returned."); | ||
481 | } | ||
482 | |||
483 | // Get the script key from the CustomAction data and use it to open | ||
484 | // the rollback log and read the data over the CustomActionData | ||
485 | // because all of the information is in the script data not the | ||
486 | // CustomActionData. | ||
487 | hr = WcaGetProperty( L"CustomActionData", &pwzCustomActionData); | ||
488 | ExitOnFailure(hr, "failed to get CustomActionData"); | ||
489 | |||
490 | WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzCustomActionData); | ||
491 | |||
492 | pwz = pwzCustomActionData; | ||
493 | |||
494 | hr = WcaReadStringFromCaData(&pwz, &pwzScriptKey); | ||
495 | if (!pwzScriptKey) | ||
496 | { | ||
497 | hr = E_UNEXPECTED; | ||
498 | ExitOnFailure(hr, "Failed due to unexpected CustomActionData passed."); | ||
499 | } | ||
500 | ExitOnFailure(hr, "Failed to read encoding key from CustomActionData."); | ||
501 | |||
502 | hr = WcaCaScriptOpen(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, &hRollbackScript); | ||
503 | ExitOnFailure(hr, "Failed to open rollback CustomAction script."); | ||
504 | |||
505 | hr = WcaCaScriptReadAsCustomActionData(hRollbackScript, &pwzCustomActionData); | ||
506 | ExitOnFailure(hr, "Failed to read rollback script into CustomAction data."); | ||
507 | |||
508 | // Loop through the script's CustomActionData, processing each | ||
509 | // service config in turn. | ||
510 | pwz = pwzCustomActionData; | ||
511 | while (pwz && *pwz) | ||
512 | { | ||
513 | hr = WcaReadStringFromCaData(&pwz, &pwzServiceName); | ||
514 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
515 | hr = WcaReadStringFromCaData(&pwz, &pwzFirstFailureActionType); | ||
516 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
517 | hr = WcaReadStringFromCaData(&pwz, &pwzSecondFailureActionType); | ||
518 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
519 | hr = WcaReadStringFromCaData(&pwz, &pwzThirdFailureActionType); | ||
520 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
521 | hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast<int*>(&dwResetPeriodInDays)); | ||
522 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
523 | hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast<int*>(&dwRestartServiceDelayInSeconds)); | ||
524 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
525 | hr = WcaReadStringFromCaData(&pwz, &pwzProgramCommandLine); | ||
526 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
527 | hr = WcaReadStringFromCaData(&pwz, &pwzRebootMessage); | ||
528 | ExitOnFailure(hr, "failed to process CustomActionData"); | ||
529 | |||
530 | WcaLog(LOGMSG_VERBOSE, "Reconfiguring Service: %ls", pwzServiceName); | ||
531 | |||
532 | // Open the handle with all the permissions we might need. | ||
533 | // SERVICE_CHANGE_CONFIG is needed for ChangeServiceConfig2(). | ||
534 | // SERVICE_START is required in order to handle SC_ACTION_RESTART action. | ||
535 | hr = GetService(hSCM, pwzServiceName, SERVICE_CHANGE_CONFIG | SERVICE_START, &hService); | ||
536 | ExitOnFailure(hr, "Failed to get service: %ls", pwzServiceName); | ||
537 | |||
538 | hr = ConfigureService(hSCM, hService, pwzServiceName, dwRestartServiceDelayInSeconds, pwzFirstFailureActionType, | ||
539 | pwzSecondFailureActionType, pwzThirdFailureActionType, dwResetPeriodInDays, pwzRebootMessage, pwzProgramCommandLine); | ||
540 | ExitOnFailure(hr, "Failed to configure service: %ls", pwzServiceName); | ||
541 | |||
542 | hr = WcaProgressMessage(COST_SERVICECONFIG, FALSE); | ||
543 | ExitOnFailure(hr, "failed to send progress message"); | ||
544 | |||
545 | // Per-service cleanup | ||
546 | ::CloseServiceHandle(hService); | ||
547 | hService = NULL; | ||
548 | dwResetPeriodInDays = 0; | ||
549 | dwRestartServiceDelayInSeconds = 0; | ||
550 | } | ||
551 | |||
552 | LExit: | ||
553 | if (lpMsgBuf) // Allocated with FormatString. | ||
554 | { | ||
555 | ::LocalFree(lpMsgBuf); | ||
556 | } | ||
557 | |||
558 | if (hService) | ||
559 | { | ||
560 | ::CloseServiceHandle(hService); | ||
561 | } | ||
562 | |||
563 | if (hSCM) | ||
564 | { | ||
565 | ::CloseServiceHandle(hSCM); | ||
566 | } | ||
567 | |||
568 | WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_DELETE); | ||
569 | |||
570 | ReleaseStr(pwzRebootMessage); | ||
571 | ReleaseStr(pwzProgramCommandLine); | ||
572 | ReleaseStr(pwzThirdFailureActionType); | ||
573 | ReleaseStr(pwzSecondFailureActionType); | ||
574 | ReleaseStr(pwzFirstFailureActionType); | ||
575 | ReleaseStr(pwzServiceName); | ||
576 | ReleaseStr(pwzScriptKey); | ||
577 | ReleaseStr(pwzCustomActionData); | ||
578 | |||
579 | er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; | ||
580 | return WcaFinalize(er); | ||
581 | } | ||
582 | |||
583 | |||
584 | /********************************************************** | ||
585 | GetSCActionType - helper function to return the SC_ACTION_TYPE | ||
586 | for a given string matching the allowed set. | ||
587 | REBOOT, RESTART, RUN_COMMAND and NONE | ||
588 | **********************************************************/ | ||
589 | static SC_ACTION_TYPE GetSCActionType( | ||
590 | __in LPCWSTR pwzActionTypeName | ||
591 | ) | ||
592 | { | ||
593 | SC_ACTION_TYPE actionType; | ||
594 | |||
595 | // verify that action types are valid. if not, just default to NONE | ||
596 | if (0 == lstrcmpiW(c_wzActionTypeReboot, pwzActionTypeName)) | ||
597 | { | ||
598 | actionType = SC_ACTION_REBOOT; | ||
599 | } | ||
600 | else if (0 == lstrcmpiW(c_wzActionTypeRestart, pwzActionTypeName)) | ||
601 | { | ||
602 | actionType = SC_ACTION_RESTART; | ||
603 | } | ||
604 | else if (0 == lstrcmpiW(c_wzActionTypeRunCommand, pwzActionTypeName)) | ||
605 | { | ||
606 | actionType = SC_ACTION_RUN_COMMAND; | ||
607 | } | ||
608 | else | ||
609 | { | ||
610 | // default to none | ||
611 | actionType = SC_ACTION_NONE; | ||
612 | } | ||
613 | |||
614 | return actionType; | ||
615 | } | ||
616 | |||
617 | |||
618 | static HRESULT GetSCActionTypeString( | ||
619 | __in SC_ACTION_TYPE type, | ||
620 | __out_ecount(cchActionTypeString) LPWSTR wzActionTypeString, | ||
621 | __in DWORD cchActionTypeString | ||
622 | ) | ||
623 | { | ||
624 | HRESULT hr = S_OK; | ||
625 | |||
626 | switch (type) | ||
627 | { | ||
628 | case SC_ACTION_REBOOT: | ||
629 | hr = StringCchCopyW(wzActionTypeString, cchActionTypeString, c_wzActionTypeReboot); | ||
630 | ExitOnFailure(hr, "Failed to copy 'reboot' into action type."); | ||
631 | break; | ||
632 | case SC_ACTION_RESTART: | ||
633 | hr = StringCchCopyW(wzActionTypeString, cchActionTypeString, c_wzActionTypeRestart); | ||
634 | ExitOnFailure(hr, "Failed to copy 'restart' into action type."); | ||
635 | break; | ||
636 | case SC_ACTION_RUN_COMMAND: | ||
637 | hr = StringCchCopyW(wzActionTypeString, cchActionTypeString, c_wzActionTypeRunCommand); | ||
638 | ExitOnFailure(hr, "Failed to copy 'runCommand' into action type."); | ||
639 | break; | ||
640 | case SC_ACTION_NONE: | ||
641 | hr = StringCchCopyW(wzActionTypeString, cchActionTypeString, c_wzActionTypeNone); | ||
642 | ExitOnFailure(hr, "Failed to copy 'none' into action type."); | ||
643 | break; | ||
644 | default: | ||
645 | break; | ||
646 | } | ||
647 | |||
648 | LExit: | ||
649 | return hr; | ||
650 | } | ||
651 | |||
652 | |||
653 | static HRESULT GetService( | ||
654 | __in SC_HANDLE hSCM, | ||
655 | __in LPCWSTR wzService, | ||
656 | __in DWORD dwOpenServiceAccess, | ||
657 | __out SC_HANDLE* phService | ||
658 | ) | ||
659 | { | ||
660 | HRESULT hr = S_OK; | ||
661 | DWORD er = ERROR_SUCCESS; | ||
662 | LPVOID lpMsgBuf = NULL; | ||
663 | |||
664 | *phService = ::OpenServiceW(hSCM, wzService, dwOpenServiceAccess); | ||
665 | if (NULL == *phService) | ||
666 | { | ||
667 | er = ::GetLastError(); | ||
668 | hr = HRESULT_FROM_WIN32(er); | ||
669 | if (ERROR_SERVICE_DOES_NOT_EXIST == er) | ||
670 | { | ||
671 | ExitOnFailure(hr, "Service '%ls' does not exist on this system.", wzService); | ||
672 | } | ||
673 | else | ||
674 | { | ||
675 | #pragma prefast(push) | ||
676 | #pragma prefast(disable:25028) | ||
677 | ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, er, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&lpMsgBuf, 0, NULL); | ||
678 | #pragma prefast(pop) | ||
679 | |||
680 | ExitOnFailure(hr, "Failed to get handle to the service '%ls'. Error: %ls", wzService, (LPWSTR)lpMsgBuf); | ||
681 | } | ||
682 | } | ||
683 | |||
684 | LExit: | ||
685 | if (lpMsgBuf) // Allocated with FormatString. | ||
686 | { | ||
687 | ::LocalFree(lpMsgBuf); | ||
688 | } | ||
689 | |||
690 | return hr; | ||
691 | } | ||
692 | |||
693 | |||
694 | static HRESULT ConfigureService( | ||
695 | __in SC_HANDLE /*hSCM*/, | ||
696 | __in SC_HANDLE hService, | ||
697 | __in LPCWSTR wzServiceName, | ||
698 | __in DWORD dwRestartServiceDelayInSeconds, | ||
699 | __in LPCWSTR wzFirstFailureActionType, | ||
700 | __in LPCWSTR wzSecondFailureActionType, | ||
701 | __in LPCWSTR wzThirdFailureActionType, | ||
702 | __in DWORD dwResetPeriodInDays, | ||
703 | __in LPWSTR wzRebootMessage, | ||
704 | __in LPWSTR wzProgramCommandLine | ||
705 | ) | ||
706 | { | ||
707 | HRESULT hr = S_OK; | ||
708 | DWORD er = ERROR_SUCCESS; | ||
709 | |||
710 | HANDLE hToken = NULL; | ||
711 | TOKEN_PRIVILEGES priv = { 0 }; | ||
712 | TOKEN_PRIVILEGES* pPrevPriv = NULL; | ||
713 | DWORD cbPrevPriv = 0; | ||
714 | BOOL fAdjustedPrivileges = FALSE; | ||
715 | |||
716 | SC_ACTION actions[3]; // the UI always shows 3 actions, so we'll always do 3 | ||
717 | SERVICE_FAILURE_ACTIONSW sfa; | ||
718 | LPVOID lpMsgBuf = NULL; | ||
719 | |||
720 | // Always get the shutdown privilege in case we need to configure service to reboot. | ||
721 | if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) | ||
722 | { | ||
723 | ExitWithLastError(hr, "Failed to get process token."); | ||
724 | } | ||
725 | |||
726 | priv.PrivilegeCount = 1; | ||
727 | priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | ||
728 | if (!::LookupPrivilegeValueW(NULL, L"SeShutdownPrivilege", &priv.Privileges[0].Luid)) | ||
729 | { | ||
730 | ExitWithLastError(hr, "Failed to get shutdown privilege LUID."); | ||
731 | } | ||
732 | |||
733 | cbPrevPriv = sizeof(TOKEN_PRIVILEGES); | ||
734 | pPrevPriv = static_cast<TOKEN_PRIVILEGES*>(MemAlloc(cbPrevPriv, TRUE)); | ||
735 | ExitOnNull(pPrevPriv, hr, E_OUTOFMEMORY, "Failed to allocate memory for empty previous privileges."); | ||
736 | |||
737 | if (!::AdjustTokenPrivileges(hToken, FALSE, &priv, cbPrevPriv, pPrevPriv, &cbPrevPriv)) | ||
738 | { | ||
739 | LPVOID pv = MemReAlloc(pPrevPriv, cbPrevPriv, TRUE); | ||
740 | ExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to allocate memory for previous privileges."); | ||
741 | pPrevPriv = static_cast<TOKEN_PRIVILEGES*>(pv); | ||
742 | |||
743 | if (!::AdjustTokenPrivileges(hToken, FALSE, &priv, cbPrevPriv, pPrevPriv, &cbPrevPriv)) | ||
744 | { | ||
745 | ExitWithLastError(hr, "Failed to get shutdown privilege LUID."); | ||
746 | } | ||
747 | } | ||
748 | |||
749 | fAdjustedPrivileges = TRUE; | ||
750 | |||
751 | // build up SC_ACTION array | ||
752 | // TODO: why is delay only respected when SC_ACTION_RESTART is requested? | ||
753 | actions[0].Type = GetSCActionType(wzFirstFailureActionType); | ||
754 | actions[0].Delay = 0; | ||
755 | if (SC_ACTION_RESTART == actions[0].Type) | ||
756 | { | ||
757 | actions[0].Delay = dwRestartServiceDelayInSeconds * 1000; // seconds to milliseconds | ||
758 | } | ||
759 | |||
760 | actions[1].Type = GetSCActionType(wzSecondFailureActionType); | ||
761 | actions[1].Delay = 0; | ||
762 | if (SC_ACTION_RESTART == actions[1].Type) | ||
763 | { | ||
764 | actions[1].Delay = dwRestartServiceDelayInSeconds * 1000; // seconds to milliseconds | ||
765 | } | ||
766 | |||
767 | actions[2].Type = GetSCActionType(wzThirdFailureActionType); | ||
768 | actions[2].Delay = 0; | ||
769 | if (SC_ACTION_RESTART == actions[2].Type) | ||
770 | { | ||
771 | actions[2].Delay = dwRestartServiceDelayInSeconds * 1000; // seconds to milliseconds | ||
772 | } | ||
773 | |||
774 | // build up the SERVICE_FAILURE_ACTIONSW struct | ||
775 | sfa.dwResetPeriod = dwResetPeriodInDays * (24 * 60 * 60); // days to seconds | ||
776 | sfa.lpRebootMsg = wzRebootMessage; | ||
777 | sfa.lpCommand = wzProgramCommandLine; | ||
778 | sfa.cActions = countof(actions); | ||
779 | sfa.lpsaActions = actions; | ||
780 | |||
781 | // Call ChangeServiceConfig2 to actually set up the failure actions | ||
782 | if (!::ChangeServiceConfig2W(hService, SERVICE_CONFIG_FAILURE_ACTIONS, (LPVOID)&sfa)) | ||
783 | { | ||
784 | er = ::GetLastError(); | ||
785 | hr = HRESULT_FROM_WIN32(er); | ||
786 | |||
787 | #pragma prefast(push) | ||
788 | #pragma prefast(disable:25028) | ||
789 | ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, er, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&lpMsgBuf, 0, NULL); | ||
790 | #pragma prefast(pop) | ||
791 | |||
792 | // Check if this is a service that can't be modified. | ||
793 | if (ERROR_CANNOT_DETECT_PROCESS_ABORT == er) | ||
794 | { | ||
795 | WcaLog(LOGMSG_STANDARD, "WARNING: Service \"%ls\" is not configurable on this server and will not be set.", wzServiceName); | ||
796 | } | ||
797 | ExitOnFailure(hr, "Cannot change service configuration. Error: %ls", (LPWSTR)lpMsgBuf); | ||
798 | |||
799 | if (lpMsgBuf) | ||
800 | { | ||
801 | ::LocalFree(lpMsgBuf); | ||
802 | lpMsgBuf = NULL; | ||
803 | } | ||
804 | } | ||
805 | |||
806 | LExit: | ||
807 | if (lpMsgBuf) | ||
808 | { | ||
809 | ::LocalFree(lpMsgBuf); | ||
810 | } | ||
811 | |||
812 | if (fAdjustedPrivileges) | ||
813 | { | ||
814 | ::AdjustTokenPrivileges(hToken, FALSE, pPrevPriv, 0, NULL, NULL); | ||
815 | } | ||
816 | |||
817 | ReleaseMem(pPrevPriv); | ||
818 | ReleaseHandle(hToken); | ||
819 | |||
820 | return hr; | ||
821 | } | ||