Toolbox

This chapter is excerpted from C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers by Jay Hilyard, Stephen Teilhet, published by O'Reilly Media

C# 3.0 Cookbook, Third Edition

Logo

Buy Now

Introduction

Every programmer has a certain set of routines that he refers back to and uses over and over again. These utility functions are usually bits of code that are not provided by any particular language or framework. This chapter is a compilation of utility routines that we have gathered during our time with C# and the .NET Framework. The type of things we share in this chapter are:

  • Determining the path for various locations in the operating system.

  • Interacting with services.

  • Inspecting the Global Assembly Cache.

  • Message queuing.

It is a grab bag of code that can help to solve a specific need while you are working on a larger set of functionality in your application.

Dealing with Operating System Shutdown, Power Management, or User Session Changes

Problem

You want to be notified whenever the operating system or a user has initiated an action that requires your application to shut down or be inactive (user logoff, remote session disconnect, system shutdown, hibernate/restore, etc.). This notification will allow you have your application respond gracefully to the changes.

Solution

Use the Microsoft.Win32.SystemEvents class to get notification of operating system, user session change, and power management events. The RegisterForSystemEvents method shown next hooks up the five event handlers necessary to capture these events and would be placed in the initialization section for your code:

 public static void RegisterForSystemEvents( )
    {
         // Always get the final notification when the event thread is shutting down
         // so we can unregister.
         SystemEvents.EventsThread Shutdown +=
              new EventHandler(OnEventsThread Shutdown);
         SystemEvents.PowerModeChanged +=
              new PowerModeChangedEventHandler(OnPowerModeChanged);
         SystemEvents.SessionSwitch +=
              new SessionSwitchEventHandler(OnSessionSwitch);
         SystemEvents.SessionEnding +=
              new SessionEndingEventHandler(OnSessionEnding);
         SystemEvents.SessionEnded +=
              new SessionEndedEventHandler(OnSessionEnded);
    }

The EventsThreadShutdown event notifies you of when the thread that is distributing the events from the SystemEvents class is shutting down so that you can unregister the events on the SystemEvents class if you have not already done so. The PowerModeChanged event triggers when the user suspends or resumes the system from a suspended state. The SessionSwitch event is triggered by a change in the logged-on user. The SessionEnding event is triggered when the user is trying to log off or shut down the system, and the SessionEnded event is triggered when the user is actually logging off or shutting down the system.

The events can be unregistered using the UnregisterFromSystemEvents method. UnregisterFromSystemEvents should be called from the termination code of your Windows Form, user control, or any other class that may come and go, as well as from one other area shown later in the recipe:

    private static void UnregisterFromSystemEvents( )
    {
        SystemEvents.EventsThreadShutdown -=
            new EventHandler(OnEventsThreadShutdown);
        SystemEvents.PowerModeChanged -=
            new PowerModeChangedEventHandler(OnPowerModeChanged);
        SystemEvents.SessionSwitch -=
            new SessionSwitchEventHandler(OnSessionSwitch);
        SystemEvents.SessionEnding -=
            new SessionEndingEventHandler(OnSessionEnding);
        SystemEvents.SessionEnded -=
            new SessionEndedEventHandler(OnSessionEnded);
    }

Tip

Since the events exposed by SystemEvents are static, if you are using them in a section of code that could be invoked multiple times (secondary Windows Form, user control, monitoring class, etc.), you must unregister your handlers, or you will cause memory leaks in the application.

