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" |
| 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 }; |
OLD | NEW |