Sunday, November 12, 2006 1:50 PM bart

Windows Vista - Exploring the Restart Manager in C#

Introduction

Everyone knows the problem of files being in use during installations of software. In the pre Windows Vista ages, a simple yet efficient (at least for the setup developer) is simply to reboot the machine, being a main cause of annoyance for end users. Windows Vista recognizes this problem by providing a Restart Manager that can be used by setups to analyze dependencies, stop and restart applications and services during the setup. MSI 4.0 relies on this API to create a better setup experience. This post introduces you to the basics of the Restart Manager technology from managed code.

The API

Right, let's take a look at the API first. You'll find the API in the Windows SDK under the topic Restart Manager, abbreviated as Restart Mgr. It's defined in a single DLL file called rstrtmgr.dll, with the associated headers defined in RestartManager.h. We'll cover the core functions and leave others for your own exploration:

  • RmStartSession is used to create a new Restart Manager session. A maximum of 64 concurrent session can be used at the same time. The session will encapsulate all of the actions required to do a setup, allowing you to stop/restart applications.
  • RmRegisterResources takes in a list of resources that have to be "managed" by the Restart Manager in the current session. Such resources can be a file (that is to be replaced by the setup), a process (e.g. when upgrading an application) and a Windows Service.
  • RmShutdown shuts down the registered resources so that setup can proceed without being faced with files in use.
  • RmRestart restarts the registered resources that have been shut down previously by RmShutdown.
  • RmEndSession stops a session that has been created by RmStartSession previously.

Other functions not discussed in this post include:

  • RmJoinSession can be used to join a secondary installer to an existing Restart Manager session.
  • RmAddFilter allows to change actions that will be performed on applications, e.g. to prevent restart or shutdown of a given application.
  • RmRemoveFilter reverses filters added by RmAddFilter.
  • RmGetFilterList lists all of the filters applied by the RmAddFilter function.
  • RmGetList provides information of the applications and services that are using resources that were registered by means of RmRegisterResources. In case a system restart is needed, the reason is provided.
  • RmCancelCurrentTask cancels a RmGetList, RmShutdown or RmRestart action in progress.

Typical actions will be as outlined in the list of discussed functions above. The goal of this post is to show this flow in managed code.

A simple scenario

We're going to simulate an application that performs a setup of an application that depends on IIS (i.e. IIS has to be stopped, or "W3SVC"), can't live with active Notepad instances and wants to update a .dll file that's in use. The first two scenarios are straightforward as you'll see further on. For the last scenario, the DLL dependency, we'll create the following application.

  1. Start by creating a new Visual Studio 2005 solution with a Console Application called "RestartManagerDemo" and a Class Library called "RestartManagerDemoLib". The console app references the library application.
  2. Now go Program.cs to add the following code:

 

[DllImport("kernel32.dll", CharSet = CharSet.Auto)] static extern uint RegisterApplicationRestart(string pwzCommandLine, RestartFlags dwFlags); static void Main(string[] args) { RegisterApplicationRestart(crashHint, RestartFlags.NONE); new RestartManagerDemoLib.Class1(); while (true) { Console.WriteLine("Ping " + ++j); Thread.Sleep(1000); } } [Flags] enum RestartFlags { NONE = 0, RESTART_CYCLICAL = 1, RESTART_NOTIFY_SOLUTION = 2, RESTART_NOTIFY_FAULT = 4, RESTART_NO_CRASH = 8, RESTART_NO_HANG = 16, RESTART_NO_PATCH = 32, RESTART_NO_REBOOT = 64 }

 

This code just simulates some work and makes sure the RestartManagerDemoLib is in use. The Restart Manager's job will be to detect this dependency and stop/restart our application. In order to be restartable, we need to register the application for application restart, part of the Application Recovery API in Vista (see previous post for more information).

Interop

Create a Console Application in Visual Studio 2005 and add the following DllImports to the Program class:

 

using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using Com = System.Runtime.InteropServices.ComTypes; using System.Diagnostics; using System.Threading; namespace RestartMgr { class Program { [StructLayout(LayoutKind.Sequential)] struct RM_UNIQUE_PROCESS { public int dwProcessId; public Com.FILETIME ProcessStartTime; } [Flags] enum RM_SHUTDOWN_TYPE : uint { RmForceShutdown = 0x1, RmShutdownOnlyRegistered = 0x10 } delegate void RM_WRITE_STATUS_CALLBACK(UInt32 nPercentComplete); [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)] static extern int RmStartSession(out IntPtr pSessionHandle, int dwSessionFlags, string strSessionKey); [DllImport("rstrtmgr.dll")] static extern int RmEndSession(IntPtr pSessionHandle); [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)] static extern int RmRegisterResources(IntPtr pSessionHandle, UInt32 nFiles, string[] rgsFilenames, UInt32 nApplications, RM_UNIQUE_PROCESS[] rgApplications, UInt32 nServices, string[] rgsServiceNames); [DllImport("rstrtmgr.dll")] static extern int RmShutdown(IntPtr pSessionHandle, RM_SHUTDOWN_TYPE lActionFlags, RM_WRITE_STATUS_CALLBACK fnStatus); [DllImport("rstrtmgr.dll")] static extern int RmRestart(IntPtr pSessionHandle, int dwRestartFlags, RM_WRITE_STATUS_CALLBACK fnStatus); [DllImport("kernel32.dll")] static extern bool GetProcessTimes(IntPtr hProcess, out Com.FILETIME lpCreationTime, out Com.FILETIME lpExitTime, out Com.FILETIME lpKernelTime, out Com.FILETIME lpUserTime);

 