The SystemEvents handler methods are the individual event handlers for each of the events that have been subscribed to in RegisterForSystemEvents. The first handler to cover is the OnEventsThreadShutdown handler. It is essential that your handlers are unregistered if this event fires, as the notification thread for the SystemEvents class is going away, and the class may be gone before your application is. If you haven't unregistered before that point, you will cause memory leaks, so add a call to UnregisterFromSystemEvents into this handler as shown here:

     private static void OnEventsThread Shutdown(object sender, EventArgs e)
        {
            Debug.WriteLine("System event thread is shutting down, no more notifications.
    ");

            // Unregister all our events as the notification thread is going away.
            UnregisterFromSystemEvents( );
        }

The next handler to explore is the OnPowerModeChanged method. This handler can report the type of power management event through the Mode property of the PowerModeEventChangedArgs parameter. The Mode property has the PowerMode enumeration type and specifies the event type through the enumeration value contained therein:

       private static void OnPowerModeChanged(object sender, PowerModeChangedEventArgs
    e)
        {
            // Power mode is changing.
            switch (e.Mode)
            {
                case PowerModes.Resume:
                    Debug.WriteLine("PowerMode: OS is resuming from suspended state");
                    break;
                case PowerModes.StatusChange:
                    Debug.WriteLine("PowerMode: There was a change relating to the power"
    +
                        " supply (weak battery, unplug, etc..)");
                    break;
                case PowerModes.Suspend:
                    Debug.WriteLine("PowerMode: OS is about to be suspended");
                    break;
            }
        }

The next three handlers all deal with operating system session states. They are OnSessionSwitch, OnSessionEnding, and OnSessionEnded. Handling all three of these events covers all of the operating system session state transitions that your application may need to worry about. In OnSessionEnding, there is a SessionEndingEventArgs parameter, which has a Cancel member. This Cancel member allows you to request that the session not end if set to false. Code for the three handlers is shown in Example 19-1, "OnSessionSwitch, OnSessionEnding, and OnSessionEnded handlers".

Example 19-1. OnSessionSwitch, OnSessionEnding, and OnSessionEnded handlers

private static void OnSessionSwitch(object sender, SessionSwitchEventArgs e)
{
    // Check reason.
    switch (e.Reason)
    {
        case SessionSwitchReason.ConsoleConnect:
            Debug.WriteLine("Session connected from the console");
            break;
        case SessionSwitchReason.ConsoleDisconnect:
            Debug.WriteLine("Session disconnected from the console");
            break;
        case SessionSwitchReason.RemoteConnect:
            Debug.WriteLine("Remote session connected");
            break;
        case SessionSwitchReason.RemoteDisconnect:
            Debug.WriteLine("Remote session disconnected");
            break;
        case SessionSwitchReason.SessionLock:
            Debug.WriteLine("Session has been locked");
            break;
        case SessionSwitchReason.SessionLogoff:
            Debug.WriteLine("User was logged off from a session");
            break;
        case SessionSwitchReason.SessionLogon:
            Debug.WriteLine("User has logged on to a session");
            break;
        case SessionSwitchReason.SessionRemoteControl:
            Debug.WriteLine("Session changed to or from remote status");
            break;
        case SessionSwitchReason.SessionUnlock:
            Debug.WriteLine("Session has been unlocked");
            break;
    }
}

private static void OnSessionEnding(object sender, SessionEndingEventArgs e)
{
    // True to cancel the user request to end the session, false otherwise
    e.Cancel = false;
    // Check reason.
    switch(e.Reason)
    {
        case SessionEndReasons.Logoff:
            Debug.WriteLine("Session ending as the user is logging off");
            break;
        case SessionEndReasons.System Shutdown:
            Debug.WriteLine("Session ending as the OS is shutting down");
            break;
    }
}

private static void On SessionEnded(object sender, SessionEnded EventArgs e)
{
    switch (e.Reason)
    {
        case SessionEndReasons.Logoff:
            Debug.WriteLine("Session ended as the user is logging off");
            break;
        case SessionEndReasons.System Shutdown:
            Debug.WriteLine("Session ended as the OS is shutting down");
            break;
    }
}

Discussion

The .NET Framework provides many opportunities to get feedback from the system when there are changes due to either user or system interactions. The SystemEvents class exposes more events than just the ones used in this recipe. For a full listing, see Table 19.1, "The SystemEvents events".

Table 19.1. The SystemEvents events

Value

Description

DisplaySettingsChanged

User changed display settings.

DisplaySettingsChanging

Display settings are changing.

EventsThreadShutdown

Thread listening for system events is terminating.

InstalledFontsChanged

User added or removed fonts.

PaletteChanged

User switched to an application with a different palette.

PowerModeChanged

User suspended or resumed the system.

SessionEnded

User shut down the system or logged off.

SessionEnding

User is attempting to shut down the system or log off.

SessionSwitch

The currently logged-in user changed.

TimeChanged

User changed system time.

TimerElapsed

A Windows timer interval expired.

UserPreferenceChanged

User changed a preference in the system.

UserPreferenceChanging

User is trying to change a preference in the system.

Tip

Keep in mind that these are system events. Therefore, the amount of work done in the handlers should be kept to a minimum, so the system can move on to the next task.

The notifications from SystemEvents come on a dedicated thread for raising these events. In a Windows Forms application, you will need to get back on to the correct user interface thread before updating a UI with any of this information, using one of the various methods for doing so (Control.BeginInvoke, Control.Invoke, BackgroundWorker).

See Also

The "SystemEvents Class," " PowerModeChangedEventArgs Class," "SessionEndedEventArgs Class," "SessionEndingEventArgs Class," "SessionSwitchEventArgs Class," "TimerElapsedEventArgs Class," "UserPreferenceChangingEventArgs Class," and "UserPreferenceChangedEventArgs Class" topics in the MSDN documentation.

Controlling a Service

Problem

You need to programmatically manipulate a service that your application interacts with.

Solution

Use the System.ServiceProcess.ServiceController class to control the service. ServiceController allows you to interact with an existing service and to read and change its properties. In the example, it will be used to manipulate the ASP.NET State Service. The name, the service type, and the display name are easily available from the ServiceName, ServiceType, and DisplayName properties:

  ServiceController scStateService = new ServiceController("COM+ Event System");
    Console.WriteLine("Service Name: " + scStateService.ServiceName);
    Console.WriteLine("Service Type: " + scStateService.ServiceType.ToString());
    Console.WriteLine("Display Name: " + scStateService.DisplayName);

The ServiceType enumeration has a number of values, as shown in Table 19.2, "The ServiceType enumeration values".

Table 19.2. The ServiceType enumeration values

Value

Description

Adapter

Service that serves a hardware device

FileSystemDriver

Driver for the filesystem (kernel level)

InteractiveProcess

Service that communicates with the desktop

KernelDriver

Low-level hardware device driver

RecognizerDriver

Driver for identifying filesystems on startup

Win32OwnProcess

Win32 program that runs as a service in its own process

Win32ShareProcess

Win32 program that runs as a service in a shared process such as SvcHost

One useful task is to determine a service's dependents. The services that depend on the current service are accessed through the DependentServices property, an array of ServiceController instances (one for each dependent service):

 foreach (ServiceController sc in scStateService.DependentServices)
    {
        Console.WriteLine(scStateService.DisplayName + " is depended on by: " +
                    sc.DisplayName);
    }

To see the services that the current service does depend on, the ServicesDependedOn array contains ServiceController instances for each of those:

   foreach (ServiceController sc in scStateService.ServicesDependedOn)
    {
        Console.WriteLine(scStateService.DisplayName + " depends on: " +
                    sc.DisplayName);
    }

One of the most important things about services is what state they are in. A service doesn't do much good if it is supposed to be running and it isn't-or worse yet, it is supposed to be disabled (perhaps as a security risk) and isn't. To find out the current status of the service, check the Status property. For this example, the original state of the service will be saved, so it can be restored later in the originalState variable:

  Console.WriteLine("Status: " + scStateService.Status);
    // Save original state.
    ServiceControllerStatus originalState = scStateService.Status;

If a service is stopped, it can be started with the Start method. First, check if the service is stopped, and then, once Start has been called on the ServiceController instance, the WaitForStatus method should be called to make sure that the service started. WaitForStatus can take a timeout value so that the application is not waiting forever for the service to start in the case of a problem:

    // If it is stopped, start it.
    TimeSpan serviceTimeout = TimeSpan.FromSeconds(60);
    if (scStateService.Status == ServiceControllerStatus.Stopped)
    {
        scStateService.Start();
        // Wait up to 60 seconds for start.
        scStateService.WaitForStatus(ServiceControllerStatus.Running,
    serviceTimeout);
        }
        Console.WriteLine("Status: " + scStateService.Status);

Services can also be paused. If the service is paused, the application needs to check if it can be continued by looking at the CanPauseAndContinue property. If so, the Continue method will get the service going again, and the WaitForStatus method should be called to wait until it does:

 // If it is paused, continue.
    if (scStateService.Status == ServiceControllerStatus.Paused)
    {
        if(scStateService.CanPauseAndContinue)
        {
            scStateService.Continue();
            // Wait up to 60 seconds for running.
            scStateService.WaitForStatus(ServiceControllerStatus.Running,
                                serviceTimeout);
        }
    }
    Console.WriteLine("Status: " + scStateService.Status);

    // Should be running at this point.

Determining if a service can be stopped is done through the CanStop property. If it can be stopped, then stopping it is a matter of calling the Stop method followed by WaitForStatus:

        // Can we stop it?
        if (scStateService.CanStop)
        {
            scStateService.Stop();
            // Wait up to 60 seconds for stop.
            scStateService.WaitForStatus(ServiceControllerStatus.Stopped,
    serviceTimeout);
        }
        Console.WriteLine("Status: " + scStateService.Status);

Now it is time to set the service back to how you found it. The originalState variable has the original state, and the switch statement holds actions for taking the service from the current stopped state to its original state:

 // Set it back to the original state.
    switch (originalState)
    {
        case ServiceControllerStatus.Stopped:
            if (scStateService.CanStop)
            {
                scStateService.Stop();
            }
            break; 
        case ServiceControllerStatus.Running:
            scStateService.Start();
            // Wait up to 60 seconds for stop.
            scStateService.WaitForStatus(ServiceControllerStatus.Running,
                            serviceTimeout);
            break;
        case ServiceControllerStatus.Paused:

            // If it was paused and is stopped, need to restart so we can pause.
            if (scStateService.Status == ServiceControllerStatus.Stopped)
            {
                scStateService.Start();
                // Wait up to 60 seconds for start.
                scStateService.WaitForStatus(ServiceControllerStatus.Running,
                                serviceTimeout);
            }
            // Now pause.
            if(scStateService.CanPauseAndContinue)
            {
               scStateService.Pause();
               // Wait up to 60 seconds for stop.
               scStateService.WaitForStatus(ServiceControllerStatus.Paused,
                               serviceTimeout);
        }
           break;
    }

In order to be sure that the Status property is correct on the service, the application should call Refresh to update it before testing the value of the Status property. Once the application is done with the service, call the Close method:

 scStateService.Refresh();
    Console.WriteLine("Status: " + scStateService.Status.ToString());

    // Close it.
    scStateService.Close();

Discussion

Services run many of the operating system functions today. They usually run under a system account (LocalSystem, NetworkService, LocalService) or a specific user account that has been granted specific permissions and rights. If your application uses a service, then this is a good way to determine if everything for the service to run is set up and configured properly before your application attempts to use it. Not all applications depend on services directly. But if your application does, or you have written a service as part of your application, it can be handy to have an easy way to check the status of your service and possibly correct the situation.

See Also

The "ServiceController Class" and "ServiceControllerStatus Enumeration" topics in the MSDN documentation.

List What Processes an Assembly Is Loaded In

Problem

You want to know what current processes have a given assembly loaded.

Solution

Use the GetProcessesAssemblyIsLoadedIn method that we've created for this purpose to return a list of processes that a given assembly is loaded in. GetProcessesAssemblyIsLoadedIn takes the filename of the assembly to look for (such as System.Data.dll), and then gets a list of the currently running processes on the machine by calling Process.GetProcesses. It then searches the processes to see if the assembly is loaded into any of them. When found in a process, that Process object is projected into an enumerable set of Process objects. The iterator for the set of processes found is returned from the query:

   public static IEnumerable<Process> GetProcessesAssemblyIsLoadedIn(
                                                        string assemblyFileName)
    {
        var processes = from process in Process.GetProcesses()
                        where process.ProcessName != "System" &
                              process.ProcessName != "Idle"
                        from ProcessModule processModule in process.Modules
                        where processModule.ModuleName.Equals(assemblyFileName,
                                             StringComparison.OrdinalIgnoreCase)
                        select process;
        return processes;
    }

Discussion

In some circumstances, such as when uninstalling software or debugging version conflicts, it is beneficial to know if an assembly is loaded into more than one process. By quickly getting a list of the Process objects that the assembly is loaded in, you can narrow the scope of your investigation.

The following code uses this routine:

 string searchAssm = "System.Data.dll";
    var processes = Toolbox.GetProcessesAssemblyIsLoadedIn(searchAssm);
    foreach (Process p in processes)
    {
        Console.WriteLine("Found {0} in {1}",searchAssm, p.MainModule.ModuleName);
    }

The preceding code might produce output like this (you may see more if you have other applications running):

    Found System.Data.dll in WebDev.WebServer.EXE
    Found System.Data.dll in devenv.exe
    Found System.Data.dll in CSharpRecipes.vshost.exe

Since this is a diagnostic function, you will need FullTrust security access to use this method.

Note that in the query, the System and Idle processes are avoided for inspection by the query:

   var processes = from process in Process.GetProcesses()
                    where process.ProcessName != "System" &
                          process.ProcessName != "Idle"
                    from ProcessModule processModule in process.Modules

This is due to the Modules collection throwing a Win32Exception as those processes are not able to be examined using the Modules collection on the process.

See Also

The "Process Class," "ProcessModule Class," and "GetProcesses Method" topics in the MSDN documentation.

Using Message Queues on a Local Workstation

Problem

You need a way to disconnect two components of your application (such as a web service endpoint and processing logic) so that the first component has to worry about only formatting the instructions, and the bulk of the processing occurs in the second component.

Solution

Use the MQWorker class shown here in both the first and second components to write and read messages to and from a message queue. MQWorker uses the local message-queuing services to do this. The queue pathname is supplied in the constructor, and the existence of the queue is checked in the SetUpQueue method:

    class MQWorker : IDisposable
    {
        private bool _disposed;
        private string _mqPathName;
        MessageQueue _queue;

        public MQWorker(string queuePathName)
        {
            if (string.IsNullOrEmpty(queuePathName)
                throw new ArgumentNullException("queuePathName");

            _mqPathName = queuePathName;

            SetUpQueue();
    }

SetUpQueue creates a message queue of the supplied name using the MessageQueue class if none exists. It accounts for the scenario in which the message-queuing services are running on a workstation computer. In that situation, it makes the queue private, as that is the only type of queue allowed on a workstation:

   private void SetUpQueue()
    {
        // See if the queue exists; create it if not.
        if (!MessageQueue.Exists(_mqPathName))
        {
            try
            {
                _queue = MessageQueue.Create(_mqPathName);
            }
            catch (MessageQueueException mqex)
            {
                // See if we are running on a workgroup computer.
                if (mqex.MessageQueueErrorCode ==
                   MessageQueueErrorCode.UnsupportedOperation)
                {
                   string origPath = _mqPathName;
                   // Must be a private queue in workstation mode.
                   int index = _mqPathName.ToLower().IndexOf("private$");
                   if (index == -1)
                   {
                       // Get the first \.
                       index = _mqPathName.IndexOf(@"\");
                       // Insert private$\ after server entry.
                       _mqPathName = _mqPathName.Insert(index + 1, @"private$\");

                       if (!MessageQueue.Exists(_mqPathName))
                           _queue = MessageQueue.Create(_mqPathName);
                       else
                           _queue = new MessageQueue(_mqPathName);
                   }
                }
            }
        }
         else
         {
             _queue = new MessageQueue(_mqPathName);
         }
    }

The SendMessage method sends a message to the queue to set up in the constructor. The body of the message is supplied in the body parameter, and then an instance of System.Messaging.Message is created and populated. The BinaryMessageFormatter is used to format the message, as it enables larger volumes of messages to be sent with fewer resources than does the default XmlMessageFormatter. Messages are set to be persistent by setting the Recoverable property to true. Finally, the Body is set, and the message is sent:

 public void SendMessage(string label, string body)
    {
        if (_queue != null)
        {
            Message msg = new Message();
            // Label our message.
            msg.Label = label;

            // Override the default XML formatting with binary
            // as it is faster (at the expense of legibility while debugging).
            msg.Formatter = new BinaryMessageFormatter();
            // Make this message persist (causes message to be written
            // to disk).
            msg.Recoverable = true;
            msg.Body = body;
            _queue.Send(msg);
        }
    }

The ReadMessage method reads messages from the queue set up in the constructor by creating a Message object and calling its Receive method. The message formatter is set to the BinaryMessageFormatter for the Message, since that is how we write to the queue. Finally, the body of the message is returned from the method:

      public string ReadMessage()
        {
         Message msg = null;
         msg = _queue.Receive();
         msg.Formatter = new BinaryMessageFormatter();
         return (string)msg.Body;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!this._disposed)
            {
                if (disposing)
                    _queue.Dispose();

                _disposed = true;
            }
        }
    }

To show how the MQWorker class is used, the following example creates an MQWorker. It then sends a message (a small blob of XML) using SendMessage and then retrieves it using ReadMessage:

 // NOTE: Message Queue services must be set up for this to work.
    // This can be added in Add/Remove Windows Components.

    // This is the right syntax for workstation queues.
    //MQWorker mqw = new MQWorker(@".\private$\MQWorkerQ");
    using (MQWorker mqw = new MQWorker(@".\MQWorkerQ"))
    {
        string xml = "<MyXml><InnerXml location=\"inside\"/></MyXml>";
        Console.WriteLine("Sending message to message queue: " + xml);
        mqw.SendMessage("Label for message", xml);
        string retXml = mqw.ReadMessage();
        Console.WriteLine("Read message from message queue: " + retXml);
    }

Discussion

Message queues are very useful when you are attempting to distribute the processing load for scalability purposes. Without question, using a message queue adds overhead to the processing; as the messages must travel through the infrastructure of MSMQ, overhead would not incur without it. One benefit is that MSMQ allows your application to spread out across multiple machines, so there can be a net gain in production. Another advantage is that this supports reliable asynchronous handling of the messages so that the sending side can be confident that the receiving side will get the message without the sender having to wait for confirmation. The Message Queue services are not installed by default but can be installed through the Add/ Remove Windows Components applet in Control Panel. Using a message queue to buffer your processing logic from high volumes of requests (such as in the web service scenario presented earlier) can lead to more stability and ultimately can produce more throughput for your application through using multiple reader processes on multiple machines.

See Also

The "Message Class" and "MessageQueue Class" topics in the MSDN documentation.

Finding the Path to the Current Framework Version

Problem

You need the path to where the version of the .NET Framework you are running on is located.

Solution

Use the GetRuntimeDirectoryRuntimeDirectory method (implemented in System. Runtime.InteropServices.RuntimeEnvironment) to return the full path to the folder that the current version of .NET is installed in:

   public static string GetCurrentFrameworkPath()
    {
         return
    System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
    }

Discussion

There are many reasons why you might want to know the current framework path, including:

  • Manually loading the configuration files in the config directory to check settings.

  • Dynamically adding references for system components in a code generator.

The list could go on and on. Since the method to get to the path is pretty far down a namespace chain (System.Runtime.InteropServices.RuntimeEnvironment), it is provided for your programming convenience.

See Also

The "Version Class" and "Version.ToString Method" topics in the MSDN documentation.

Determining the Versions of an Assembly That Are Registered in the Global Assembly Cache (GAC)

Problem

You need to determine all of the versions of an assembly that are currently installed in the GAC.

Solution

Use the PrintGacRegisteredVersions method (implemented here) to display all of the versions (both native and managed) of an assembly in the GAC. In order to be complete, the code looks for .dll, .exe, and the native versions of .dll and // .exe files in the Global Assembly Cache:

      public static void PrintGacRegisteredVersions(string assemblyFileName)
        {

            Console.WriteLine("Searching for GAC Entries for {0}\r\n", assemblyFileName);
            // Get the filename without the extension as that is the subdirectory
            // name in the GAC where it would be registered.
            string assemblyFileNameNoExt = Path.
    GetFileNameWithoutExtension(assemblyFileName);

            // Need to look for both the native images as well as "regular" .dlls and . 
    exes.
            string searchDLL = assemblyFileNameNoExt + ".dll";
            string searchEXE = assemblyFileNameNoExt + ".exe";
            string searchNIDLL = assemblyFileNameNoExt + ".ni.dll";
            string searchNIEXE = assemblyFileNameNoExt + ".ni.exe";

The Directory.GetFiles method is used in a LINQ query to determine if any of those versions are present in the GAC, which is located in the [Windows]\ASSEMBLY folder.

Tip

The ASSEMBLY folder is not visible through Windows Explorer, as the GAC shell extension gets in the way. But if you run a Command Prompt window, you can maneuver to the [Windows]\ASSEMBLY folder and see how things are stored in the GAC.

 // Query the GAC
    var files = from file in Directory.GetFiles(gacPath, "*", SearchOption.
    AllDirectories)
                let fileInfo = new FileInfo(file)
                where fileInfo.Name == searchDLL ||
                      fileInfo.Name == searchEXE ||
                      fileInfo.Name == searchNIDLL ||
                      fileInfo.Name == searchNIEXE
                select fileInfo.FullName;

Now that you have a master list of the versions of this file in the GAC, you display the information for each individual item by examining the FileVersionInfo and writing it out to the console:

    foreach (string file in files)
    {
            // Grab the version info and print.
            FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(file);
            if (file.IndexOf("NativeImage",StringComparison.OrdinalIgnoreCase) != -1)
            {
                Console.WriteLine("Found {0} in the GAC under {1} as a native image", 
                    assemblyFileNameNoExt, Path.GetDirectoryName(file));
            }
            else
            {
                Console.WriteLine("Found {0} in the GAC under {1} with version " +
                        "information:\r\n{2}",
                              assemblyFileNameNoExt, Path.GetDirectoryName(file),
                      fileVersion.ToString());
            }
        }
    }

The output from this when looking for mscorlib looks like this:

    Searching for GAC Entries for mscorlib

    Found mscorlib in the GAC under C:\WINDOWS\ASSEMBLY\NativeImages_v2.0.50727_32\m
    scorlib\9a485a2c7533b6601064c8e660bb8a5d as a native image
    Found mscorlib in the GAC under C:\WINDOWS\ASSEMBLY\NativeImages1_v1.1.4322\msco
    rlib\1.0.5000.0__b77a5c561934e089_c6f4d3b7 as a native image
    Found mscorlib in the GAC under C:\WINDOWS\ASSEMBLY\NativeImages1_v1.1.4322\msco
    rlib\1.0.5000.0__b77a5c561934e089_65ce95c7 as a native image
    Found mscorlib in the GAC under C:\WINDOWS\ASSEMBLY\GAC_32\mscorlib\2.0.0.0__b77
    a5c561934e089 with version information:
    File:             C:\WINDOWS\ASSEMBLY\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\
    mscorlib.dll
    InternalName:     mscorlib.dll
    OriginalFilename: mscorlib.dll
    FileVersion:      2.0.50727.1378 (REDBITSB2.050727-1300)
    FileDescription:  Microsoft Common Language Runtime Class Library
    Product:          Microsoftr .NET Framework
    ProductVersion:   2.0.50727.1378
    Debug:            False
    Patched:          False
    PreRelease:       False
    PrivateBuild:     False
    SpecialBuild:     False
    Language:         English (United States)

    Searching for GAC Entries for System.Web.dll

    Found System.Web in the GAC under C:\WINDOWS\ASSEMBLY\NativeImages_v2.0.50727_32
    \System.Web\48209ad55221a8f04c621153965925e4 as a native image
    Found System.Web in the GAC under C:\WINDOWS\ASSEMBLY\GAC_32\System.Web\2.0.0.0_
    _b03f5f7f11d50a3a with version information:
    File:             C:\WINDOWS\ASSEMBLY\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3
    a\System.Web.dll
    InternalName:     System.Web.dll
    OriginalFilename: System.Web.dll
    FileVersion:      2.0.50727.1378 (REDBITSB2.050727-1300)
    FileDescription:  System.Web.dll
    Product:          Microsoftr .NET Framework
    ProductVersion:   2.0.50727.1378
    Debug:            False
    Patched:          False
    PreRelease:       False
    PrivateBuild:     False
    SpecialBuild:     False
    Language:         English (United States)

    Found System.Web in the GAC under C:\WINDOWS\ASSEMBLY\GAC\System.Web\1.0.5000.0_
    _b03f5f7f11d50a3a with version information:
    File:             C:\WINDOWS\ASSEMBLY\GAC\System.Web\1.0.5000.0__b03f5f7f11d50a3
    a\System.Web.dll
    InternalName:     System.Web.dll
    OriginalFilename: System.Web.dll
    FileVersion:      1.1.4322.2037
    FileDescription:  System.Web.dll
    Product:          Microsoft (R) .NET Framework 
    ProductVersion:   1.1.4322.2037
    Debug:            False
    Patched:          False
    PreRelease:       False
    PrivateBuild:     False
    SpecialBuild:     False 
    Language:         English (United States)

Discussion

The ability to have multiple versions of assemblies on a machine and having absolute binding mechanisms to the specific version of an assembly, were proclaimed as the cure to .dll hell. .dll hell was the case in which two applications linked to a .dll of the same name in a common folder (such as System32), but each application needed a different version of the .dll. Problems occurred when you attempted to run one application or the other, depending upon which version was present. With assemblies and the GAC, this scenario occurs only when the application is improperly configured by allowing it to use newer versions of an assembly automatically or via publisher policy issues. Perhaps things are better now. In any case, they are different, and the starting point for debugging assembly loads is to figure out what is on the system. This can be helped by looking at the Assembly Binding Log Viewer (FUS-LOGVW.exe). But having a way to just see what is on the system with a particular filename and what versions are included can be a very useful thing.

See Also

The "Directory Class," "ArrayList Class," and "FileVersionInfo Class" topics in the MSDN documentation.

Capturing Output from the Standard Output Stream

Problem

You want to capture output that is going to the standard output stream from within your C# program.

Solution

Use the Console.SetOut method to capture and release the standard output stream. SetOut sets the standard output stream to whatever System.IO.TextWriter-based stream it is handed. To capture the output to a file, create a StreamWriter to write to it, and set that writer using SetOut. Now when Console.WriteLine is called, the out-put goes to the StreamWriter, not to stdout, as shown here:

   try
    {
        Console.WriteLine("Stealing standard output!");
        using (StreamWriter writer = new StreamWriter(@"c:\log.txt"))
        {
            // Steal stdout for our own purposes...
            Console.SetOut(writer);

            Console.WriteLine("Writing to the console... NOT!");

            for (int i = 0; i < 10; i+)
                Console.WriteLine(i);
        }
    }
    catch(IOException e)
    {
        Debug.WriteLine(e.ToString());
        return ;
    }

To restore writing to the standard output stream, create another StreamWriter. This time, call the Console.OpenStandardOutput method to acquire the standard output stream and use SetOut to set it once again. Now calls to Console.WriteLine appear on the console again:

 // Recover the standard output stream so that a
    // completion message can be displayed.
    using (StreamWriter standardOutput =
              new StreamWriter(Console.OpenStandardOutput()))
    {
              standardOutput.AutoFlush = true;
              Console.SetOut(standardOutput);
              Console.WriteLine("Back to standard output!");
    }

The console output from this code looks like this:

  Stealing standard output!
    Back to standard output!

log.txt contains the following after the code is executed:

  Writing to the console... NOT!
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

Discussion

Redirecting the standard output stream inside of the program may seem a bit antiquated. But consider the situation when you're using another class that writes information to this stream. You don't want the output to appear in your application, but you have to use the class. This could also be useful if you create a small launcher application to capture output from a console application.

See Also

The "Console.SetOut Method," "Console.OpenStandardOutput Method," and "StreamWriter Class" topics in the MSDN documentation.

Running Code in Its Own AppDomain

Problem

You want to run code isolated from the main part of your application.

Solution

Create a separate AppDomain to run the code using the AppDomain.CreateDomain method. CreateDomain allows the application to control many aspects of the AppDomain being created like the security environment, the AppDomain settings, and base paths for the AppDomain. To demonstrate this, the code creates an instance of the RunMe class (shown in full later in this recipe) and calls the PrintCurrentAppDomainName method. This prints the name of the AppDomain where the code is running:

    public static void RunCodeInNewAppDomain()
    {
        AppDomain myOwnAppDomain = AppDomain.CreateDomain("MyOwnAppDomain");
        // Print out our current AppDomain name.
        RunMe rm = new RunMe();
        rm.PrintCurrentAppDomainName();

Now, you create an instance of the RunMe class in the "MyOwnAppDomain" AppDomain by calling CreateInstance on the AppDomain. We pass CreateInstance the module and type information necessary for constructing the type, and it returns an ObjectHandle.

We can then retrieve a proxy to the instance running in the AppDomain by taking the returned ObjectHandle and casting it to a RunMe reference using the Unwrap method:

 // Create our RunMe class in the new AppDomain.
    Type adType = typeof(RunMe);
    ObjectHandle objHdl =
        myOwnAppDomain.CreateInstance(adType.Module.Assembly.FullName, 
                        adType.FullName);

    // Unwrap the reference.
    RunMe adRunMe = (RunMe)objHdl.Unwrap();

The PrintCurrentAppDomainName method is called on the RunMe instance in the "MyOwnAppDomain" AppDomain, and it prints out "Hello from MyOwnAppDomain!". The AppDomain is unloaded using AppDomain.Unload and the program terminates:

      // Make a call on the toolbox.
        adRunMe.PrintCurrentAppDomainName();

        // Now unload the AppDomain.
        AppDomain.Unload(myOwnAppDomain);
    }

The RunMe class is defined here. It inherits from MarshalByRefObject, as that allows you to retrieve the proxy reference when you call Unwrap on the ObjectHandle and have the calls on the class remoted into the new AppDomain. The PrintCurrentApp-DomainName method simply accesses the FriendlyName property on the current AppDomain and prints out the "Hello from {AppDomain}!" message:

   public class RunMe : MarshalByRefObject
    {
        public RunMe()
        {
            PrintCurrentAppDomainName();
        }

        public void PrintCurrentAppDomainName()
        {
            string name = AppDomain.CurrentDomain.FriendlyName;
            Console.WriteLine("Hello from {0}!", name);
        }
    }

The output from this example is shown here:

 Hello from CSharpRecipes.vshost.exe!
    Hello from CSharpRecipes.vshost.exe!
    Hello from MyOwnAppDomain!
    Hello from MyOwnAppDomain!

Discussion

Isolating code in a separate AppDomain is overkill for something as trivial as this example, but it demonstrates that code can be executed remotely in an AppDomain created by your application. There are six overloads for the CreateDomain method, and each adds a bit more complexity to the AppDomain creation. In situations in which the isolation or configuration benefits outweigh the complexities of not only setting up a separate AppDomain but debugging code in it as well, it is a useful tool. A good realworld example is hosting a separate AppDomain to run ASP.NET pages outside of the normal ASP.NET environment, though this is truly a nontrivial usage.

See Also

The "AppDomain Class," "AppDomain.CreateDomain Method," and "ObjectHandle Class" topics in the MSDN documentation.

Determining the Operating System and Service Pack Version of the Current Operating System

Problem

You want to know the current operating system and service pack.

Solution

Use the GetOSAndServicePack method shown in Example 19-2, "GetOSAndServicePack method" to get a string representing the current operating system and service pack. GetOSAndServicePack uses the Environment.OSVersion property to get the version information for the operating system and then determines the "official" name of the OS from that. The OperatingSystem class retrieved from Environment.OSVersion has a property for the service pack called ServicePack. The two strings are then merged together and returned as the OS and service pack string.

Example 19-2. GetOSAndServicePack method

public static string GetOSAndServicePack()
{
    // Get the current OS info
    OperatingSystem os = Environment.OSVersion;
    string osText = string.Empty;
    // if version is 5, then it is Win2K, XP, or 2003
    switch (os.Version.Major)
    {
        case 5:
            switch (os.Version.Minor)
            {
                case 0: osText = "Windows 2000";
                    break;
                case 1: osText = "Windows XP";
                    break;
                case 2: osText = "Windows Server 2003";
                    break; 
                default: osText = os.ToString();
                    break;
            }
            break;
        case 6:
            switch (os.Version.Minor)
            {
                case 0: osText = "Windows Vista";
                    break;
                case 1: osText = "Windows Server 2008";
                    break;
                default: osText = os.ToString();
                    break;
            }
            break;
    }
    if (!string.IsNullOrEmpty(osText))
    {
        // get the text for the service pack
        string spText = os.ServicePack;
        // build the whole string
        return string.Format("{0} {1}", osText, spText);
    }
    // Unknown OS so return version
    return os.VersionString;
}

Discussion

Enabling your application to know the current operating system and service pack allows you to include that information in debugging reports and in the about box (if you have one) for your application. The simple knowledge of the correct operating system and service pack transmitted through your support department can save you hours in debugging time. It is well worth making available, so your support department can easily direct your clients to it in case they cannot otherwise locate it.

See Also

The "Environment.OSVersion Property" and "OperatingSystem Class" topics in the MSDN documentation.