Writing coupled WMI providers using WMI.NET Provider Extension 2.0

Writing coupled WMI providers using WMI.NET Provider Extension 2.0

Gabriel Ghizila

Microsoft Corporation

January 2008

 

Summary: Details how to write a coupled WMI provider using WMI.NET Provider Extension 2.0 shipped in .NET Framework 3.5

Contents

Introduction

A simple .NET class

Assembly level attribute

Class level WMI.NET attributes

Runtime requirements

Registration with WMI

Extending classes through inheritance

Implementing Methods

Exceptions and error reporting

Other tips

Conclusion

Listing 1 – SCMInterop.cs

Listing 2 – WIN32ServiceHost.cs

 

Introduction

Windows Management Instrumentation (WMI) is a widely-used infrastructure to manage Windows and Windows applications. Despite being very extensible and popular among system administrators and management applications, many developers get cold feet thinking about writing WMI providers due to the complexity of the native interfaces that need to be implemented.

While the initial versions of the .NET Framework came with a set of objects and patterns to implement WMI providers, those were limited to application management, they did not let you define methods and the keys for instances were automatically generated. WMI.NET Provider Extension v2 (WMI.NET) is a new infrastructure in Orcas (.NET Framework 3.5) that lets you implement a full set of WMI provider functionality. This new infrastructure coexists with the WMI.NET provider model from previous versions but it is much more powerful and extensible.

The focus of the present article is how to write WMI coupled providers, which is one of the most significant capabilities in WMI.NET. There are no significant differences in the way the decoupled providers are written so the article can give a good start for a reader who tries to write WMI providers of any kind using WMI.NET. The article will show how to create a coupled WMI provider starting from a simple .NET class and then enrich it with few extra features. The goal is to be able to enumerate processes hosting Windows services and to be able to enumerate Windows services in each such process and be able to integrate this functionality into WMI.

A simple .NET class

To begin with, we will create a C# class that will model a process hosting Windows services. Each instance will show a list of hosted services in the associated process. The class has a static method returning all instances associated to service hosts running in the system.

    public class WIN32ServiceHost

    {

The class is a wrapper around the Process class. The innerProcess field will store a reference to the Process object.

        Process innerProcess;

The class has one constructor that accepts a process object as parameter. 

        public WIN32ServiceHost(Process innerProcess)

        {

            this.innerProcess = innerProcess;

        }

We include an accessor for the process id.

        public int ID

        {

            get { return this.innerProcess.Id; }

        }

The Services property will return an array with the names of hosted services. This property is null if no services are running in the process.

        public string[] Services

        {

            get

            {

// Idle process does not host any services.

                if (innerProcess.Id == 0)

                    return null;

// Get a list of all windows services on the system

                ServiceController[] services = ServiceController.GetServices();

                List<ServiceController> servicesForProcess = new List<ServiceController>();

                using (SCM scm = new SCM())

                {

                    for (int svcIndex = 0; svcIndex < services.Length; svcIndex++)

                    {

                        ServiceController crtService = services[svcIndex];

                        int processId = scm.GetProcessId(crtService.ServiceName);

// Compare the id of the process a service is running in to the id of the current process.

                        if (processId == innerProcess.Id)

                        {

                            servicesForProcess.Add(services[svcIndex]);

                        }

                    }

                }

 

                if (servicesForProcess.Count == 0)

                    return null;

// Prepare, populate and return the array with the service’ names

                string[] servicesNames = new string[servicesForProcess.Count];

 

                for (int serviceIdx = 0; serviceIdx < servicesForProcess.Count; serviceIdx++)

                {

                    servicesNames[serviceIdx] = servicesForProcess[serviceIdx].ServiceName;

                }

                return servicesNames;

            }

        }

EnumerateServiceHosts is a static method that will return an IEnumerable to go through all running service hosts.

        static public IEnumerable EnumerateServiceHosts()

        {

            Process[] processes = Process.GetProcesses();

            foreach (Process crtProcess in processes)

            {

                WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(crtProcess);

                if (crtServiceHost.Services != null)

                {

                    yield return crtServiceHost;

                }

            }

        }

    }

