Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: installer/src/custom-action/close_application.cpp

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

Powered by Google App Engine
This is Rietveld