Chapter 13. Toolbox

13.0 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 types of things we share in this chapter are:

  • Power management events

  • 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.

13.1 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 to 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 should 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.EventsThreadShutdown +=
        new EventHandler(OnEventsThreadShutdown);
    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.

You can unregister the events 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);
}
Note

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 event that has 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 OnEventsThreadShutdown(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 it is set to false. Code for the three handlers is shown in Example 13-1.

Example 13-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.SystemShutdown:
            Debug.WriteLine("Session ending as the OS is shutting down");
            break;
    }
}

private static void OnSessionEnded(object sender, SessionEndedEventArgs e)
{
    switch (e.Reason)
    {
        case SessionEndReasons.Logoff:
            Debug.WriteLine("Session ended as the user is logging off");
            break;
        case SessionEndReasons.SystemShutdown:
            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 user or system interactions. The SystemEvents class exposes more events than just the ones used in this recipe. For a full listing, see Table 13-1.

Table 13-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.
Note

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 UI application, you will need to get back onto 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, or BackgroundWorker).

Note that .NET Core (the open source version of .NET for cross-platform coding) does not include a Microsoft.Win32.SystemEvents class at the time of this writing, so this recipe will not work on .NET Core (until someone adds it!).

See Also

The “SystemEvents Class,” “PowerModeChangedEventArgs Class,” “SessionEndedEventArgs Class,” “SessionEndingEventArgs Class,” and “SessionSwitchEventArgs Class” topics in the MSDN documentation.

13.2 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 Type: {scStateService.ServiceType.ToString()}");
Console.WriteLine($"Service Name: {scStateService.ServiceName}");
Console.WriteLine($"Display Name: {scStateService.DisplayName}");

The ServiceType enumeration has a number of values, as shown in Table 13-2.

Table 13-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}");

By contrast, the ServicesDependedOn array contains ServiceController instances for each of the services the current service depends on:

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, if 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;

Now that we have set up the proper access, we can start to work with the service methods. 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, call the WaitForStatus method 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:

TimeSpan serviceTimeout = TimeSpan.FromSeconds(60);
// if it is stopped, start it
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 determine if it can be continued by checking 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 start
        scStateService.WaitForStatus(ServiceControllerStatus.Running, 
            serviceTimeout);
    }
}
Console.WriteLine($"Status: {scStateService.Status}");

// Should be running at this point.

To determine if a service can be stopped, you use 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}");

Even though CanStop could have returned true, if we are not running under an administrative context, we would have gotten this exception when trying to stop the service:

A first chance exception of type 'System.InvalidOperationException' occurred in
System.ServiceProcess.dll
Additional information: Cannot open EventSystem service on computer '.'.

See the Discussion section for how to set up proper security access for the code.

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 start
        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 paused
            scStateService.WaitForStatus(ServiceControllerStatus.Paused,
                serviceTimeout);
        }
        break;
}

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, 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.

When you are manipulating services, the question of access comes into play. While in earlier Microsoft operating systems (pre–Windows 7) you could call the ServiceController APIs without any special privileges, with the introduction of User Account Control you now have to be in an administrative context to access methods that affect the service operation. You can still inspect the properties of the service without this level of access, but if you want to Start, Stop, and so on, you need to have elevated privileges.

To accomplish this in code, you would add an app.manifest file to your application by right-clicking the project and selecting Add*New Item and selecting the Application Manifest File, as shown in Figure 13-1.

The Application Manifest File creation window
Figure 13-1. The Application Manifest File creation window

In the asmv1:assembly rustinfosecurity equestedPrivileges section of the file the default requested execution level is to run as the person invoking the code:

<requestedExecutionLevel level="asInvoker" uiAccess="false" />

To allow access to the service methods, we will change this so our code requires an administrative context by setting the level attribute to requireAdministrator:

<!-- Necessary for service interaction in Recipe 13.2 Controlling a Service -->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>

This will ensure that when the code is run, it requires the user to have enough rights to perform the actions we are requesting.

See Also

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