A developer can use this class from any .NET application. Though, in this case, various other management applications will have no way to use it. WMI.NET has the hooks to expose the class in this example to the WMI world and we will explain the process in the following paragraphs. WMI provides a model to allow leveraging such objects and integrate them into an enterprise scale management applications as Systems Management Server or Operations Manager, provide remote interaction and enables you to see and use this class from multiple platforms.

Assembly level attribute

The first step to exposing an assembly for instrumentation using WMI.NET is setting a WmiConfiguration attribute at the assembly level. This attribute marks the assembly as one that implements a WMI.NET provider and lets you configure various things about the classes implemented by the provider, including the namespace in which they will be exposed. For our example we will define the WMI namespace to be root\Test and set the hosting model to coupled provider model in the NetworkService security context. Note that all the operations will be executed in the security context of the calling user through impersonation.

[assembly: WmiConfiguration(@"root\Test", HostingModel = ManagementHostingModel.NetworkService)]

Class level WMI.NET attributes

To instrument a class using WMI.NET, the class, its methods, fields and properties exposed to WMI.NET need to be public and properly marked with WMI.NET attributes. The attributes are used to generate the metadata required by WMI to use the existing C# class as a WMI class.

Instrumenting a .NET class

The ManagementEntity attribute marks a .NET class as being instrumented. At deployment time, the WMI.NET infrastructure will generate a corresponding WMI class, with the same name, in the WMI repository. To modify this name, we need to provide the named parameter Name in the argument list for the ManagementEntity attribute. Name is used as a named parameter to alter the instrumentation name for most attributes. For our example we choose to name the WMI class WIN32_ServiceHost without renaming the .NET class.

    [ManagementEntity(Name = "WIN32_ServiceHost")]

    public class WIN32ServiceHost

Note that naming of any entity may be a little tricky.  C# is case sensitive but WMI is not. Hence all the names will be treated as case insensitive from the WMI.NET infrastructure perspective. If the names of two fields or two methods that are members of the same class differ only by case, the assembly will fail to register with WMI.

Controlling the WMI class schema

The payload of an instance is given by its properties. We need to mark all the properties to be reflected into WMI world with ManagementProbe, ManagementConfiguration or ManagementKey attributes. ManagementProbe is the attribute to mark properties exposed as read only into WMI. In our sample the Services property is the payload property that we want to expose to the management world. It shows a state of a process that can’t be modified directly, so we will mark it with the ManagementProbe. For read/write properties, ManagementConfiguration attribute will have to be used, in which case the property itself needs to have both a setter and a getter.

        [ManagementProbe]

        public string[] Services

ID is also part of WIN32ServiceHost’s payload as it identifies the process itself. The properties that uniquely identify an instance of their class are the keys for that class in the WMI world. All classes in WMI.NET are required to define keys and implement them to uniquely identify instances unless they are abstract, singleton or inherit from a class that already defines keys. Keys are marked with the ManagementKey attribute. In this sample, the process id uniquely identifies a process; therefore, it uniquely identifies instances of our class.

        [ManagementKey]

        public int ID

Runtime requirements

Marking the class and its collection of properties will allow to expose the class schema to be exposed to WMI but it will not suffice to have the classes expose actual data. We need to define entry points for getting, creating or deleting an instance as well as enumerating instances. We will provide code for instance enumeration as well as retrieving a particular instance which is practically the minimum for a functional WMI provider. 

Enumerating the instances

To enumerate instances, WMI.NET expects a static public method without parameters that returns an IEnumerable interface. It needs to be static as it implements functionality relevant to the entire class. This method needs to be marked with the ManagementEnumerator attribute. Already defined in our sample, EnumerateServiceHosts meets all the requirements so it can be just attributed and used for this purpose.

        [ManagementEnumerator]

        static public IEnumerable EnumerateServiceHosts()

It is really important for this method to ensure that each element returned in the enumeration is an instance of the instrumented class. Otherwise a runtime error will result.

Binding to an instance

