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