13.3 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 contain a given assembly. GetProcessesAssemblyIsLoadedIn takes the filename of the assembly to look for (such as mscoree.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)
{
    // System and Idle are not actually processes, so there are no modules
    // associated and we skip them.

    var processes = from process in Process.GetProcesses()
                    where process.ProcessName != "System" &&
                            process.ProcessName != "Idle"
                    from ProcessModule processModule in process.SafeGetModules()
                    where processModule.ModuleName.Equals(assemblyFileName,
                         StringComparison.OrdinalIgnoreCase)
                    select process;
    return processes;
}

The Process.GetSafeModules extension method gets a list of the modules that the caller is authorized to see for the process. If we just accessed the Modules property directly, we would get a series of different access errors depending on the caller’s security context:

public static ProcessModuleCollection SafeGetModules(this Process process)
{
    List<ProcessModule> listModules = new List<ProcessModule>();
    ProcessModuleCollection modules =
             new ProcessModuleCollection(listModules.ToArray());
    try
    {
        modules = process.Modules;
    }
    catch (InvalidOperationException) { }
    catch (PlatformNotSupportedException) { }
    catch (NotSupportedException) { }
    catch (Win32Exception wex)
    {
        Console.WriteLine($"Couldn't get modules for {process.ProcessName}: " +
                            $"{wex.Message}");
    }
    // return either the modules or an empty collection
    return modules;
}

Discussion

In some circumstances, such as when you are 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 to look for .NET 4 processes:

string searchAssm = "mscoree.dll";
var processes = GetProcessesAssemblyIsLoadedIn(searchAssm);
foreach (Process p in processes)
    Console.WriteLine($"Found {searchAssm} in {p.MainModule.ModuleName}");

When you’re running the GetProcessesAssemblyIsLoadedIn method, the user’s security context plays a large role in how much the code can discover. If the caller is a normal Windows user not running in the administrative context (which must be entered into explicitly), you would see a number of processes reported that cannot have their modules examined, as shown in Example 13-2.

Example 13-2. Normal user security context output example
Couldn't get modules for dasHost: Access is denied
Couldn't get modules for WUDFHost: Access is denied
Couldn't get modules for StandardCollector.Service: Access is denied
Couldn't get modules for winlogon: Access is denied
Couldn't get modules for svchost: Access is denied
Couldn't get modules for FcsSas: Access is denied
Couldn't get modules for VBCSCompiler: Access is denied
Couldn't get modules for svchost: Access is denied
Couldn't get modules for coherence: Access is denied
Couldn't get modules for coherence: Access is denied
Couldn't get modules for svchost: Access is denied
Couldn't get modules for MOMService: Access is denied
Couldn't get modules for svchost: Access is denied
Couldn't get modules for csrss: Access is denied
Couldn't get modules for svchost: Access is denied
Couldn't get modules for vmms: Access is denied
Couldn't get modules for dwm: Access is denied
Found mscoree.dll in Microsoft.VsHub.Server.HttpHostx64.exe
Couldn't get modules for wininit: Access is denied
Couldn't get modules for svchost: Access is denied
Couldn't get modules for prl_tools: Access is denied
Couldn't get modules for coherence: Access is denied
Couldn't get modules for MpCmdRun: Access is denied
Couldn't get modules for svchost: Access is denied
Couldn't get modules for svchost: Access is denied
Couldn't get modules for audiodg: Access is denied
Couldn't get modules for mqsvc: Access is denied
Couldn't get modules for WmiApSrv: Access is denied
Couldn't get modules for conhost: Access is denied
Couldn't get modules for sqlwriter: Access is denied
Couldn't get modules for svchost: Access is denied
Couldn't get modules for svchost: Access is denied
Found mscoree.dll in CSharpRecipes.exe
Couldn't get modules for WmiPrvSE: Access is denied
Couldn't get modules for spoolsv: Access is denied
Couldn't get modules for svchost: Access is denied
Couldn't get modules for WmiPrvSE: Access is denied
Couldn't get modules for svchost: Access is denied
Found mscoree.dll in msvsmon.exe
Couldn't get modules for csrss: Access is denied
Couldn't get modules for dllhost: Access is denied
Couldn't get modules for svchost: Access is denied
Couldn't get modules for SearchIndexer: Access is denied
Couldn't get modules for WmiPrvSE: Access is denied
Found mscoree.dll in VBCSCompiler.exe
Couldn't get modules for svchost: Access is denied
Couldn't get modules for OSPPSVC: Access is denied
Couldn't get modules for WmiPrvSE: Access is denied
Couldn't get modules for smss: Access is denied
Couldn't get modules for IpOverUsbSvc: Access is denied
Couldn't get modules for lsass: Access is denied
Couldn't get modules for services: Access is denied
Couldn't get modules for MsMpEng: Access is denied
Couldn't get modules for msdtc: Access is denied
Couldn't get modules for prl_tools_service: Access is denied
Couldn't get modules for inetinfo: Access is denied
Couldn't get modules for sppsvc: Access is denied