To be able to retrieve a particular instance (which is called binding in WMI.NET), we need to have a method that returns an instance based on the values of the keys identifying it. We need a method that has the same number of parameters as keys, with the parameters having the same names and types as the keys. The return type should be the instrumented class itself. We will use a static method and, in order to associate class’ keys to parameters, we need to specify the same instrumented names for parameters as for keys.  In our sample, ID is the key for the WIN32_ServiceHost class and we have to either name the parameter for our binding method ID or use the ManagementName attribute to expose the parameter to WMI under the name “ID”. The WMI.NET infrastructure will recognize a binding method or constructor when it is marked with the ManagementBind attribute.

        [ManagementBind]

        static public WIN32ServiceHost GetInstance([ManagementName("ID")] int processId)

        {

            try

            {

                Process process = Process.GetProcessById(processId);

                WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(process);

                if (crtServiceHost.Services != null)

                {

                    return crtServiceHost;

                }

                else

                {

                    return null;

                }

            }

            // thrown by GetProcessById if no process with the given id is found

            catch (ArgumentException)

            {

                return null;

            }

        }

It is important to note that if the instance is not found the method will return null. In our case we will do that if we do not find the requested process at all or if the process found is not hosting any windows services.

Registration with WMI

The class is ready to do its work but we still need to register it with WMI and place it in an accessible location on the disk to be loaded from.

The Global Assembly Cache (GAC) is the place where we want the assembly located. That way, .NET can retrieve it by its full .NET name. Note that the assembly needs to have a .NET strong name to be registered in the GAC. For our example, we will use the .NET gacutil.exe tool to store the assembly in the GAC.

To get the information from the instrumented assembly into WMI metadata we need to use the .NET tool InstallUtil.exe to invoke a WMI.NET class named DefaultManagementInstaller. DefaultManagementInstaller knows how to parse the whole assembly for instrumented classes and generate the corresponding WMI metadata. As InstallUtil.exe needs a class marked with the RunInstaller attribute, we will define an empty class derived from DefaultManagementInstaller which InstallUtil.exe will invoke.

    [System.ComponentModel.RunInstaller(true)]

    public class MyInstall : DefaultManagementInstaller

    {

    }

Registration with WMI can be done offline or online. Online registration will store an assembly’s instrumentation metadata straight into the WMI repository. To directly install the metadata into the WMI repository, InstallUtil.exe command will be invoked with the assembly name as parameter. The assembly has to be in GAC before executing InstallUtil.exe. For the assembly generated in this example named WMIServiceHost.dll we will use the following commands:

C:>gacutil.exe /i WMIServiceHost.dll

C:>Installutil.exe WMIServiceHost.dll

Offline registration requires two steps. The first step is to generate the WMI metadata associated with the assembly and store it in a MOF file. The second step is the actual registration on target machines using the mofcomp.exe tool or as part of an installation package. The advantage of offline registration is that the MOF file can be localized and modified as needed. For this example we can generate and store the WMI metadata in a file named WMIServiceHost.mof using the MOF parameter as follows:

C:>Installutil.exe /MOF=WMIServiceHost.mof WMIServiceHost.dll

As in the online case, the assembly will have to be in the GAC on the target machine. To validate the deployment, we can use the wmic.exe system tool to see the instances and values of this class.

C:>wmic /NAMESPACE:\\root\test PATH win32_servicehost get /value

During development, it is useful to deploy the symbols to the same folder in the GAC where the assembly is stored. That way any stack reported as result of an error or crash, will include the full source path and line numbers that will help identify the offending piece of code.

Extending classes through inheritance

WIN32_ServiceHost is about service hosts and the information it provides is limited to processes hosting windows services. It will be interesting to extend this information to include process-specific information like memory usage, executable path, session id and so on. To obtain this information we can extend the schema and write some more code to retrieve as much information as we need. A nice alternative to writing extra code is to take advantage of the already existent WIN32_Process class that is on the operating system out of the box in root\cimv2 namespace and which provides all this extra information for any process running in the system. This class provides extensive information about a running process and we can use WMI derivation to extend it with our own class.

WMI inheritance is translated in the WMI.NET coding model to class inheritance. Since the class we want to derive from is a class from the WMI space that we do not actually implement in the code, we have to mark it in specific ways.

Before we start to write derivation, we need to note two important things about the WIN32_Process class. First WIN32_Process is in the root\cimv2 namespace and the derivation requires us to register the win32_servicehost class in the same namespace. Therefore, we will change the WmiConfiguration attribute statement slightly.