Next, we'll with the beginning: creating a session by calling RmStartSession.

 

static void Main(string[] args) { IntPtr handle; string key = Guid.NewGuid().ToString(); int res = RmStartSession(out handle, 0, key); if (res == 0) { Console.WriteLine("Restart Manager session created with ID {0}", key);
Now it's time to register the resources we want to manipulate. This is done by calling RmRegisterResources:

 

 

RM_UNIQUE_PROCESS[] processes = GetProcesses("notepad"); res = RmRegisterResources( handle, 1, new string[] { @"C:\Users\Bart\Documents\Visual Studio 2005\Projects\RestartMgr\RestartManagerDemo\bin\Release\RestartManagerDemoLib.dll" }, (uint)processes.Length, processes, 1, new string[] { "W3SVC" } ); if (res == 0) { Console.WriteLine("Successfully registered resources.");
The RmRegisterResources function takes three lists. First, a list of files that the setup want to manipulate. Here you point to the RestartManagerDemoLib.dll file of the application created in "A Simple Scenario" above. Secondly, a list of processes of the type RM_UNIQUE_PROCESS. The GetProcesses method used to retrieve all Notepad instances running on the machine will be provided later, but let's continue with main flow now. The last list has the name of services we depend on and we want to be stopped during setup. On to the stop and restart logic with RmShutdown and RmRestart:

 

 

res = RmShutdown(handle, RM_SHUTDOWN_TYPE.RmForceShutdown, ReportPercentage); if (res == 0) { Console.WriteLine("Applications stopped successfully.\n"); Console.ForegroundColor = ConsoleColor.Yellow; Console.Write("Installing... "); Thread.Sleep(5000); Console.WriteLine("Done.\n"); Console.ResetColor(); res = RmRestart(handle, 0, ReportPercentage); if (res == 0) Console.WriteLine("Applications restarted successfully."); } }
Again, the code is straightforward. The last parameter to both calls is a delegate to a percent reporting tool, shown below, but first we end the Restart Manager session using RmEndSession:

 

 

res = RmEndSession(handle); if (res == 0) Console.WriteLine("Restart Manager session ended."); Console.ReadLine(); } }
Time for the two promised functions: one to report status of stop/restart and one to retrieve a list of processes with a given name (notepad in this case):

 

 

static RM_UNIQUE_PROCESS[] GetProcesses(string name) { List<RM_UNIQUE_PROCESS> lst = new List<RM_UNIQUE_PROCESS>(); foreach (Process p in Process.GetProcessesByName(name)) { RM_UNIQUE_PROCESS rp = new RM_UNIQUE_PROCESS(); rp.dwProcessId = p.Id; Com.FILETIME creationTime, exitTime, kernelTime, userTime; GetProcessTimes(p.Handle, out creationTime, out exitTime, out kernelTime, out userTime); rp.ProcessStartTime = creationTime; lst.Add(rp); } return lst.ToArray(); } static void ReportPercentage(UInt32 percent) { Console.WriteLine(percent); }
We're done. Pretty simple after all, with a bit of interop and some HRESULT-alike checking.

 

Test time

Time to take a look at the result. First make sure IIS 7 is started and open a command prompt as administrator. Launch the RestartManagerDemo.exe executable created in the "A Simple Scenario" step. Finally, open a couple of notepad instances. Now launch our demo application as administrator (to have the privileges to stop IIS). You'll see the following:

In the first phase, the system will stop all the dependencies. At 66% the system will block for a while, because it's stopping W3SVC which takes a while. What was stopped, will be relaunched in reverse order afterwards (around 25% you'll see a little disruption while IIS starts again). Notice Notepad won't be relaunched. This is due to the lack of a RegisterApplicationRestart call in that application, whileas our demo application has made such a call. Below you'll find a sample video of all applications (IIS W3SVC service, notepad and RestartManagerDemo.exe) in concert:

In this demo you saw how the Restart Manager can track dependencies back to the applications using these, and how it can stop and restart applications smoothly.

Have fun!

Bart De Smet
On my way to TechEd, somewhere in the air between Brussels and Barcelona

kick it on DotNetKicks.com

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# The November 06 Month Report

Friday, December 01, 2006 4:06 AM by B# .NET Blog

Yet another great (well, at least in my opinion) month of Daily Blogging . Once more, feedback from readers

# Il Restart Manager di Windows Vista

Tuesday, February 20, 2007 3:20 PM by Around and About .NET World