When we run the same call to the GetProcessesAssemblyIsLoadedIn method under an administrative context, we get output similar to Example 13-3.

Example 13-3. Administrative user security context output example
Found mscoree.dll in VBCSCompiler.exe
Found mscoree.dll in Microsoft.VsHub.Server.HttpHostx64.exe
Found mscoree.dll in msvsmon.exe
Found mscoree.dll in VBCSCompiler.exe
Couldn't get modules for audiodg: Access is denied
Found mscoree.dll in ElevatedPrivilegeActions.vshost.exe
Couldn't get modules for sppsvc: Access is denied

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

Note that the query skips inspection for the System and Idle processes:

var processes = from process in Process.GetProcesses()
                where process.ProcessName != "System" &&
                        process.ProcessName != "Idle"
                from ProcessModule processModule in process.SafeGetModules()
                where processModule.ModuleName.Equals(assemblyFileName,
                     StringComparison.OrdinalIgnoreCase)
                select process;

The Modules collection can’t be used to examine these two processes, so it throws a Win32Exception. There are two other processes you might see access denied for: audiodg and sppsvc:

  • audiodg is a DRM-protected process used to host audio drivers so that they can be run in login sessions isolated from locally logged-in users.

  • sppsvc is a Microsoft software protection platform service that can be used to prevent the use of software without a license.

Since both of those services are sensitive in the operating system, you can see why they would not be accessible through Process enumeration.

See Also

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

13.4 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 the processing logic) such that the first component only has to worry about formatting the instructions. The bulk of the processing can then occur in the second component.

Solution

Use message queues to separate this work, and use the MQWorker class shown here in both the first and second components to write and read messages to and from the associated message queue.

Note

Message queues provide an asynchronous communications protocol between parties, meaning that the sender and receiver of the message do not need to interact with the message queue at the same time. Messages placed onto the queue are stored until the recipient retrieves them. They allow you to ensure messages don’t get lost, keep the work segmented, handle inconsistent loads, and scale your application by having multiple workers that can read from a queue.

MQWorker uses the local message-queuing services to store and retrieve messages. 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(nameof(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.ToLowerInvariant().
                                IndexOf("private$", 
                                    StringComparison.OrdinalIgnoreCase);
                if (index == -1)
                {
                    // get the first 
                    index = _mqPathName.IndexOf(@"",
                                StringComparison.OrdinalIgnoreCase);
                    // insert private$ after server entry
                    _mqPathName = _mqPathName.Insert(index + 1, @"private$");
                    // try try again
                    try
                    {
                        if (!MessageQueue.Exists(_mqPathName))
                            _queue = MessageQueue.Create(_mqPathName);
                        else
                            _queue = new MessageQueue(_mqPathName);
                    }
                    catch (Exception)
                    {
                        // set original as inner exception
                        throw new Exception(
                            $"Failed to create message queue with {origPath}" +
                            $" or {_mqPathName}", mqex);
                    }
                }
            }
        }
    }
    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. To make messages persistent (so that they stick around until they are processed and are not lost if the machine loses power), it sets the Recoverable property to true. Finally, the Body is set, and the message is sent:

public void SendMessage(string label, string body)
{
    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;
    }


    #region IDisposable Members

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

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

            _disposed = true;
        }
    }
    #endregion
}

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 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.

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 because the messages must travel through the infrastructure of MSMQ. One benefit, however, is that MSMQ allows your application to spread out across multiple machines, so there can be a net gain in processing. Another advantage is that message queuing 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 produce more throughput for your application by using multiple reader processes on multiple machines.

See Also

The “Message Class” and “MessageQueue Class” topics in the MSDN documentation.

13.5 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. We use Path.GetTempFileName to get a location to write our log to that is accessible by the identity calling the code.

Now when Console.WriteLine is called, the output goes to the StreamWriter, not to stdout, as shown here:

try
{
    Console.WriteLine("Stealing standard output!");
    string logfile = Path.GetTempFileName();
    Console.WriteLine($"Logging to: {logfile}");
    using (StreamWriter writer = new StreamWriter(logfile))
    {
        // 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.
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 similar to this:

Stealing standard output!
Logging to: C:UsersuserAppDataLocalTemp	mpFE7C.tmp
Back to standard output!

The logfile we created 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 or if you are using a third-party assembly that insists on outputting lots of verbose messages that would be confusing to your user.

See Also

The “Console.SetOut Method,” “Console.OpenStandardOutput Method,” “Path.GetTempFilePath Method,” and “StreamWriter Class” topics in the MSDN documentation.

13.6 Capturing Standard Output for a Process

Problem

You need to be able to capture standard output for a process you are launching.

Solution

Use the RedirectStandardOutput property of the Process.StartInfo class to capture the output from the process. By redirecting the standard output stream of the process, you read it when the process terminates. UseShellExecute is a property on the ProcessInfo class that tells the runtime whether or not to use the Windows shell to start the process. By default, it is turned on (true) and the shell runs the program, which means that the output cannot be redirected. UseShellExecute needs to be turned off (set to false) so the redirection can occur.

In this example, a Process object for cmd.exe is set up with arguments to perform a directory listing, and then the output is redirected. A logfile is created to hold the resulting output, and the Process.Start method is called:

Process application = new Process();
// run the command shell
application.StartInfo.FileName = @"cmd.exe";

// get a directory listing from the current directory
application.StartInfo.Arguments = @"/Cdir " + Environment.CurrentDirectory;
Console.WriteLine($"Running cmd.exe with arguments:" +
    $"{application.StartInfo.Arguments}");

// redirect standard output so we can read it
application.StartInfo.RedirectStandardOutput = true;
application.StartInfo.UseShellExecute = false;

// Create a log file to hold the results in the current EXE directory
string logfile = Path.GetTempFileName();
Console.WriteLine($"Logging to: {logfile}");
using (StreamWriter logger = new StreamWriter(logfile))
{
    // start it up
    application.Start();

Once the process is started, the StandardOutput stream can be accessed and a reference to it held. Once the application finishes, the code then reads in the information from the output stream that was written while the application ran and writes it to the logfile that was set up previously. Finally, the logfile is closed and then the Process object is closed:

    application.WaitForExit();

    string output = application.StandardOutput.ReadToEnd();

    logger.Write(output);
}

// close the process object
application.Close();

The temporary logfile we created using Path.GetTempPathFile holds information similar to the following output:

Volume in drive C has no label.
Volume Serial Number is DDDD-FFFF

Directory of C:CS60_CookbookCSCB6CSharpRecipesinDebug

04/11/2015  04:27 PM    <DIR>          .
04/11/2015  04:27 PM    <DIR>          ..
02/05/2015  10:06 PM               724 BigSpenders.xml
02/05/2015  10:05 PM               719 Categories.xml
02/05/2015  04:04 PM            64,566 CSCBCover.bmp
12/31/2014  05:23 PM           489,269 CSharpCookbook.zip
04/11/2015  04:27 PM           495,616 CSharpRecipes.exe
04/11/2015  04:27 PM            31,154 CSharpRecipes.exe.CodeAnalysisLog.xml
02/05/2015  09:53 PM             3,075 CSharpRecipes.exe.config
04/11/2015  04:27 PM                 0 
CSharpRecipes.exe.lastcodeanalysissucceeded
04/11/2015  04:27 PM           775,680 CSharpRecipes.pdb
02/05/2015  04:04 PM         5,190,856 EntityFramework.dll
02/05/2015  04:04 PM           620,232 EntityFramework.SqlServer.dll
02/05/2015  04:04 PM           154,645 EntityFramework.SqlServer.xml
02/05/2015  04:04 PM         3,645,119 EntityFramework.xml
03/09/2015  02:51 PM             6,569 IngredientList.txt
04/04/2015  09:55 AM           513,536 Newtonsoft.Json.dll
04/04/2015  09:55 AM           494,336 Newtonsoft.Json.xml
03/09/2015  02:51 PM         4,390,912 Northwind.mdf
04/06/2015  04:11 PM            51,712 NorthwindLinq2Sql.dll
04/06/2015  04:11 PM           128,512 NorthwindLinq2Sql.pdb
04/11/2015  01:18 PM           573,440 Northwind_log.ldf
03/09/2015  02:51 PM                80 RecipeChapters.txt
04/06/2015  04:11 PM            16,384 SampleClassLibrary.dll
04/06/2015  04:11 PM             1,283 SampleClassLibrary.dll.CodeAnalysisLog.xml
04/06/2015  04:11 PM                 0 
SampleClassLibrary.dll.lastcodeanalysissucceeded
04/06/2015  04:11 PM            11,776 SampleClassLibrary.pdb
12/02/2014  03:35 PM               387 SampleClassLibraryTests.xml
04/11/2015  03:48 PM             8,704 SharedCode.dll
04/11/2015  03:48 PM            15,872 SharedCode.pdb
              28 File(s)     17,685,158 bytes
               2 Dir(s)  67,929,718,784 bytes free

Discussion

Redirecting standard output can be of great use for tasks like automated build scenarios or test harnesses. While not quite as easy as simply placing > after the command line for a process at the command prompt, this approach is more flexible, as the stream output can be reformatted to XML or HTML for posting to a website. It also provides the opportunity to send the data to multiple locations at once, which the simple command-line redirect function in Windows can’t do.

Waiting to read from the stream until the application has finished ensures that there will be no deadlock issues. If the stream is accessed synchronously before this time, then it’s possible for the parent to block the child. At a minimum, the child will wait until the parent has finished reading from the stream before it continues writing to it. So, by postponing the read until the end, you save the child some performance degradation at the cost of some additional time at the end.

See Also

The “ProcessStartInfo.RedirectStandardOutput Property” and “ProcessStartInfo.UseShellExecute Property” topics in the MSDN documentation.

13.7 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, AppDomain settings, and base paths for the AppDomain. To demonstrate this, the following 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:

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 via 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 {name}!");
    }
}