[assembly: WmiConfiguration(@"root\cimv2", HostingModel = ManagementHostingModel.NetworkService)]

Furthermore, our superclass, win32_process, has the property Handle defined as key. This key is of type CIM_STRING which translates to .NET’s System.String. We will have to stop using ID as our key property and use the property Handle instead.

To define an external class in WMI.NET to match win32_process we simply mirror its schema but only include properties we want or need to use. The keys in the class hierarchy are always required. At this time Handle is the only interesting property as it is the key and we will need that for binding to a specific instance.

    [ManagementEntity(External = true)]

    abstract public class Win32_Process

    {

        protected string handle;

 

        [ManagementKey]

        public string Handle

        {

            get {

                return this.handle;

            }

        }

    }

Setting External to true on the ManagementEntity attribute stops the infrastructure from generating WMI metadata at deployment time, but keeps the declared information for usage at runtime when it will need to find keys and properties for derived classes. Note that it is very important to control the content of the keys of base class as the keys are used by the WMI subsystem to merge information from various providers.

To get the WMI class WIN32_ServiceHost inherit the WMI class Win32Process, we derive WIN32ServiceHost from the newly created abstract class in .NET world

    [ManagementEntity(Name = "WIN32_ServiceHost")]

    public class WIN32ServiceHost: Win32_Process

   

We remove the ID property.

        [ManagementKey]

        public int ID

        {

            get { return this.innerProcess.Id; }

        }

alter the constructor to populate the new key in the handle field of the base class

        public WIN32ServiceHost(Process innerProcess)

        {

            this.innerProcess = innerProcess;

            this.handle = innerProcess.Id.ToString();

        }

