(Re-)Start Me Up ...

As I mentioned in a recent post, I'm currently working on a Windows Vista workshop. As part of that, I've created a simple application that demonstrates the basic interaction of the Windows Installer with Vista's new Restart Manager (RM). I thought I'd post some information on how easy this was to do, which you might find useful for your own applications and packages.

My sample application is a basic text editor that uses a multi-line edit control to allow the user to write short notes:

Screenshot 1: RMtestX - not Restart Manager aware

There are two version of the application, RMtestX (non Restart Manager aware, used for screenshots 1-3) and RMtestV (Restart Manager aware). If RMtestX were to crash, you'd see something like this:

Screenshot 2: RMtestX crashes

So, the user can close the application or debug it, which is not any better than Windows XP offers. Generally, the user doesn't want to do either. They just want to carry on with their work. Similarly, if a patch is applied when the application is running, they have two equally unappealing option:

Screenshot 3: RMtestX is being patched.

If they want to update to happen, then the user can either manually save data, close the application, complete the update, restart the application, then open the saved data, or opt for a full system reboot. Neither option is ideal.

So, how can we use Restart Manager to improve things?

Becoming Restart Manager Aware
Firstly, before the application can interact with the RM, it needs to register itself using the RegisterApplicationRestart API. Doing this is extremely simple:

// register with Restart Manager RegisterApplicationRestart(L"-rm", 0);

For RMtestV, I call the API just after the main window is created and just before jumping into the message loop, though you could call it elsewhere - doing it early makes sense since your application can't know when it might be called upon to restart. The "-rm" switch is an arbitrary command line that the RM passes to the application if it's been restarted. You can make this more or less anything you like. The idea is that the application can tell from this that it was restarted and can respond appropriately. In the case of RMtestV, this check is performed when the WM_CREATE message is received:

case WM_CREATE:    //    // some initialisation stuff    //    // check to see if RM called us    if(RMstart())    {       RestoreText(GetDlgItem(hWnd, IDC_EDIT1));       MessageBox(hWnd, L"RMtestV was restarted by the Restart Manager", L"RMtestV: Restart Alert", MB_OK|MB_ICONEXCLAMATION);    }    return 0;

RMstart() uses the GetCommandLine and CommandLineToArgvW APIs to check if RMtestV was called with "-rm". If it was, then the RestoreText() call restores the contents of the EDIT control saved during shutdown (as mentioned below). Alerting the user to what just happened isn't mandatory, of course, but is probably a good idea. Restart Manger adds entries like this to the Application Event Log:

Event ID: 10002 Source: Restart Manager Type: Information Description: Shutting down application or service 'Restart Manager Example: RM Compliant'. Event ID: 10003 Source: Restart Manager Type: Information Description: Restarting application or service 'Restart Manager Example: RM Compliant'.

To make this automated stopping and starting more useful, RMtestV needs to be able to save any text the user has entered before it shuts down. When the RM is wanting to restart an application, it sends two window messages in order, which the application needs to process, WM_QUERYENDSESSION and WM_ENDSESSION:

case WM_QUERYENDSESSION:    // no need to do anything special here    // but must return "1" if the application is ready to restart    return 1;

You can perform some clean up work when handling WM_QUERYENDSESSION, but the application has to respond quickly at this point, so it is better to delay this work until handling WM_ENDSESSION. In particular, it is a bad idea to prompt for user input in response to WM_QUERYENDSESSION.  If your application can't restart, say because it is doing work that can't be interrupted, return "0". 

Next comes the notification that the session is ending:

case WM_ENDSESSION:    if(ENDSESSION_CLOSEAPP & lParam)    {       SaveText(GetDlgItem(hWnd, IDC_EDIT1));    }    return 0;

If the application is being asked to restart due to file replacement during patching, the ENDSESSION_CLOSEAPP flag is passed to the application. RMtestV responds to this flag by saving the current contents of the EDIT control in a temp file using the user-defined SaveText() function.

So, now the application is ready to respond to the Restart Manager. For example, if RMtestV crashes now, the user sees this:

Screenshot 4: Restart Manager Prompt

