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

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

Issue 5675960980471808: Updated installer with custom action (Closed)
Patch Set: Created March 8, 2014, 5:06 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"
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 };
OLDNEW

Powered by Google App Engine
This is Rietveld