modify GetInstance to work with a string argument named Handle, just the first few lines of it as the rest remains the same.

        [ManagementBind]

        static public WIN32ServiceHost GetInstance(string Handle)

        {

            int processId;

            if (!Int32.TryParse(Handle, out processId))

            {

                return null;

            }

 

            try

            [...]

We need to recompile and redeploy the new assembly in GAC. We use InstallUtil.exe to deploy the new schema. Than we can query the system using a slightly modified wmic.exe command.

C:>wmic /NAMESPACE:\\root\cimv2 PATH win32_servicehost get /value

The returned instances will be populated with information from both classes, win32_process and win32_servicehost. In the output, Services will come from win32_servicehost while everything else comes from win32_process. To simplify the output, we can specify the columns desired.

C:>wmic PATH win32_servicehost get Handle,Caption,CommandLine,Services /value

It gets even more interesting when we try to enumerate win32_process. Such a query will return all the processes and it will populate the Services field just for the instances of win32_servicehost.

C:>wmic PATH win32_process get /value

The output may be a little bit overwhelming so just dump it in a file (by adding > out.txt at the end of the command line) and open it in notepad to search for the Services property. To understand what is going on, we can show the system properties to identify, for each instance, what WMI class it is from.

C:>wmic PATH win32_process get Handle,CommandLine,__CLASS /value

Out of the resulting list pick a win32_ServiceHost instance and display its values.

C:>wmic path WIN32_Process.Handle="536" get /value

Similar operations can be performed from any WMI client application using Windows scripting,  Microsoft PowerShell, managed or native code, the system will treat this assembly the same way it treats any other provider.

Implementing Methods

WMI.NET supports methods, both static and per instance. In our case we will add a method to stop all the services hosted by a process, to allow to cleanly stop a process, without killing it while services are still running. To make a public method visible in WMI, we mark it with the ManagementTask attribute.

        [ManagementTask]

        public bool StopServices(int millisecondsWait)

        {

            if (innerProcess.Id == 0)

                return false;

            ServiceController[] services = ServiceController.GetServices();

 

            bool oneFailed = false;

            using (SCM scm = new SCM())

            {

                for (int svcIndex = 0; svcIndex < services.Length; svcIndex++)

                {

                    ServiceController crtService = services[svcIndex];

                    int processId = scm.GetProcessId(crtService.ServiceName);

                    if (processId == innerProcess.Id)

                    {

                        try

                        {

                            crtService.Stop();

                            if (millisecondsWait != 0)

                            {

                                crtService.WaitForStatus(   ServiceControllerStatus.Stopped,

                                                            new TimeSpan((long)millisecondsWait * 10000));

                            }

                        }

                        catch (System.ServiceProcess.TimeoutException)

                        {

                            oneFailed = true;

                        }

                        catch (System.ComponentModel.Win32Exception)

                        {

                            oneFailed = true;

                        }

                        catch (InvalidOperationException)

                        {

                            oneFailed = true;

                        }

                    }

                }

            }

            return !oneFailed;

        }

 To invoke this method we need an instance of win32_servicehost class. We get a list of available service hosts typing:

C:>wmic path win32_servicehost get handle,Services

and pick the one with most benign list of services (so as not to bring your system down, also, some services may also be not stoppable) and use its Handle property to identify the instance of the call.

C:>wmic path win32_servicehost.Handle="540" CALL StopServices(0)

Exceptions and error reporting

Exceptions are an important aspect of WMI.NET. The infrastructure uses some exceptions to communicate information and it treats most exceptions as unhandled.

Accepted exceptions

WMI.NET handles only few exceptions that will be described further in the article. All the other exceptions are considered as programming errors and treated as unhandled exceptions causing the WMI provider host to crash. WMI.NET reports unhandled exceptions in the Windows event log.

The accepted exceptions are actually translated into WMI error codes and sent back to the client code as shown in the Table 1. Therefore, the WMI.NET providers will behave as any other native provider.

System.OutOfMemoryException

WBEM_E_OUT_OF_MEMORY

System.Security.SecurityException

WBEM_E_ACCESS_DENIED

System.ArgumentException

WBEM_E_INVALID_PARAMETER

System.ArgumentOutOfRangeException

WBEM_E_INVALID_PARAMETER

System.InvalidOperationException

WBEM_E_INVALID_OPERATION

System.Management.Instrumentation.InstanceNotFoundException

WBEM_E_NOT_FOUND

System.Management.Instrumentation.InstrumentationException

From the internal exception, described further in the article

Table1 – translation of exceptions into WMI errors

In the list above all but two are general .NET framework exceptions. All the exceptions listed can be thrown to report to the client on the particular status these exceptions represent.

Two new exceptions were added in the System.Management.Instrumentation namespace as described further.

InstanceNotFoundException

This exception serves to notify that a requested instance is not found. The sample in this article uses the GetInstance static method for binding to a given instance and returns null when the instance is not found. A constructor can be used for the same purpose, but it will need to throw an InstanceNotFoundException when is not able to find the required instance. Here is the constructor to replace the GetInstance static method.

        [ManagementBind]

        public WIN32ServiceHost(string Handle)

        {

            int processId;

            if (!Int32.TryParse(Handle, out processId))

            {

                throw new InstanceNotFoundException();

            }

 

            try

            {

                Process process = Process.GetProcessById(processId);

                this.innerProcess = process;

                this.handle = Handle;

                if (this.Services == null)

                {

                    throw new InstanceNotFoundException();

                }

            }

            // thrown by GetProcessById if no process with the given id is found

            catch (ArgumentException)

            {

                throw new InstanceNotFoundException();

            }

        }

InstrumentationException

An InstrumentationException is the wrapper for handled exceptions. A provider may choose to inform the client of an error that happened on its side. To do so it will throw an InstrumentationException. Note that the developer should have in mind that the exception is going back into the WMI system. Hence, WMI.NET will try its best to transform it into a COM HRESULT. To throw a precise error code back to the client we need to pass in as inner an exception for the InstrumentationException an Exception class that allows setting the internal HResult in the base exception class directly.

Error reporting

Any exception not listed in the previous section will end up as an unhandled exception which will result in a crash reported as “An unhandled exception (‘System.ExecutionEngineException’) occurred in wmiprvse.exe[<NNNN>].”, NNNN being a process number. The error and stack will be reported in the eventlog. Having symbols in the same folder as the assembly will show the offending stack complete, with file name and line number.

Another error case is when the assembly can’t be loaded because it was not deployed into the GAC. WMI.NET will return a Provider load failure (WBEM_E_PROVIDER_LOAD_FAILURE) for this case.

A frequent problem during provider development is the mismatch between the WMI schema and the code. That may happen when deploying the new assembly without deploying the schema into WMI or when the issues are not caught when deploying using InstallUtil.exe because the information is available only at runtime. That would be the case, for example, if an incorrect type was returned during an enumeration. The WMI.NET infrastructure reports that to the client as a provider failure (WMI error WBEM_E_PROVIDER_FAILURE) and it will generate a message in the Windows event log describing the runtime problem.

Other tips

All the attributes and the WMI.NET code are located in the System.Management.Instrumentation namespace. To build an assembly for WMI.NET, the project needs to have references to System.Core.dll and System.Management.Infrastructure.dll as they contain the definition of attributes and the runtime code respectively. The later knows how to load instrumented assemblies, match the instrumented classes against the WMI repository and invoke them accordingly.

Keep all the classes for a particular kind of application in the same assembly. That makes maintenance and deployment much easier. 

All the code in the article needs to be deployed and ran as administrator. On Windows Vista, running as administrator will require an elevated security command prompt. Providers do not usually require clients to be administrators unless they access specially secured system data as in our example. 

Conclusion

The new management library in .NET Framework 3.5 gives developers a powerful tool in the quest for writing WMI providers. Given the simplicity of the programming model, writing WMI providers becomes a rather easy task allowing you to implement most WMI functionality. The sample in this article shows how to write just a simple WMI provider, but the library also provides support for development of decoupled providers and implementation of singletons, inheritance, abstract classes, references and association classes making WMI.NET Provider Extension v2 a serious alternative to development using WMI native interfaces.

Listing 1 – SCMInterop.cs

using System;

using System.Runtime.InteropServices;

using System.ServiceProcess;

 

 

namespace External.PInvoke

{

    [StructLayout(LayoutKind.Sequential)]

    internal struct SERVICE_STATUS_PROCESS

    {

        public uint dwServiceType;

        public uint dwCurrentState;

        public uint dwControlsAccepted;

        public uint dwWin32ExitCode;

        public uint dwServiceSpecificExitCode;

        public uint dwCheckPoint;

        public uint dwWaitHint;

        public uint dwProcessId;

        public uint dwServiceFlags;

        public static readonly int SizeOf = Marshal.SizeOf(typeof(SERVICE_STATUS_PROCESS));

    }

 

    [Flags]

    internal enum SCM_ACCESS : uint

    {

        SC_MANAGER_CONNECT = 0x00001,

    }

 

    [Flags]

    internal enum SERVICE_ACCESS : uint

    {

        STANDARD_RIGHTS_REQUIRED = 0xF0000,

        SERVICE_QUERY_CONFIG = 0x00001,

        SERVICE_CHANGE_CONFIG = 0x00002,

        SERVICE_QUERY_STATUS = 0x00004,

        SERVICE_ENUMERATE_DEPENDENTS = 0x00008,

        SERVICE_START = 0x00010,

        SERVICE_STOP = 0x00020,

        SERVICE_PAUSE_CONTINUE = 0x00040,

        SERVICE_INTERROGATE = 0x00080,

        SERVICE_USER_DEFINED_CONTROL = 0x00100,

        SERVICE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED |

                          SERVICE_QUERY_CONFIG |

                          SERVICE_CHANGE_CONFIG |

                          SERVICE_QUERY_STATUS |

                          SERVICE_ENUMERATE_DEPENDENTS |

                          SERVICE_START |

                          SERVICE_STOP |

                          SERVICE_PAUSE_CONTINUE |

                          SERVICE_INTERROGATE |

                          SERVICE_USER_DEFINED_CONTROL)

    }

 

    internal enum SC_STATUS_TYPE

    {

        SC_STATUS_PROCESS_INFO = 0

    }

 

    internal class ServiceHandle : SafeHandle

    {

        public ServiceHandle()

            : base(IntPtr.Zero, true)

        {

        }

 

        public void OpenService(SafeHandle scmHandle, string serviceName)

        {

            IntPtr serviceHandle = SCM.OpenService(scmHandle, serviceName, SERVICE_ACCESS.SERVICE_QUERY_STATUS);

 

            if (serviceHandle == IntPtr.Zero)

            {

                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(),

                                                                "SCM.QueryServiceStatusEx");

            }

            SetHandle(serviceHandle);

        }

 

        protected override bool ReleaseHandle()

        {

            return SCM.CloseServiceHandle(base.handle);

        }

 

        public override bool IsInvalid

        {

            get { return IsClosed || handle == IntPtr.Zero; }

        }

    }

 

    internal class SCM : SafeHandle

    {

        [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", CharSet = CharSet.Unicode,

                                                                     SetLastError = true)]

        public static extern IntPtr OpenSCManager(string machineName,

                                                  string databaseName,

                                    [MarshalAs(UnmanagedType.U4)]SCM_ACCESS dwAccess);

 

        [DllImport("advapi32.dll", EntryPoint = "OpenServiceW", CharSet = CharSet.Unicode,

                                                                     SetLastError = true)]

        public static extern IntPtr OpenService(SafeHandle hSCManager,

                                    [MarshalAs(UnmanagedType.LPWStr)] string lpServiceName,

                                    [MarshalAs(UnmanagedType.U4)]SERVICE_ACCESS dwDesiredAccess);

 

        [DllImport("advapi32.dll", EntryPoint = "QueryServiceStatusEx", CharSet = CharSet.Auto,

                                                                     SetLastError = true)]

        public static extern bool QueryServiceStatusEx(SafeHandle hService,

                                                      SC_STATUS_TYPE InfoLevel,

                                                      ref SERVICE_STATUS_PROCESS dwServiceStatus,

                                                      int cbBufSize,

                                                      ref int pcbBytesNeeded);

 

        [DllImport("advapi32.dll", EntryPoint = "CloseServiceHandle", CharSet = CharSet.Auto,

                                                                     SetLastError = true)]

        public static extern bool CloseServiceHandle(IntPtr hService);

 

        public SCM()

            : base(IntPtr.Zero, true)

        {

            IntPtr handle = OpenSCManager(null, null, SCM_ACCESS.SC_MANAGER_CONNECT);

            base.SetHandle(handle);

        }

 

        protected override bool ReleaseHandle()

        {

            return SCM.CloseServiceHandle(base.handle);

        }

 

        public override bool IsInvalid

        {

            get { return IsClosed || handle == IntPtr.Zero; }

        }

 

        public void QueryService(string serviceName, out SERVICE_STATUS_PROCESS statusProcess)

        {

            statusProcess = new SERVICE_STATUS_PROCESS();

            int cbBytesNeeded = 0;

 

            using (ServiceHandle serviceHandle = new ServiceHandle())

            {

                serviceHandle.OpenService(this, serviceName);

 

                bool scmRet = SCM.QueryServiceStatusEx(serviceHandle,

                                                        SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO,

                                                        ref statusProcess,

                                                        SERVICE_STATUS_PROCESS.SizeOf,

                                                        ref cbBytesNeeded);

                if (!scmRet)

                {

                    throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(),

                                                                    "SCM.QueryServiceStatusEx");

                }

            }

        }

 

        public int GetProcessId(string serviceName)

        {

            SERVICE_STATUS_PROCESS serviceStatus;

            this.QueryService(serviceName, out serviceStatus);

            return (int)serviceStatus.dwProcessId;

        }

    }

}

