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

Unified 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.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: installer/src/custom-action/close_application.cpp
===================================================================
--- a/installer/src/custom-action/close_application.cpp
+++ b/installer/src/custom-action/close_application.cpp
@@ -2,48 +2,441 @@
* \file close_application.cpp
*/
+#include <algorithm>
+
#include "session.h"
#include "property.h"
#include "database.h"
+#include "process.h"
+#include "interaction.h"
+#include "custom-i18n.h"
+//-------------------------------------------------------
+//-------------------------------------------------------
+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
+{
+ 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'
-#include <TlHelp32.h>
+ Process_Closer ie_closer ;
+ Process_Closer engine_closer ;
+
+ static const wchar_t * ie_names[] ;
+ static const wchar_t * engine_names[] ;
+ //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
+
+public:
+ IE_Closer()
+ : snapshot(), ie_closer( snapshot, ie_names, 1 ), engine_closer( snapshot, engine_names, 1 )
Oleksandr 2014/03/12 19:26:04 One more cosmetic comment - we don't usually have
+ {}
+
+ void refresh()
+ {
+ snapshot.refresh() ;
+ ie_closer.refresh() ;
+ engine_closer.refresh() ;
+ }
+
+ bool is_running()
+ {
+ return ie_closer.is_running() || engine_closer.is_running() ;
+ }
+
+ bool shut_down()
+ {
+ if ( ie_closer.is_running() && ! ie_closer.shut_down() )
+ {
+ // Assert IE is still running
+ // This is after we've tried to shut it down, so we fail
+ return false ;
+ }
+ if ( engine_closer.is_running() && ! engine_closer.shut_down() )
+ {
+ // Assert the engine is still running
+ // This is after IE has shut down itself and after we've tried to shut down the engine. Whatever.
+ return false ;
+ }
+ return true ;
+ }
+} ;
+
+const wchar_t * IE_Closer::ie_names[] = { L"IExplore.exe" } ;
+const wchar_t * IE_Closer::engine_names[] = { L"AdblockPlusEngine.exe" } ;
+
+//-------------------------------------------------------
+// abp_close_ie
+//-------------------------------------------------------
/**
* Exposed DLL entry point for custom action.
* The function signature matches the calling convention used by Windows Installer.
*
+ * This function supports four policy stances with respect to a running IE process.
+ *
+ * - Allow reboot.
+ * IE is running, so what? I'm willing to reboot after installation.
+ * - Avoid reboot passively.
+ * I don't want to affect any other running processes, but I don't want to reboot. I'll abort the installation.
+ * - Avoid reboot actively.
+ * I want to shut down IE in order to avoid a reboot.
+ * I'll do it manually when the time is right.
+ * - Avoid reboot automatically.
+ * I want to shut down IE automatically in order to avoid a reboot.
+ *
+ * In a silent installation, the default stance is "allow reboot", which is to say, to act like most installers.
+ * In an interactive installation, the stance is gathered from the user through dialog boxes.
+ * If the MSI property AVOIDREBOOT is set to one of the values NO, PASSIVE, ACTIVE, or AUTOMATIC, the policy is set accordingly.
+ * In a silent installation, this is the only way getting a stance other than the default.
+ * In an interactive installation, AVOIDREBOOT skips the dialogs.
+ *
* \param[in] session_handle
* Windows installer session handle
+ *
+ * \return
+ * An integer interpreted as a custom action return value.
+ *
+ * \post
+ * + The return value is one of the following:
+ * - ERROR_INSTALL_USEREXIT if the user cancelled installation.
+ * - ERROR_INSTALL_FAILURE if something unexpected happened, usually if the top level try-block caught an exception.
+ * - ERROR_SUCCESS otherwise.
+ * + The function performed at least one check that Internet Explorer was running.
+ * + If ERROR_SUCCESS, the MSI property RUNNINGBROWSER is set and has one of the following values:
+ * - 1 if Internet Explorer was running upon the last check.
+ * - 0 otherwise.
+ * \post
+ * Note that this function cannot provide any assurance that Internet Explorer stays either stays running or stays not running.
+ *
+ * \sa
+ * - MSDN [Custom Action Return Values](http://msdn.microsoft.com/en-us/library/aa368072%28v=vs.85%29.aspx)
*/
extern "C" UINT __stdcall
-abp_close_applications( MSIHANDLE session_handle )
+abp_close_ie( MSIHANDLE session_handle )
{
- // Always supply an externally-exposed function with a catch-all block
+ // Utility typedef to shorten the class name.
+ typedef Installer_Message_Box IMB ;
+
+ /*
+ * Immediate_Session cannot throw, so it can go outside the try-block.
+ * It's needed in the catch-all block to write an error message to the log.
+ */
+ Immediate_Session session( session_handle, "abp_close_ie" ) ;
+
+ // The body of an entry point function must have a catch-all.
try {
- Immediate_Session session( session_handle, L"abp_close_applications" ) ;
- session.log( L"Have session object" ) ;
+ // MSI property BROWSERRUNNING is one of the return values of this function.
+ Property browser_running( session, L"BROWSERRUNNING" ) ;
+ Property browser_closed( session, L"BROWSERCLOSED" ) ;
+
+ // Instantiation of Process_Closer takes a snapshot.
+ IE_Closer iec ;
+
+ /*
+ * 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.
+ */
+ if ( ! iec.is_running() )
+ {
+ browser_running = L"0" ; // The browser is not running.
+ browser_closed = L"0" ; // We didn't close the browser (and we couldn't have).
+ session.log( "IE not running. No issue with reboot policy." ) ;
+ return ERROR_SUCCESS ;
+ }
+
+ /*
+ * As a (potentially) user-driven function, a state machine manages control flow.
+ * The states are organized around the policy stances.
+ */
+ enum Policy_State {
+ // Non-terminal states
+ not_known, // We don't know the user's stance at all
+ part_known, // The user has indicated either ACTIVE or AUTOMATIC
+ active, // Actively avoid reboot
+ automatic, // Automatically avoid reboot
+ // Terminal states
+ success,
+ abort,
+ // Aliases for what would ordinarily be non-terminal states.
+ // They're terminal because of implementation details.
+ allow = success, // Allow reboot.
+ passive = abort, // Passively avoid reboot, that is, don't try to close IE.
+ };
+ Policy_State state = not_known;
Oleksandr 2014/03/12 19:26:04 Redundant initialization here
Eric 2014/03/17 12:26:46 Done.
+
+ /*
+ * Use the AVOIDREBOOT property, if present, to set an initial state.
+ */
+ std::wstring avoid_reboot = Property( session, L"AVOIDREBOOT" ) ;
+ std::transform( avoid_reboot.begin(), avoid_reboot.end(), avoid_reboot.begin(), ::towupper ) ;
+ if ( avoid_reboot == L"" )
+ {
+ state = not_known ;
+ }
+ else if ( avoid_reboot == L"NO" )
+ {
+ state = allow ;
+ session.log( "Reboot allowed on command line." ) ;
+ }
+ else if ( avoid_reboot == L"PASSIVE" )
+ {
+ state = passive ;
+ session.log( "Reboot avoided on command line." ) ;
+ }
+ else if ( avoid_reboot == L"ACTIVE" )
+ {
+ state = active ;
+ }
+ else if ( avoid_reboot == L"AUTOMATIC" )
+ {
+ state = automatic ;
+ }
+ else
+ {
+ // It's an error to specify an unrecognized value for AVOIDREBOOT.
+ throw std::runtime_error( "unrecognized value for AVOIDREBOOT" ) ;
+ }
+
+ /*
+ * When running as an update (see Updater.cpp), this installer receives the command line option "/qb",
+ * which sets the UI level to "Basic UI".
+ * When running as an initial install, we cannot expect what command line options this installer receives.
+ */
+ /*
+ * The UILevel property indicates whether we have the ability to put dialog boxes up.
+ * Levels 2 (silent) and 3 (basic) do not have this ability.
+ * Levels 4 (reduced) and 5 (full) do.
+ *
+ * MSDN [UILevel property](http://msdn.microsoft.com/en-us/library/windows/desktop/aa372096%28v=vs.85%29.aspx)
+ */
+ std::wstring uilevel = Property( session, L"UILevel" ) ;
+ bool interactive ;
+ if ( uilevel == L"5" || uilevel == L"4" )
+ {
+ interactive = true ;
+ // Assert state is one of { not_known, allow, passive, active, automatic }
+ }
+ else if ( uilevel == L"3" || uilevel == L"2" )
+ {
+ // Assert installer is running without user interaction.
+ interactive = false ;
+ if ( state == not_known )
+ {
+ // Assert AVOIDREBOOT was not specified
+ /*
+ * This is where we specify default behavior for non-interactive operation.
+ * The choice of "allow" makes it act like other installers, which is to make no effort to avoid a reboot after installation.
+ */
+ state = allow ;
+ session.log( "Reboot allowed by default in non-interactive session." ) ;
+ }
+ else if ( state == active )
+ {
+ throw std::runtime_error( "AVOIDREBOOT=ACTIVE in non-interative session is not consistent" ) ;
+ }
+ // Assert state is one of { allow, passive, automatic }
+ }
+ else
+ {
+ throw std::runtime_error( "unrecognized value for UILevel" ) ;
+ }
+
+ /*
+ * Now that preliminaries are over, we set up the accessors for UI text.
+ * We only use the object 'message_text' for interactive sessions, but it's cheap to set up and a hassle to conditionalize.
+ *
+ * The key "close_ie" is the component name within the file "close_ie.wxi" that defines rows in the localization table.
+ * The identifiers for the message_text.text() function are defined within that file.
+ */
Installation_Database db( session ) ;
- session.log( L"Have database object" ) ;
+ custom_message_text message_text( db, L"close_ie" ) ;
- session.log( L"Still with new Property operator+ implementations!" ) ;
- session.log( L"VersionMsi = " + Property( session, L"VersionMsi" ) ) ;
+ /*
+ * State machine: Loop through non-terminal states.
+ *
+ * Loop invariant: IE was running at last check, that is, iec.is_running() would return true.
+ */
+ while ( state <= automatic ) // "automatic" is the non-terminal state with the highest value
+ {
+ switch ( state )
+ {
+ case not_known:
+ /*
+ * Precondition: interactive session
+ *
+ * Ask the user "Would you like to close IE and avoid reboot?"
+ * Yes -> Close IE somehow. Goto part_known.
+ * No -> Install with reboot. Goto allow.
+ * Cancel -> terminate installation. Goto abort.
+ */
+ {
+ int x = session.write_message( IMB( message_text.text( L"dialog_unknown" ), IMB::warning_box, IMB::yes_no_cancel, IMB::default_button_three ) ) ;
+ switch ( x )
+ {
+ case IDYES:
+ state = part_known ;
+ break ;
+ case IDNO:
+ state = allow ;
+ session.log( "User chose to allow reboot" ) ;
+ break ;
+ case IDCANCEL:
+ state = abort ;
+ session.log( "User cancelled installation" ) ;
+ break ;
+ default:
+ throw unexpected_return_value_from_message_box() ;
+ }
+ }
+ break ;
- Property tv( session, L"TESTVARIABLE" ) ;
- session.log( L"TESTVARIABLE = " + tv ) ;
- session.log( L"Setting TESTVARIABLE to 'testvalue'" ) ;
- tv = L"testvalue" ;
- session.log( L"TESTVARIABLE = " + tv ) ;
+ case part_known:
+ /*
+ * Precondition: interactive session
+ *
+ * Ask the user "Would you like the installer to close IE for you?"
+ * Yes -> Goto automatic
+ * No -> Goto active
+ * Cancel -> Goto not_known
+ */
+ {
+ int x = session.write_message( IMB( message_text.text( L"dialog_part_known" ), IMB::warning_box, IMB::yes_no_cancel, IMB::default_button_three ) ) ;
+ switch ( x )
+ {
+ case IDYES:
+ state = automatic ;
+ break ;
+ case IDNO:
+ state = active ;
+ break ;
+ case IDCANCEL:
+ state = not_known ;
+ break ;
+ default:
+ throw unexpected_return_value_from_message_box() ;
+ }
+ }
+ break ;
+
+ case active:
+ /*
+ * Precondition: interactive session
+ *
+ * IE is no longer running -> Goto success
+ * IE is still running ->
+ * Ask the user to close IE manually
+ * OK -> re-enter this state
+ * Cancel -> Goto not_known
+ */
+ {
+ int x = session.write_message( IMB( message_text.text( L"dialog_active_retry" ), IMB::warning_box, IMB::ok_cancel, IMB::default_button_one ) ) ;
+ switch ( x )
+ {
+ case IDOK:
+ /*
+ * Refresh our knowledge of whether IE is running.
+ * If it is, we display the dialog again. The state doesn't change, so we just iterate again.
+ * If it's not, then the user has closed IE and we're done.
+ */
+ iec.refresh() ;
+ if ( ! iec.is_running() )
+ {
+ state = success ;
+ session.log( "User shut down IE manually." ) ;
+ }
+ break ;
+ case IDCANCEL:
+ state = not_known ;
+ break ;
+ default:
+ throw unexpected_return_value_from_message_box() ;
+ }
+ }
+ break ;
+
+ case automatic:
+ /*
+ * Close all known IE instances.
+ * Unlike other cases, this state starts with an action and not a user query.
+ * We first shut down IE, or at least attempt to.
+ *
+ * Succeeded -> Goto success
+ * Failed && interactive ->
+ * Ask user if they would like to try again
+ * Retry -> re-enter this state
+ * Cancel -> Goto not_known
+ * Failed && not interactive -> Goto abort
+ */
+ {
+ bool IE_was_closed = iec.shut_down() ;
+ if ( iec.is_running() )
+ {
+ session.log( "Attempt to shut down IE automatically failed." ) ;
+ if ( interactive )
+ {
+ // Assert Interactive session and IE did not shut down.
+ int x = session.write_message( IMB( message_text.text( L"dialog_automatic_retry" ), IMB::warning_box, IMB::retry_cancel, IMB::default_button_one ) ) ;
+ switch ( x )
+ {
+ case IDRETRY:
+ // Don't change the state. Iterate again.
+ break ;
+ case IDCANCEL:
+ state = not_known ;
+ break ;
+ default:
+ throw unexpected_return_value_from_message_box() ;
+ }
+ }
+ else
+ {
+ // Assert Non-interactive session and IE did not shut down.
+ state = abort ;
+ session.log( "Failed to shut down IE automatically." ) ;
+ }
+ }
+ else
+ {
+ // Assert IE is not running, so shut_down() succeeded.
+ state = success ;
+ session.log( "Automatically shut down IE." ) ;
+ }
+ }
+ break;
+ }
+ }
+ /*
+ * State machine: Actions for terminal states.
+ */
+ switch ( state )
+ {
+ case success:
+ if ( iec.is_running() )
+ {
+ browser_running = L"1" ;
+ browser_closed = L"0" ;
+ }
+ else
+ {
+ browser_running = L"0" ;
+ browser_closed = L"1" ;
+ }
+ return ERROR_SUCCESS ;
+ break;
+ case abort:
+ return ERROR_INSTALL_USEREXIT ;
+ break;
+ }
+ }
+ catch( std::exception & e )
+ {
+ session.log_noexcept( "terminated by exception: " + std::string( e.what() ) ) ;
+ return ERROR_INSTALL_FAILURE ;
}
catch( ... )
{
+ session.log_noexcept( "terminated by unknown exception" ) ;
return ERROR_INSTALL_FAILURE ;
}
-
- /*
- * While we're working on infrastructure (and not the CA itself), fail the action.
- */
+ // Should be unreachable.
return ERROR_INSTALL_FAILURE ;
}
@@ -61,8 +454,6 @@
/**
* Windows_List
- *
- *
*/
class Window_List {
public:

Powered by Google App Engine
This is Rietveld