(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:
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
Comments
- Anonymous
January 01, 2003
The comment has been removed - Anonymous
January 01, 2003
On the The OEM Ready Program page, OEM’s can download the OEM Ready Program Test Cases for Windows Vista