The output from this example is shown here:

Hello from CSharpRecipes.exe!
Hello from CSharpRecipes.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 real-world 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) or loading third-party code into a secondary AppDomain for isolation.

See Also

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

13.8 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 13-4 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 checks the registry for the “official” name of the OS. The OperatingSystem class retrieved from Environment.OSVersion has a property for the service pack, called ServicePack. These values are all returned as the operating system name, version, and service pack string.

Example 13-4. GetOSAndServicePack method
public static string GetOSAndServicePack()
{
    // Get the current OS info
    OperatingSystem os = Environment.OSVersion;
    RegistryKey rk =
        Registry.LocalMachine.OpenSubKey(
            @"SOFTWAREMicrosoftWindows NTCurrentVersion");
    string osText = (string)rk?.GetValue("ProductName");
    if (string.IsNullOrWhiteSpace(osText))
        osText = os.VersionString;
    else
        osText = (
            $"{osText} {os.Version.Major}.{os.Version.Minor}.{os.Version.Build}");
    if (!string.IsNullOrWhiteSpace(os.ServicePack))
        osText = $"{osText} {os.ServicePack}";
    return osText;
}

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. This simple knowledge, transmitted through your support department, can save you hours in debugging time. It is well worth making this information available so your support department can easily direct your clients to it if they cannot otherwise locate it.

See Also

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

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.135.58