Process and ServiceProcess Caching [Robert Villahermosa]

I’ve seen several questions over the last month or two about refreshing the status of certain properties on the Process and ServiceProcess classes.  I’d like to take this opportunity to take a look at an example from both these classes and see how we can resolve these issues.

Issue One: Process.MainWindowHandle resturns 0, even though a window is created

A user found that even though they waited for the UI Window of their application to be displayed, they were getting a MainWindowHandle of 0.  User code may look something like this:

    Process myProc = new Process();    myProc.StartInfo.FileName = "notepad.exe";    myProc.Start();    IntPtr myWinHandle = myProc.MainWindowHandle;    System.Threading.Thread.Sleep(5000);    Console.WriteLine(myWinHandle);

After this executes, myWinHandle has a value of 0, even though the sleep should be sufficient for the Window to pop up.  The first proposed solution I saw was to do the following:

    Process myProc = new Process();    myProc.StartInfo.FileName = "notepad.exe";    myProc.Start();    System.Threading.Thread.Sleep(5000);    IntPtr myWinHandle = myProc.MainWindowHandle;    Console.WriteLine(myWinHandle);

Better, but we still have the problem with the Sleep – we don’t know if the window is up or not when the thread awakes.  It is important to realize that the Process class actually takes a snapshot of Process state and caches it, and does not continually update the state information.  That is why there is a Refresh() method on this class, this should be called if you want updated Process state information.  Also, a better way to determine if the Window has come up is to use the WaitForInputIdle() method.   The fixed code looks something like this:

    Process myProc = new Process();    myProc.StartInfo.FileName = "notepad.exe";    myProc.Start();    myProc.WaitForInputIdle(5000);  //at this point we know the window is up                                    //we could handle the time-out based on                                    //the return value of WaitForInputIdle                                    //it’s omitted for clarity    myProc.Refresh();    IntPtr myWinHandle = myProc.MainWindowHandle;    Console.WriteLine(myWinHandle);

Issue 2: Changing the ServiceName property does not refresh DependentServices/ServicesDependedOn

This customer found that changing the service name didn’t update the DependentServices or ServicesDepended on.  A code snippet for the repro looked something like:

    ServiceController sc = new ServiceController("workstation");    Console.WriteLine("Workstation dependent services:");    foreach (ServiceController dependency in sc.DependentServices)        Console.WriteLine(dependency.ServiceName);    Console.WriteLine();    sc.ServiceName = "alerter";    Console.WriteLine("Alerter dependent services:");    foreach (ServiceController dependency in sc.DependentServices)        Console.WriteLine(dependency.ServiceName);

This would output:

    Workstation dependent services:    RpcLocator    Netlogon    Messenger    Browser    Alerter    Alerter dependent services:    RpcLocator    Netlogon    Messenger    Browser    Alerter

Like Process, ServiceProcess caches this information so Refresh() needs to be called.  In the documentation, we say that calling Refresh() sets the DependentServices and ServicesDependedOn properties to null.  Technically, this is true as in our internal implementation this is indeed what happens.  However, the next time the getter for either of these properties is called, a null check is performed and if either are null we will update them to their current values.  We’ve updated the documentation for the next release to make this clearer.  The fix is one line adding the call to Refresh():

    ServiceController sc = new ServiceController("workstation");    Console.WriteLine("Workstation dependent services:");    foreach (ServiceController dependency in sc.DependentServices)        Console.WriteLine(dependency.ServiceName);    Console.WriteLine();    sc.ServiceName = "alerter";    sc.Refresh();    Console.WriteLine("Alerter dependent services:");    foreach (ServiceController dependency in sc.DependentServices)        Console.WriteLine(dependency.ServiceName);