| 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 |