Left: | ||
Right: |
OLD | NEW |
---|---|
1 /** | 1 /** |
2 * \file close_application.cpp | 2 * \file close_application.cpp |
3 */ | 3 */ |
4 | 4 |
5 #include <algorithm> | |
6 | |
5 #include "session.h" | 7 #include "session.h" |
6 #include "property.h" | 8 #include "property.h" |
7 #include "database.h" | 9 #include "database.h" |
8 | 10 #include "process.h" |
9 | 11 #include "interaction.h" |
10 #include <TlHelp32.h> | 12 #include "custom-i18n.h" |
11 | 13 |
14 //------------------------------------------------------- | |
15 //------------------------------------------------------- | |
16 class IE_Closer | |
Oleksandr
2014/03/12 19:26:04
I am not a fan of a name IE_Closer here. Seeing th
Eric
2014/03/17 12:26:46
Well, a proper name would indicate that we're clos
| |
17 { | |
18 Snapshot snapshot ; | |
Oleksandr
2014/03/12 19:26:04
Cosmetic note here and further. We don't usually h
Eric
2014/03/17 12:26:46
I seem to always use more whitespace than most. I'
| |
19 | |
20 Process_Closer ie_closer ; | |
21 | |
22 Process_Closer engine_closer ; | |
23 | |
24 static const wchar_t * ie_names[] ; | |
25 static const wchar_t * engine_names[] ; | |
26 //Process_Closer iec( snapshot, IE_names, 2 ) ; | |
Oleksandr
2014/03/12 19:26:04
This should probably be deleted?
Eric
2014/03/17 12:26:46
Yep. That's the previous algorithm I was using to
| |
27 | |
28 public: | |
29 IE_Closer() | |
30 : snapshot(), ie_closer( snapshot, ie_names, 1 ), engine_closer( snapshot, e ngine_names, 1 ) | |
Oleksandr
2014/03/12 19:26:04
One more cosmetic comment - we don't usually have
| |
31 {} | |
32 | |
33 void refresh() | |
34 { | |
35 snapshot.refresh() ; | |
36 ie_closer.refresh() ; | |
37 engine_closer.refresh() ; | |
38 } | |
39 | |
40 bool is_running() | |
41 { | |
42 return ie_closer.is_running() || engine_closer.is_running() ; | |
43 } | |
44 | |
45 bool shut_down() | |
46 { | |
47 if ( ie_closer.is_running() && ! ie_closer.shut_down() ) | |
48 { | |
49 // Assert IE is still running | |
50 // This is after we've tried to shut it down, so we fail | |
51 return false ; | |
52 } | |
53 if ( engine_closer.is_running() && ! engine_closer.shut_down() ) | |
54 { | |
55 // Assert the engine is still running | |
56 // This is after IE has shut down itself and after we've tried to shut dow n the engine. Whatever. | |
57 return false ; | |
58 } | |
59 return true ; | |
60 } | |
61 } ; | |
62 | |
63 const wchar_t * IE_Closer::ie_names[] = { L"IExplore.exe" } ; | |
64 const wchar_t * IE_Closer::engine_names[] = { L"AdblockPlusEngine.exe" } ; | |
65 | |
66 //------------------------------------------------------- | |
67 // abp_close_ie | |
68 //------------------------------------------------------- | |
12 /** | 69 /** |
13 * Exposed DLL entry point for custom action. | 70 * Exposed DLL entry point for custom action. |
14 * The function signature matches the calling convention used by Windows Install er. | 71 * The function signature matches the calling convention used by Windows Install er. |
15 * | 72 * |
73 * This function supports four policy stances with respect to a running IE proce ss. | |
74 * | |
75 * - Allow reboot. | |
76 * IE is running, so what? I'm willing to reboot after installation. | |
77 * - Avoid reboot passively. | |
78 * I don't want to affect any other running processes, but I don't want to reb oot. I'll abort the installation. | |
79 * - Avoid reboot actively. | |
80 * I want to shut down IE in order to avoid a reboot. | |
81 * I'll do it manually when the time is right. | |
82 * - Avoid reboot automatically. | |
83 * I want to shut down IE automatically in order to avoid a reboot. | |
84 * | |
85 * In a silent installation, the default stance is "allow reboot", which is to s ay, to act like most installers. | |
86 * In an interactive installation, the stance is gathered from the user through dialog boxes. | |
87 * If the MSI property AVOIDREBOOT is set to one of the values NO, PASSIVE, ACTI VE, or AUTOMATIC, the policy is set accordingly. | |
88 * In a silent installation, this is the only way getting a stance other than th e default. | |
89 * In an interactive installation, AVOIDREBOOT skips the dialogs. | |
90 * | |
16 * \param[in] session_handle | 91 * \param[in] session_handle |
17 * Windows installer session handle | 92 * Windows installer session handle |
93 * | |
94 * \return | |
95 * An integer interpreted as a custom action return value. | |
96 * | |
97 * \post | |
98 * + The return value is one of the following: | |
99 * - ERROR_INSTALL_USEREXIT if the user cancelled installation. | |
100 * - ERROR_INSTALL_FAILURE if something unexpected happened, usually if the top level try-block caught an exception. | |
101 * - ERROR_SUCCESS otherwise. | |
102 * + The function performed at least one check that Internet Explorer was runn ing. | |
103 * + If ERROR_SUCCESS, the MSI property RUNNINGBROWSER is set and has one of t he following values: | |
104 * - 1 if Internet Explorer was running upon the last check. | |
105 * - 0 otherwise. | |
106 * \post | |
107 * Note that this function cannot provide any assurance that Internet Explorer stays either stays running or stays not running. | |
108 * | |
109 * \sa | |
110 * - MSDN [Custom Action Return Values](http://msdn.microsoft.com/en-us/librar y/aa368072%28v=vs.85%29.aspx) | |
18 */ | 111 */ |
19 extern "C" UINT __stdcall | 112 extern "C" UINT __stdcall |
20 abp_close_applications( MSIHANDLE session_handle ) | 113 abp_close_ie( MSIHANDLE session_handle ) |
21 { | 114 { |
22 // Always supply an externally-exposed function with a catch-all block | 115 // Utility typedef to shorten the class name. |
116 typedef Installer_Message_Box IMB ; | |
117 | |
118 /* | |
119 * Immediate_Session cannot throw, so it can go outside the try-block. | |
120 * It's needed in the catch-all block to write an error message to the log. | |
121 */ | |
122 Immediate_Session session( session_handle, "abp_close_ie" ) ; | |
123 | |
124 // The body of an entry point function must have a catch-all. | |
23 try { | 125 try { |
24 Immediate_Session session( session_handle, L"abp_close_applications" ) ; | 126 |
25 session.log( L"Have session object" ) ; | 127 // MSI property BROWSERRUNNING is one of the return values of this function. |
26 | 128 Property browser_running( session, L"BROWSERRUNNING" ) ; |
129 Property browser_closed( session, L"BROWSERCLOSED" ) ; | |
130 | |
131 // Instantiation of Process_Closer takes a snapshot. | |
132 IE_Closer iec ; | |
133 | |
134 /* | |
135 * We take the short path through this function if IE is not running at the outset. | |
Oleksandr
2014/03/12 19:26:04
IE >or Engine<, actually
Eric
2014/03/17 12:26:46
Done.
| |
136 */ | |
137 if ( ! iec.is_running() ) | |
138 { | |
139 browser_running = L"0" ;» // The browser is not running. | |
140 browser_closed = L"0" ;» // We didn't close the browser (and we could n't have). | |
141 session.log( "IE not running. No issue with reboot policy." ) ; | |
142 return ERROR_SUCCESS ; | |
143 } | |
144 | |
145 /* | |
146 * As a (potentially) user-driven function, a state machine manages control flow. | |
147 * The states are organized around the policy stances. | |
148 */ | |
149 enum Policy_State { | |
150 // Non-terminal states | |
151 not_known,» // We don't know the user's stance at all | |
152 part_known,» // The user has indicated either ACTIVE or AUTOMATIC | |
153 active,» » // Actively avoid reboot | |
154 automatic, // Automatically avoid reboot | |
155 // Terminal states | |
156 success, | |
157 abort, | |
158 // Aliases for what would ordinarily be non-terminal states. | |
159 // They're terminal because of implementation details. | |
160 allow = success,» // Allow reboot. | |
161 passive = abort,» // Passively avoid reboot, that is, don't try to close IE. | |
162 }; | |
163 Policy_State state = not_known; | |
Oleksandr
2014/03/12 19:26:04
Redundant initialization here
Eric
2014/03/17 12:26:46
Done.
| |
164 | |
165 /* | |
166 * Use the AVOIDREBOOT property, if present, to set an initial state. | |
167 */ | |
168 std::wstring avoid_reboot = Property( session, L"AVOIDREBOOT" ) ; | |
169 std::transform( avoid_reboot.begin(), avoid_reboot.end(), avoid_reboot.begin (), ::towupper ) ; | |
170 if ( avoid_reboot == L"" ) | |
171 { | |
172 state = not_known ; | |
173 } | |
174 else if ( avoid_reboot == L"NO" ) | |
175 { | |
176 state = allow ; | |
177 session.log( "Reboot allowed on command line." ) ; | |
178 } | |
179 else if ( avoid_reboot == L"PASSIVE" ) | |
180 { | |
181 state = passive ; | |
182 session.log( "Reboot avoided on command line." ) ; | |
183 } | |
184 else if ( avoid_reboot == L"ACTIVE" ) | |
185 { | |
186 state = active ; | |
187 } | |
188 else if ( avoid_reboot == L"AUTOMATIC" ) | |
189 { | |
190 state = automatic ; | |
191 } | |
192 else | |
193 { | |
194 // It's an error to specify an unrecognized value for AVOIDREBOOT. | |
195 throw std::runtime_error( "unrecognized value for AVOIDREBOOT" ) ; | |
196 } | |
197 | |
198 /* | |
199 * When running as an update (see Updater.cpp), this installer receives the command line option "/qb", | |
200 * which sets the UI level to "Basic UI". | |
201 * When running as an initial install, we cannot expect what command line op tions this installer receives. | |
202 */ | |
203 /* | |
204 * The UILevel property indicates whether we have the ability to put dialog boxes up. | |
205 * Levels 2 (silent) and 3 (basic) do not have this ability. | |
206 * Levels 4 (reduced) and 5 (full) do. | |
207 * | |
208 * MSDN [UILevel property](http://msdn.microsoft.com/en-us/library/windows/d esktop/aa372096%28v=vs.85%29.aspx) | |
209 */ | |
210 std::wstring uilevel = Property( session, L"UILevel" ) ; | |
211 bool interactive ; | |
212 if ( uilevel == L"5" || uilevel == L"4" ) | |
213 { | |
214 interactive = true ; | |
215 // Assert state is one of { not_known, allow, passive, active, automatic } | |
216 } | |
217 else if ( uilevel == L"3" || uilevel == L"2" ) | |
218 { | |
219 // Assert installer is running without user interaction. | |
220 interactive = false ; | |
221 if ( state == not_known ) | |
222 { | |
223 » // Assert AVOIDREBOOT was not specified | |
224 » /* | |
225 » * This is where we specify default behavior for non-interactive operati on. | |
226 » * The choice of "allow" makes it act like other installers, which is to make no effort to avoid a reboot after installation. | |
227 » */ | |
228 » state = allow ; | |
229 » session.log( "Reboot allowed by default in non-interactive session." ) ; | |
230 } | |
231 else if ( state == active ) | |
232 { | |
233 » throw std::runtime_error( "AVOIDREBOOT=ACTIVE in non-interative session is not consistent" ) ; | |
234 } | |
235 // Assert state is one of { allow, passive, automatic } | |
236 } | |
237 else | |
238 { | |
239 throw std::runtime_error( "unrecognized value for UILevel" ) ; | |
240 } | |
241 | |
242 /* | |
243 * Now that preliminaries are over, we set up the accessors for UI text. | |
244 * We only use the object 'message_text' for interactive sessions, but it's cheap to set up and a hassle to conditionalize. | |
245 * | |
246 * The key "close_ie" is the component name within the file "close_ie.wxi" t hat defines rows in the localization table. | |
247 * The identifiers for the message_text.text() function are defined within t hat file. | |
248 */ | |
27 Installation_Database db( session ) ; | 249 Installation_Database db( session ) ; |
28 session.log( L"Have database object" ) ; | 250 custom_message_text message_text( db, L"close_ie" ) ; |
29 | 251 |
30 session.log( L"Still with new Property operator+ implementations!" ) ; | 252 /* |
31 session.log( L"VersionMsi = " + Property( session, L"VersionMsi" ) ) ; | 253 * State machine: Loop through non-terminal states. |
32 | 254 * |
33 Property tv( session, L"TESTVARIABLE" ) ; | 255 * Loop invariant: IE was running at last check, that is, iec.is_running() w ould return true. |
34 session.log( L"TESTVARIABLE = " + tv ) ; | 256 */ |
35 session.log( L"Setting TESTVARIABLE to 'testvalue'" ) ; | 257 while ( state <= automatic )» // "automatic" is the non-terminal sta te with the highest value |
36 tv = L"testvalue" ; | 258 { |
37 session.log( L"TESTVARIABLE = " + tv ) ; | 259 switch ( state ) |
260 { | |
261 case not_known: | |
262 » /* | |
263 » * Precondition: interactive session | |
264 » * | |
265 » * Ask the user "Would you like to close IE and avoid reboot?" | |
266 » * Yes -> Close IE somehow. Goto part_known. | |
267 » * No -> Install with reboot. Goto allow. | |
268 » * Cancel -> terminate installation. Goto abort. | |
269 » */ | |
270 » { | |
271 » int x = session.write_message( IMB( message_text.text( L"dialog_unknow n" ), IMB::warning_box, IMB::yes_no_cancel, IMB::default_button_three ) ) ; | |
272 » switch ( x ) | |
273 » { | |
274 » case IDYES: | |
275 » state = part_known ; | |
276 » break ; | |
277 » case IDNO: | |
278 » state = allow ; | |
279 » session.log( "User chose to allow reboot" ) ; | |
280 » break ; | |
281 » case IDCANCEL: | |
282 » state = abort ; | |
283 » session.log( "User cancelled installation" ) ; | |
284 » break ; | |
285 » default: | |
286 » throw unexpected_return_value_from_message_box() ; | |
287 » } | |
288 » } | |
289 » break ; | |
290 | |
291 case part_known: | |
292 » /* | |
293 » * Precondition: interactive session | |
294 » * | |
295 » * Ask the user "Would you like the installer to close IE for you?" | |
296 » * Yes -> Goto automatic | |
297 » * No -> Goto active | |
298 » * Cancel -> Goto not_known | |
299 » */ | |
300 » { | |
301 » int x = session.write_message( IMB( message_text.text( L"dialog_part_k nown" ), IMB::warning_box, IMB::yes_no_cancel, IMB::default_button_three ) ) ; | |
302 » switch ( x ) | |
303 » { | |
304 » case IDYES: | |
305 » state = automatic ; | |
306 » break ; | |
307 » case IDNO: | |
308 » state = active ; | |
309 » break ; | |
310 » case IDCANCEL: | |
311 » state = not_known ; | |
312 » break ; | |
313 » default: | |
314 » throw unexpected_return_value_from_message_box() ; | |
315 » } | |
316 » } | |
317 » break ; | |
318 | |
319 case active: | |
320 » /* | |
321 » * Precondition: interactive session | |
322 » * | |
323 » * IE is no longer running -> Goto success | |
324 » * IE is still running -> | |
325 » * Ask the user to close IE manually | |
326 » * OK -> re-enter this state | |
327 » * Cancel -> Goto not_known | |
328 » */ | |
329 » { | |
330 » int x = session.write_message( IMB( message_text.text( L"dialog_active _retry" ), IMB::warning_box, IMB::ok_cancel, IMB::default_button_one ) ) ; | |
331 » switch ( x ) | |
332 » { | |
333 » case IDOK: | |
334 » /* | |
335 » * Refresh our knowledge of whether IE is running. | |
336 » * If it is, we display the dialog again. The state doesn't change, so we just iterate again. | |
337 » * If it's not, then the user has closed IE and we're done. | |
338 » */ | |
339 » iec.refresh() ; | |
340 » if ( ! iec.is_running() ) | |
341 » { | |
342 » state = success ; | |
343 » session.log( "User shut down IE manually." ) ; | |
344 » } | |
345 » break ; | |
346 » case IDCANCEL: | |
347 » state = not_known ; | |
348 » break ; | |
349 » default: | |
350 » throw unexpected_return_value_from_message_box() ; | |
351 » } | |
352 » } | |
353 » break ; | |
354 | |
355 case automatic: | |
356 » /* | |
357 » * Close all known IE instances. | |
358 » * Unlike other cases, this state starts with an action and not a user q uery. | |
359 » * We first shut down IE, or at least attempt to. | |
360 » * | |
361 » * Succeeded -> Goto success | |
362 » * Failed && interactive -> | |
363 » * Ask user if they would like to try again | |
364 » * Retry -> re-enter this state | |
365 » * Cancel -> Goto not_known | |
366 » * Failed && not interactive -> Goto abort | |
367 » */ | |
368 » { | |
369 » bool IE_was_closed = iec.shut_down() ; | |
370 » if ( iec.is_running() ) | |
371 » { | |
372 » session.log( "Attempt to shut down IE automatically failed." ) ; | |
373 » if ( interactive ) | |
374 » { | |
375 » // Assert Interactive session and IE did not shut down. | |
376 » int x = session.write_message( IMB( message_text.text( L"dialog_au tomatic_retry" ), IMB::warning_box, IMB::retry_cancel, IMB::default_button_one ) ) ; | |
377 » switch ( x ) | |
378 » { | |
379 » case IDRETRY: | |
380 » » // Don't change the state. Iterate again. | |
381 » » break ; | |
382 » case IDCANCEL: | |
383 » » state = not_known ; | |
384 » » break ; | |
385 » default: | |
386 » » throw unexpected_return_value_from_message_box() ; | |
387 » } | |
388 » } | |
389 » else | |
390 » { | |
391 » // Assert Non-interactive session and IE did not shut down. | |
392 » state = abort ; | |
393 » session.log( "Failed to shut down IE automatically." ) ; | |
394 » } | |
395 » } | |
396 » else | |
397 » { | |
398 » // Assert IE is not running, so shut_down() succeeded. | |
399 » state = success ; | |
400 » session.log( "Automatically shut down IE." ) ; | |
401 » } | |
402 » } | |
403 » break; | |
404 } | |
405 } | |
406 /* | |
407 * State machine: Actions for terminal states. | |
408 */ | |
409 switch ( state ) | |
410 { | |
411 case success: | |
412 » if ( iec.is_running() ) | |
413 » { | |
414 » browser_running = L"1" ; | |
415 » browser_closed = L"0" ; | |
416 » } | |
417 » else | |
418 » { | |
419 » browser_running = L"0" ; | |
420 » browser_closed = L"1" ; | |
421 » } | |
422 » return ERROR_SUCCESS ; | |
423 » break; | |
424 case abort: | |
425 » return ERROR_INSTALL_USEREXIT ; | |
426 » break; | |
427 } | |
428 } | |
429 catch( std::exception & e ) | |
430 { | |
431 session.log_noexcept( "terminated by exception: " + std::string( e.what() ) ) ; | |
432 return ERROR_INSTALL_FAILURE ; | |
38 } | 433 } |
39 catch( ... ) | 434 catch( ... ) |
40 { | 435 { |
436 session.log_noexcept( "terminated by unknown exception" ) ; | |
41 return ERROR_INSTALL_FAILURE ; | 437 return ERROR_INSTALL_FAILURE ; |
42 } | 438 } |
43 | 439 // Should be unreachable. |
44 /* | |
45 * While we're working on infrastructure (and not the CA itself), fail the act ion. | |
46 */ | |
47 return ERROR_INSTALL_FAILURE ; | 440 return ERROR_INSTALL_FAILURE ; |
48 } | 441 } |
49 | 442 |
50 /* | 443 /* |
51 * EnumWindows system call: http://msdn.microsoft.com/en-us/library/windows/desk top/ms633497%28v=vs.85%29.aspx | 444 * EnumWindows system call: http://msdn.microsoft.com/en-us/library/windows/desk top/ms633497%28v=vs.85%29.aspx |
52 */ | 445 */ |
53 /** | 446 /** |
54 * | 447 * |
55 * Callback function for EnumWindows. | 448 * Callback function for EnumWindows. |
56 */ | 449 */ |
57 BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) | 450 BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) |
58 { | 451 { |
59 return TRUE ; | 452 return TRUE ; |
60 } | 453 } |
61 | 454 |
62 /** | 455 /** |
63 * Windows_List | 456 * Windows_List |
64 * | |
65 * | |
66 */ | 457 */ |
67 class Window_List { | 458 class Window_List { |
68 public: | 459 public: |
69 void enumerate_top_level(); | 460 void enumerate_top_level(); |
70 }; | 461 }; |
OLD | NEW |