Listing 2 – WIN32ServiceHost.cs

using System;

using System.Collections;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

using System.ServiceProcess;

using System.Diagnostics;

using External.PInvoke;

using System.Management.Instrumentation;

 

[assembly: WmiConfiguration(@"root\cimv2", HostingModel = ManagementHostingModel.NetworkService)]

 

 

namespace TestWMI.Hosted

{

    [System.ComponentModel.RunInstaller(true)]

    public class MyInstall : DefaultManagementInstaller

    {

    }

 

 

    [ManagementEntity(External = true)]

    abstract public class Win32_Process

    {

        protected string handle;

 

        [ManagementKey]

        public string Handle

        {

            get {

                return this.handle;

            }

        }

    }

 

    [ManagementEntity(Name = "WIN32_ServiceHost")]

    public class WIN32ServiceHost: Win32_Process

    {

        Process innerProcess;

 

        /// <summary>

        ///

        /// </summary>

        /// <param name="innerProcess"></param>

        public WIN32ServiceHost(Process innerProcess)

        {

            this.innerProcess = innerProcess;

            this.handle = innerProcess.Id.ToString();

        }

 

        public int ID

        {

            get { return this.innerProcess.Id; }

        }

 

        [ManagementProbe]

        public string[] Services

        {

            get

            {

                if (innerProcess.Id == 0)

                    return null;

                ServiceController[] services = ServiceController.GetServices();

                List<ServiceController> servicesForProcess = new List<ServiceController>();

                using (SCM scm = new SCM())

                {

                    for (int svcIndex = 0; svcIndex < services.Length; svcIndex++)

                    {

                        ServiceController crtService = services[svcIndex];

                        int processId = scm.GetProcessId(crtService.ServiceName);

                        if (processId == innerProcess.Id)

                        {

                            servicesForProcess.Add(services[svcIndex]);

                        }

                    }

                }

 

                if (servicesForProcess.Count == 0)

                    return null;

                string[] servicesNames = new string[servicesForProcess.Count];

 

                for (int serviceIdx = 0; serviceIdx < servicesForProcess.Count; serviceIdx++)

                {

                    servicesNames[serviceIdx] = servicesForProcess[serviceIdx].ServiceName;

                }

                return servicesNames;

            }

        }

 