To make things a lot smoother, RMtestV should regularly save the user's data to a temporary location, so that if the user selects "Restart the program", RMtestV will close and re-open with the data restored with minimum interruption. As I was mainly interested in the Windows Installer interaction, I didn't implement this, but it would be a simple matter to use the SetTimer API to trigger a call to SaveText() or similar on a regular basis.

Before looking at how Windows Installer makes use of this, two important points to note about the above are:

  • Adding Restart Manager functionality to your application is not hugely complicated
  • You need to do all of the important work of saving/restoring any data, configuration settings, etc.

 

Interacting With the Windows Installer
As mentioned above, prior to Windows Vista, any attempt to patch files held open by an application would fail. The best you could get was to prompt the user to manually close the application (and re-open it themselves later), or to require a system reboot. Once an application is RM aware, we need to author the MSI package correctly to take advantage of it. The outline of what needs done is as follows:

Dialog Table
You need to define the MsiRMFilesInUse dialog. This is very similar to the existing FilesInUse dialog, but it allows the user to select an automatic restart of the application using Restart Manager.

Dialog HCentering VCentering Width Height Attributes Title Control_First Control_Default Control_Cancel
MsiRMFilesInUse 50 50 374 266 19 [ProductName] PushButton1 PushButton1 PushButton2

Control Table
The controls used on the MsiRMFilesInUse dialog are defined as follows:

Dialog_ Control Type X Y Width Height Attributes Property Text Control_Next Help
MsiRMFilesInUse DlgLine Line 0 234 375 0 1        
MsiRMFilesInUse Banner Bitmap 0 0 374 44 1   [BannerBitmap]    
MsiRMFilesInUse DlgDesc Text 21 23 292 25 65539   Some files that need to be updated are currently in use.    
MsiRMFilesInUse DlgTitle Text 13 6 292 25 65539   {&MSSansBold8}Files in Use    
MsiRMFilesInUse DlgText Text 21 49 348 17 3   The following applications are using files that need to be updated by this setup.    
MsiRMFilesInUse BannerLine Line 0 44 374 0 1        
MsiRMFilesInUse RadioButtonGroup1 RadioButtonGroup 19 187 343 40 16777219 ShutdownOption   List  
MsiRMFilesInUse List ListBox 21 66 331 118 7 FileInUseProcess   PushButton1  
MsiRMFilesInUse PushButton1 PushButton 228 244 66 17 3   OK PushButton2  
MsiRMFilesInUse PushButton2 PushButton 299 244 66 17 3   Cancel RadioButtonGroup1  

ControlEvent Table
Once the controls are defined, we need to define what actions they trigger when used:

Dialog_ Control_ Event Argument Condition Ordering
MsiRMFilesInUse PushButton1 EndDialog Return 1 1
MsiRMFilesInUse PushButton1 RMShutdownAndRestart 0 ShutdownOption=1 2
MsiRMFilesInUse PushButton2 EndDialog Exit 1 1

RadioButton Table
We use a pair of radio buttons to alow the user to choose between using restart manager and going for an old-style post-boot replacement:

Property Order Value X Y Width Height Text Help
ShutdownOption 1 1 6 9 331 14 Automatically close all applications and attempt to restart them after setup is complete.  
ShutdownOption 2 2 6 21 322 17 Do not close applications. (A reboot will be required.)  

Property Table
A property is used to determine which of the radio buttons the user has selected:

Property Value
ShutdownOption 1

I've exported examples of these tables as IDT files, which you can import into your packages using ORCA:

MsiRMFilesInUse Related Tables

Once this is in place, the user will see different options when patching while the application is in use:

Screenshot 5: MsiRMFilesInUse dialog 

Further Reading
The above isn't everything the Restart Manager has to offer. For example, you can call on it to give you a list of processes holding a given file open, then (if the applications are registered), ask for them to be restarted - essentially what the Windows Installer does behind the scenes. Get more information here:

Restart Manager Documentation
Application Recovery and Restart Documentation
Using Windows Installer with Restart Manager
Windows Installer Team Discussion on Restart Manager