        [ManagementEnumerator]

        static public IEnumerable EnumerateServiceHosts()

        {

            Process[] processes = Process.GetProcesses();

            foreach (Process crtProcess in processes)

            {

                WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(crtProcess);

                if (crtServiceHost.Services != null)

                {

                    yield return crtServiceHost;

                }

            }

        }

 

        [ManagementBind]

        public WIN32ServiceHost(string Handle)

        {

            int processId;

            if (!Int32.TryParse(Handle, out processId))

            {

                throw new InstanceNotFoundException();

            }

 

            try

            {

                Process process = Process.GetProcessById(processId);

                this.innerProcess = process;

                this.handle = Handle;

                if (this.Services == null)

                {

                    throw new InstanceNotFoundException();

                }

            }

            // thrown by GetProcessById if no process with the given id is found

            catch (ArgumentException)

            {

                throw new InstanceNotFoundException();

            }

        }

 

        static public WIN32ServiceHost GetInstance(string Handle)

        {

            int processId;

            if (!Int32.TryParse(Handle, out processId))

            {

                return null;

            }

 

            try

            {

                Process process = Process.GetProcessById(processId);

                WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(process);

                if (crtServiceHost.Services != null)

                {

                    return crtServiceHost;

                }

                else

                {

                    return null;

                }

            }

            // thrown by GetProcessById if no process with the given id is found

            catch (ArgumentException)

            {

                return null;

            }

        }

 

        [ManagementTask]

        public bool StopServices(int millisecondsWait)

        {

            if (innerProcess.Id == 0)

                return false;

            ServiceController[] services = ServiceController.GetServices();

 

            bool oneFailed = false;

            using (SCM scm = new SCM())

            {

                for (int svcIndex = 0; svcIndex < services.Length; svcIndex++)

                {

                    ServiceController crtService = services[svcIndex];

                    int processId = scm.GetProcessId(crtService.ServiceName);

                    if (processId == innerProcess.Id)

                    {

                        try

                        {

                            crtService.Stop();

                            if (millisecondsWait != 0)

                            {

                                crtService.WaitForStatus(   ServiceControllerStatus.Stopped,

                                                            new TimeSpan((long)millisecondsWait * 10000));

                            }

                        }

                        catch (System.ServiceProcess.TimeoutException)

                        {

                            oneFailed = true;

                        }

                        catch (System.ComponentModel.Win32Exception)

                        {

                            oneFailed = true;

                        }

                        catch (InvalidOperationException)

                        {

                            oneFailed = true;

                        }

                    }

                }

            }

            return !oneFailed;

        }

    }

}

 

 

 

© 2008 Microsoft Corporation. All rights reserved.