Compartir a través de


Escritura de proveedores WMI acoplados mediante la extensión de proveedor de WMI.NET 2.0

Escritura de proveedores WMI acoplados mediante la extensión de proveedor de WMI.NET 2.0

Gabriel Ghizila

Microsoft Corporation

Enero de 2008

 

Resumen: Detalles sobre cómo escribir un proveedor WMI acoplado mediante la extensión de proveedor de WMI.NET 2.0 enviada en .NET Framework 3.5

Contenido

Introducción

Una clase simple de .NET

Atributo de nivel de ensamblado

Atributos de nivel de clase WMI.NET

Requisitos en tiempo de ejecución

Registro con WMI

Extensión de clases a través de la herencia

Implementación de métodos

Informes de errores y excepciones

Otras sugerencias

Conclusión

Listado 1: SCMInterop.cs

Listado 2: WIN32ServiceHost.cs

 

Introducción

Instrumental de administración de Windows (WMI) es una infraestructura ampliamente usada para administrar aplicaciones Windows y Windows. A pesar de ser muy extensible y popular entre los administradores del sistema y las aplicaciones de administración, muchos desarrolladores tienen pies fríos pensando en escribir proveedores de WMI debido a la complejidad de las interfaces nativas que deben implementarse.

Aunque las versiones iniciales de .NET Framework incluyen un conjunto de objetos y patrones para implementar proveedores de WMI, estos se limitaron a la administración de aplicaciones, no le permitieron definir métodos y las claves de las instancias se generaron automáticamente. WMI.NET extensión de proveedor v2 (WMI.NET) es una nueva infraestructura en Orcas (.NET Framework 3.5) que le permite implementar un conjunto completo de funcionalidades del proveedor WMI. Esta nueva infraestructura coexiste con el modelo de proveedor de WMI.NET de versiones anteriores, pero es mucho más eficaz y extensible.

El enfoque del presente artículo es cómo escribir proveedores acoplados de WMI, que es una de las funcionalidades más importantes de WMI.NET. No hay diferencias significativas en la forma en que se escriben los proveedores desacoplados para que el artículo pueda dar un buen comienzo a un lector que intente escribir proveedores WMI de cualquier tipo mediante WMI.NET. En el artículo se muestra cómo crear un proveedor WMI acoplado a partir de una clase sencilla de .NET y, a continuación, enriquecerlo con algunas características adicionales. El objetivo es poder enumerar los procesos que hospedan servicios de Windows y poder enumerar los servicios de Windows en cada proceso de este tipo y poder integrar esta funcionalidad en WMI.

Una clase simple de .NET

Para empezar, crearemos una clase de C# que modelará un proceso que hospedará los servicios de Windows. Cada instancia mostrará una lista de servicios hospedados en el proceso asociado. La clase tiene un método estático que devuelve todas las instancias asociadas a los hosts de servicio que se ejecutan en el sistema.

    clase pública WIN32ServiceHost

    {

La clase es un contenedor alrededor de la clase Process. El campo innerProcess almacenará una referencia al objeto Process.

        Process innerProcess;

La clase tiene un constructor que acepta un objeto de proceso como parámetro. 

        public WIN32ServiceHost(Process innerProcess)

        {

            this.innerProcess = innerProcess;

        }

Se incluye un descriptor de acceso para el identificador de proceso.

        id. de int público

        {

            get { return this.innerProcess.Id; }

        }

La propiedad Services devolverá una matriz con los nombres de los servicios hospedados. Esta propiedad es null si no se ejecutan servicios en el proceso.

        public string[] Services

        {

            get

            {

El proceso inactivo no hospeda ningún servicio.

                if (innerProcess.Id == 0)

                    return null;

Obtener una lista de todos los servicios de Windows en el sistema

                ServiceController[] services = ServiceController.GetServices();

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

                using (SCM scm = new SCM())

                {

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

                    {

                        ServiceController crtService = services[svcIndex];

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

Compare el identificador del proceso en el que se ejecuta un servicio con el identificador del proceso actual.

                        if (processId == innerProcess.Id)

                        {

                            servicesForProcess.Add(services[svcIndex]);

                        }

                    }

                }

 

                if (servicesForProcess.Count == 0)

                    return null;

Preparar, rellenar y devolver la matriz con los nombres del servicio

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

 

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

                {

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

                }

                return servicesNames;

            }

        }

EnumerateServiceHosts es un método estático que devolverá un IEnumerable para recorrer todos los hosts de servicio en ejecución.

        static public IEnumerable EnumerateServiceHosts()

        {

            Process[] processes = Process.GetProcesses();

            foreach (Procesar crtProcess en procesos)

            {

                WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(crtProcess);

                if (crtServiceHost.Services != null)

                {

                    yield return crtServiceHost;

                }

            }

        }

    }

Un desarrollador puede usar esta clase desde cualquier aplicación .NET. Sin embargo, en este caso, otras aplicaciones de administración no tendrán forma de usarla. WMI.NET tiene los enlaces para exponer la clase en este ejemplo al mundo WMI y explicaremos el proceso en los párrafos siguientes. WMI proporciona un modelo para permitir aprovechar estos objetos e integrarlos en aplicaciones de administración de escala empresarial como Systems Management Server o Operations Manager, proporcionar interacción remota y le permite ver y usar esta clase desde varias plataformas.

Atributo de nivel de ensamblado

El primer paso para exponer un ensamblado para la instrumentación mediante WMI.NET es establecer un atributo WmiConfiguration en el nivel de ensamblado. Este atributo marca el ensamblado como uno que implementa un proveedor de WMI.NET y le permite configurar varias cosas sobre las clases implementadas por el proveedor, incluido el espacio de nombres en el que se expondrán. En nuestro ejemplo, definiremos el espacio de nombres WMI para que sea root\Test y estableceremos el modelo de hospedaje en modelo de proveedor acoplado en el contexto de seguridad NetworkService. Tenga en cuenta que todas las operaciones se ejecutarán en el contexto de seguridad del usuario que realiza la llamada a través de la suplantación.

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

Atributos de nivel de clase WMI.NET

Para instrumentar una clase mediante WMI.NET, la clase, sus métodos, campos y propiedades expuestos a WMI.NET deben estar públicos y marcados correctamente con WMI.NET atributos. Los atributos se usan para generar los metadatos requeridos por WMI para usar la clase C# existente como una clase WMI.

Instrumentación de una clase .NET

El atributo ManagementEntity marca una clase .NET como instrumentada. En el momento de la implementación, la infraestructura de WMI.NET generará una clase WMI correspondiente, con el mismo nombre, en el repositorio WMI. Para modificar este nombre, es necesario proporcionar el parámetro con nombre Name en la lista de argumentos para el atributo ManagementEntity . Name se usa como parámetro con nombre para modificar el nombre de instrumentación de la mayoría de los atributos. En nuestro ejemplo, se elige asignar un nombre a la clase WMI WIN32_ServiceHost sin cambiar el nombre de la clase .NET.

    [ManagementEntity(Name = "WIN32_ServiceHost")]

    clase pública WIN32ServiceHost

Tenga en cuenta que la nomenclatura de cualquier entidad puede ser un poco complicada.  C# distingue mayúsculas de minúsculas, pero WMI no lo es. Por lo tanto, todos los nombres se tratarán como no distinguen mayúsculas de minúsculas desde la perspectiva de la infraestructura de WMI.NET. Si los nombres de dos campos o dos métodos que son miembros de la misma clase solo difieren por caso, el ensamblado no se registrará con WMI.

Controlar el esquema de clase WMI

La carga de una instancia se proporciona mediante sus propiedades. Es necesario marcar todas las propiedades que se van a reflejar en el mundo de WMI con atributos ManagementProbe, ManagementConfiguration o ManagementKey . ManagementProbe es el atributo para marcar las propiedades expuestas como de solo lectura en WMI. En nuestro ejemplo, la propiedad Services es la propiedad de carga que queremos exponer al mundo de administración. Muestra un estado de un proceso que no se puede modificar directamente, por lo que lo marcaremos con ManagementProbe. Para las propiedades de lectura y escritura, el atributo ManagementConfiguration tendrá que usarse, en cuyo caso la propia propiedad debe tener un establecedor y un captador.

        [ManagementProbe]

        public string[] Services

El identificador también forma parte de la carga de WIN32ServiceHost, ya que identifica el propio proceso. Las propiedades que identifican de forma única una instancia de su clase son las claves de esa clase en el mundo de WMI. Todas las clases de WMI.NET son necesarias para definir claves e implementarlas para identificar de forma única las instancias a menos que sean abstractas, singleton o hereden de una clase que ya defina claves. Las claves se marcan con el atributo ManagementKey . En este ejemplo, el identificador de proceso identifica de forma única un proceso; por lo tanto, identifica de forma única las instancias de nuestra clase.

        [ManagementKey]

        public int ID

Requisitos en tiempo de ejecución

Marcar la clase y su colección de propiedades permitirá exponer el esquema de clase que se va a exponer a WMI, pero no bastará para que las clases expongan datos reales. Es necesario definir puntos de entrada para obtener, crear o eliminar una instancia, así como enumerar instancias. Proporcionaremos código para la enumeración de instancias, así como para recuperar una instancia determinada que sea prácticamente el mínimo para un proveedor WMI funcional. 

Enumeración de las instancias

Para enumerar instancias, WMI.NET espera un método público estático sin parámetros que devuelve una interfaz IEnumerable . Debe ser estático, ya que implementa la funcionalidad relevante para toda la clase. Este método debe marcarse con el atributo ManagementEnumerator . Ya definido en nuestro ejemplo, EnumerateServiceHosts cumple todos los requisitos para que se pueda atribuir y usar para este fin.

        [ManagementEnumerator]

        static public IEnumerable EnumerateServiceHosts()

Es realmente importante para este método asegurarse de que cada elemento devuelto en la enumeración es una instancia de la clase instrumentada. De lo contrario, se producirá un error en tiempo de ejecución.

Enlace a una instancia

Para poder recuperar una instancia determinada (que se denomina enlace en WMI.NET), es necesario tener un método que devuelva una instancia basada en los valores de las claves que lo identifican. Necesitamos un método que tenga el mismo número de parámetros que las claves, con los parámetros que tienen los mismos nombres y tipos que las claves. El tipo de valor devuelto debe ser la propia clase instrumentada. Usaremos un método estático y, para asociar las claves de la clase a los parámetros, es necesario especificar los mismos nombres instrumentados para los parámetros que para las claves.  En nuestro ejemplo, ID es la clave de la clase WIN32_ServiceHost y tenemos que asignar un nombre al parámetro para nuestro identificador de método de enlace o usar el atributo ManagementName para exponer el parámetro a WMI con el nombre "ID". La infraestructura de WMI.NET reconocerá un método de enlace o constructor cuando esté marcado con el atributo ManagementBind .

        [ManagementBind]

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

        {

            probar

            {

                Proceso de proceso = Process.GetProcessById(processId);

                WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(process);

                if (crtServiceHost.Services != null)

                {

                    devuelve crtServiceHost;

                }

                else

                {

                    return null;

                }

            }

            se produce por GetProcessById si no se encuentra ningún proceso con el identificador especificado.

            catch (ArgumentException)

            {

                return null;

            }

        }

Es importante tener en cuenta que si no se encuentra la instancia, el método devolverá null. En nuestro caso, lo haremos si no encontramos el proceso solicitado en absoluto o si el proceso encontrado no hospeda ningún servicio de Windows.

Registro con WMI

La clase está lista para realizar su trabajo, pero todavía es necesario registrarla con WMI y colocarla en una ubicación accesible en el disco desde la que se va a cargar.

La caché global de ensamblados (GAC) es el lugar donde queremos que se encuentre el ensamblado. De este modo, .NET puede recuperarlo por su nombre completo de .NET. Tenga en cuenta que el ensamblado debe tener un nombre seguro de .NET para registrarse en la GAC. En nuestro ejemplo, usaremos la herramienta de gacutil.exe de .NET para almacenar el ensamblado en la GAC.

Para obtener la información del ensamblado instrumentado en metadatos de WMI, es necesario usar la herramienta .NET InstallUtil.exe para invocar una clase WMI.NET denominada DefaultManagementInstaller. DefaultManagementInstaller sabe cómo analizar todo el ensamblado para las clases instrumentadas y generar los metadatos WMI correspondientes. Como InstallUtil.exe necesita una clase marcada con el atributo RunInstaller , definiremos una clase vacía derivada de DefaultManagementInstaller que InstallUtil.exe invocará.

    [System.ComponentModel.RunInstaller(true)]

    public class MyInstall : DefaultManagementInstaller

    {

    }

El registro con WMI se puede realizar sin conexión o en línea. El registro en línea almacenará los metadatos de instrumentación de un ensamblado directamente en el repositorio WMI. Para instalar directamente los metadatos en el repositorio WMI, InstallUtil.exe comando se invocará con el nombre del ensamblado como parámetro. El ensamblado debe estar en GAC antes de ejecutar InstallUtil.exe. Para el ensamblado generado en este ejemplo denominado WMIServiceHost.dll usaremos los siguientes comandos:

C:>gacutil.exe /i WMIServiceHost.dll

C:>Installutil.exe WMIServiceHost.dll

El registro sin conexión requiere dos pasos. El primer paso es generar los metadatos de WMI asociados al ensamblado y almacenarlos en un archivo MOF. El segundo paso es el registro real en las máquinas de destino mediante la herramienta mofcomp.exe o como parte de un paquete de instalación. La ventaja del registro sin conexión es que el archivo MOF se puede localizar y modificar según sea necesario. En este ejemplo se pueden generar y almacenar los metadatos de WMI en un archivo denominado WMIServiceHost.mof mediante el parámetro MOF de la siguiente manera:

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

Como en el caso en línea, el ensamblado tendrá que estar en la GAC en la máquina de destino. Para validar la implementación, podemos usar la herramienta del sistema wmic.exe para ver las instancias y los valores de esta clase.

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

Durante el desarrollo, resulta útil implementar los símbolos en la misma carpeta en la GAC donde se almacena el ensamblado. De este modo, cualquier pila notificada como resultado de un error o bloqueo incluirá la ruta de acceso de origen completa y los números de línea que ayudarán a identificar el fragmento de código infractor.

Extensión de clases a través de la herencia

WIN32_ServiceHost trata sobre los hosts de servicio y la información que proporciona se limita a los procesos que hospedan servicios de Windows. Será interesante ampliar esta información para incluir información específica del proceso, como el uso de memoria, la ruta de acceso ejecutable, el identificador de sesión, etc. Para obtener esta información, podemos ampliar el esquema y escribir código más para recuperar toda la información que necesitemos. Una buena alternativa a escribir código adicional es aprovechar las ventajas de la clase WIN32_Process existente que está en el sistema operativo de fábrica en el espacio de nombres root\cimv2 y que proporciona toda esta información adicional para cualquier proceso que se ejecute en el sistema. Esta clase proporciona información extensa sobre un proceso en ejecución y podemos usar la derivación de WMI para ampliarla con nuestra propia clase.

La herencia de WMI se traduce en el modelo de codificación WMI.NET a la herencia de clases. Puesto que la clase a la que queremos derivar es una clase del espacio WMI que realmente no implementamos en el código, tenemos que marcarla de maneras específicas.

Antes de empezar a escribir la derivación, es necesario tener en cuenta dos aspectos importantes sobre la clase WIN32_Process . En primer lugar , WIN32_Process está en el espacio de nombres root\cimv2 y la derivación requiere que registremos la clase win32_servicehost en el mismo espacio de nombres. Por lo tanto, cambiaremos ligeramente la instrucción de atributo WmiConfiguration .

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

Además, nuestra superclase, win32_process, tiene la propiedad Handle definida como clave. Esta clave es de tipo CIM_STRING que se traduce en . System.String de NET. Tendremos que dejar de usar id. como propiedad de clave y usar la propiedad Handle en su lugar.

Para definir una clase externa en WMI.NET para que coincida con win32_process simplemente se refleja su esquema, pero solo se incluyen las propiedades que queremos o necesitamos usar. Las claves de la jerarquía de clases siempre son necesarias. En este momento , Handle es la única propiedad interesante, ya que es la clave y la necesitaremos para enlazar a una instancia específica.

    [ManagementEntity(External = true)]

    Win32_Process de clase pública abstracta

    {

        identificador de cadena protegido;

 

        [ManagementKey]

        identificador de cadena pública

        {

            get {

                devuelve this.handle;

            }

        }

    }

Establecer External en true en el atributo ManagementEntity impide que la infraestructura genere metadatos de WMI en el momento de la implementación, pero mantiene la información declarada para su uso en tiempo de ejecución cuando necesite buscar claves y propiedades para las clases derivadas. Tenga en cuenta que es muy importante controlar el contenido de las claves de la clase base, ya que el subsistema WMI usa las claves para combinar información de varios proveedores.

Para obtener la clase WMI WIN32_ServiceHost heredar la clase WMI Win32Process, derivamos WIN32ServiceHost de la clase abstracta recién creada en el mundo de .NET.

    [ManagementEntity(Name = "WIN32_ServiceHost")]

    public class WIN32ServiceHost: Win32_Process

   

Quitamos la propiedad ID.

        [ManagementKey]

        public int ID

        {

            get { return this.innerProcess.Id; }

        }

modifique el constructor para rellenar la nueva clave en el campo handle de la clase base.

        public WIN32ServiceHost(Process innerProcess)

        {

            this.innerProcess = innerProcess;

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

        }

modifique GetInstance para que funcione con un argumento de cadena denominado Handle, solo las primeras líneas que el resto sigue siendo el mismo.

        [ManagementBind]

        static public WIN32ServiceHost GetInstance(string Handle)

        {

            int processId;

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

            {

                return null;

            }

 

            probar

            [...]

Es necesario volver a compilar y volver a implementar el nuevo ensamblado en GAC. Usamos InstallUtil.exe para implementar el nuevo esquema. De lo que podemos consultar el sistema mediante un comando dewmic.exe ligeramente modificado.

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

Las instancias devueltas se rellenarán con información de ambas clases, win32_process y win32_servicehost. En la salida, Los servicios proceden de win32_servicehost mientras todo lo demás procede de win32_process. Para simplificar la salida, podemos especificar las columnas deseadas.

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

Resulta aún más interesante cuando intentamos enumerar win32_process. Esta consulta devolverá todos los procesos y rellenará el campo Servicios solo para las instancias de win32_servicehost.

C:>wmic PATH win32_process obtener /value

La salida puede ser un poco abrumadora, por lo que basta con volcarla en un archivo (agregando > out.txt al final de la línea de comandos) y ábrala en el Bloc de notas para buscar la propiedad Services. Para comprender lo que está ocurriendo, podemos mostrar las propiedades del sistema para identificar, para cada instancia, la clase WMI de la que procede.

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

Fuera de la lista resultante, seleccione una instancia de win32_ServiceHost y muestre sus valores.

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

Se pueden realizar operaciones similares desde cualquier aplicación cliente de WMI mediante scripting de Windows, Microsoft PowerShell, código administrado o nativo, el sistema tratará este ensamblado de la misma manera que trata a cualquier otro proveedor.

Implementación de métodos

WMI.NET admite métodos, tanto estáticos como por instancia. En nuestro caso, agregaremos un método para detener todos los servicios hospedados por un proceso, para permitir detener un proceso de forma limpia, sin eliminarlo mientras los servicios siguen en ejecución. Para que un método público sea visible en WMI, lo marcamos con el atributo ManagementTask .

        [ManagementTask]

        public bool StopServices(int milisecondsWait)

        {

            if (innerProcess.Id == 0)

                devuelve false;

            ServiceController[] services = ServiceController.GetServices();

 

            bool oneFailed = false;

            using (SCM scm = new SCM())

            {

                para (int svcIndex = 0; svcIndex < services. Longitud; svcIndex++)

                {

                    ServiceController crtService = services[svcIndex];

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

                    if (processId == innerProcess.Id)

                    {

                        probar

                        {

                            crtService.Stop();

                            if (milisegundosWait != 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;

        }

 Para invocar este método, necesitamos una instancia de win32_servicehost clase. Se obtiene una lista de hosts de servicio disponibles que escriben:

C:>wmic path win32_servicehost get handle,Services

y elige el que tiene la lista más benigna de servicios (por lo que no para reducir el sistema, también es posible que algunos servicios también no se puedan detener) y use su propiedad Handle para identificar la instancia de la llamada.

Ruta de acceso C:>wmic win32_servicehost. Handle="540" CALL StopServices(0)

Excepciones y informes de errores

Las excepciones son un aspecto importante de WMI.NET. La infraestructura usa algunas excepciones para comunicar información y trata la mayoría de las excepciones como no controladas.

Excepciones aceptadas

WMI.NET controla solo algunas excepciones que se describirán más adelante en el artículo. Todas las demás excepciones se consideran errores de programación y se tratan como excepciones no controladas que hacen que el host del proveedor WMI se bloquee. WMI.NET notifica excepciones no controladas en el registro de eventos de Windows.

Las excepciones aceptadas se traducen realmente en códigos de error WMI y se devuelven al código de cliente, como se muestra en la tabla 1. Por lo tanto, los proveedores de WMI.NET se comportarán como cualquier otro proveedor nativo.

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

A partir de la excepción interna, se describe más adelante en el artículo

Table1: traducción de excepciones en errores de WMI

En la lista anterior, pero dos son excepciones generales de .NET Framework. Todas las excepciones enumeradas se pueden producir para informar al cliente en el estado concreto que representan estas excepciones.

Se agregaron dos nuevas excepciones en el espacio de nombres System.Management.Instrumentation , tal y como se describe más adelante.

InstanceNotFoundException

Esta excepción sirve para notificar que no se encuentra una instancia solicitada. En el ejemplo de este artículo se usa el método estático GetInstance para el enlace a una instancia determinada y se devuelve null cuando no se encuentra la instancia. Se puede usar un constructor para el mismo propósito, pero tendrá que iniciar una excepción InstanceNotFoundException cuando no pueda encontrar la instancia necesaria. Este es el constructor para reemplazar el método estático GetInstance.

        [ManagementBind]

        public WIN32ServiceHost(string Handle)

        {

            int processId;

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

            {

                throw new InstanceNotFoundException();

            }

 

            probar

            {

                Proceso de proceso = Process.GetProcessById(processId);

                this.innerProcess = process;

                this.handle = Handle;

                si (esto. Services == null)

                {

                    throw new InstanceNotFoundException();

                }

            }

            producida por GetProcessById si no se encuentra ningún proceso con el identificador especificado.

            catch (ArgumentException)

            {

                throw new InstanceNotFoundException();

            }

        }

InstrumentationException

InstrumentationException es el contenedor para las excepciones controladas. Un proveedor puede optar por informar al cliente de un error que se produjo en su lado. Para ello, iniciará una excepción InstrumentationException. Tenga en cuenta que el desarrollador debe tener en cuenta que la excepción vuelve al sistema WMI. Por lo tanto, WMI.NET intentará lo mejor para transformarlo en un HRESULT COM. Para devolver un código de error preciso al cliente, es necesario pasar como una excepción interna para instrumentationException una clase Exception que permita establecer el HResult interno en la clase de excepción base directamente.

Informe de errores

Cualquier excepción que no aparezca en la sección anterior terminará como una excepción no controlada, lo que provocará un bloqueo notificado como "Una excepción no controlada ('System.ExecutionEngineException') se produjo en wmiprvse.exe[<NNNN>].", NNNN siendo un número de proceso. El error y la pila se notificarán en el registro de eventos. Tener símbolos en la misma carpeta que el ensamblado mostrará la pila incorrecta completada, con el nombre de archivo y el número de línea.

Otro caso de error es cuando no se puede cargar el ensamblado porque no se implementó en la GAC. WMI.NET devolverá un error de carga del proveedor (WBEM_E_PROVIDER_LOAD_FAILURE) para este caso.

Un problema frecuente durante el desarrollo del proveedor es la discrepancia entre el esquema WMI y el código. Esto puede ocurrir al implementar el nuevo ensamblado sin implementar el esquema en WMI o cuando no se detectan los problemas al implementar con InstallUtil.exe porque la información solo está disponible en tiempo de ejecución. Ese sería el caso, por ejemplo, si se devolviera un tipo incorrecto durante una enumeración. La infraestructura de WMI.NET notifica que al cliente como error de proveedor (error WMI WBEM_E_PROVIDER_FAILURE) y generará un mensaje en el registro de eventos de Windows que describe el problema en tiempo de ejecución.

Otras sugerencias

Todos los atributos y el código WMI.NET se encuentran en el espacio de nombres System.Management.Instrumentation . Para compilar un ensamblado para WMI.NET, el proyecto debe tener referencias a System.Core.dll y System.Management.Infrastructure.dll , ya que contienen la definición de atributos y el código en tiempo de ejecución, respectivamente. Más adelante sabe cómo cargar ensamblados instrumentados, hacer coincidir las clases instrumentadas con el repositorio WMI e invocarlos en consecuencia.

Mantenga todas las clases para un tipo determinado de aplicación en el mismo ensamblado. Esto facilita mucho el mantenimiento y la implementación. 

Todo el código del artículo debe implementarse y ejecutarse como administrador. En Windows Vista, la ejecución como administrador requerirá un símbolo del sistema de seguridad con privilegios elevados. Normalmente, los proveedores no requieren que los clientes sean administradores a menos que accedan a datos del sistema especialmente protegidos como en nuestro ejemplo. 

Conclusión

La nueva biblioteca de administración de .NET Framework 3.5 proporciona a los desarrolladores una herramienta eficaz en la búsqueda de escribir proveedores WMI. Dada la simplicidad del modelo de programación, escribir proveedores de WMI se convierte en una tarea bastante sencilla que le permite implementar la mayoría de las funciones de WMI. En el ejemplo de este artículo se muestra cómo escribir solo un proveedor WMI simple, pero la biblioteca también proporciona compatibilidad para el desarrollo de proveedores desacoplados e implementación de singletons, herencia, clases abstractas, referencias y clases de asociación, lo que WMI.NET Provider Extension v2 es una alternativa seria al desarrollo mediante interfaces nativas WMI.

Listado 1: SCMInterop.cs

using System;

using System.Runtime.InteropServices;

using System.ServiceProcess;

 

 

espacio de nombres External.PInvoke

{

    [StructLayout(LayoutKind.Sequential)]

    SERVICE_STATUS_PROCESS de estructura interna

    {

        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));

    }

 

    [Marcas]

    SCM_ACCESS de enumeración interna: uint

    {

        SC_MANAGER_CONNECT = 0x00001,

    }

 

    [Marcas]

    SERVICE_ACCESS de enumeración interna: 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)

    }

 

    SC_STATUS_TYPE de enumeración interna

    {

        SC_STATUS_PROCESS_INFO = 0

    }

 

    clase interna 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()

        {

            devuelve SCM. CloseServiceHandle(base.handle);

        }

 

        public override bool IsInvalid

        {

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

        }

    }

 

    clase interna 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)

        {

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

            Base. SetHandle(handle);

        }

 

        protected override bool ReleaseHandle()

        {

            devuelve 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;

            éste. QueryService(serviceName, out serviceStatus);

            return (int)serviceStatus.dwProcessId;

        }

    }

}

Listado 2: WIN32ServiceHost.cs

using System;

utilizando System.Collections;

using System.Collections.Generic;

usando System.Linq;

using System.Text;

 

using System.ServiceProcess;

using System.Diagnostics;

using External.PInvoke;

utilizando System.Management.Instrumentation;

 

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

 

 

espacio de nombres TestWMI.Hosted

{

    [System.ComponentModel.RunInstaller(true)]

    public class MyInstall : DefaultManagementInstaller

    {

    }

 

 

    [ManagementEntity(External = true)]

    abstract public class Win32_Process

    {

        identificador de cadena protegido;

 

        [ManagementKey]

        identificador de cadena pública

        {

            get {

                devuelve this.handle;

            }

        }

    }

 

    [ManagementEntity(Name = "WIN32_ServiceHost")]

    clase pública WIN32ServiceHost: Win32_Process

    {

        Process innerProcess;

 

        <Resumen>

        ///

        </Resumen>

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

        public WIN32ServiceHost(Process innerProcess)

        {

            this.innerProcess = innerProcess;

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

        }

 

        id. de int público

        {

            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. Longitud; 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 (Procesar crtProcess en procesos)

            {

                WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(crtProcess);

                if (crtServiceHost.Services != null)

                {

                    yield return crtServiceHost;

                }

            }

        }

 

        [ManagementBind]

        public WIN32ServiceHost(string Handle)

        {

            int processId;

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

            {

                throw new InstanceNotFoundException();

            }

 

            probar

            {

                Proceso de proceso = Process.GetProcessById(processId);

                this.innerProcess = process;

                this.handle = Handle;

                si (esto. Services == null)

                {

                    throw new InstanceNotFoundException();

                }

            }

            producida por GetProcessById si no se encuentra ningún proceso con el identificador especificado.

            catch (ArgumentException)

            {

                throw new InstanceNotFoundException();

            }

        }

 

        static public WIN32ServiceHost GetInstance(string Handle)

        {

            int processId;

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

            {

                return null;

            }

 

            probar

            {

                Proceso de proceso = Process.GetProcessById(processId);

                WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(process);

                if (crtServiceHost.Services != null)

                {

                    devuelve crtServiceHost;

                }

                else

                {

                    return null;

                }

            }

            producida por GetProcessById si no se encuentra ningún proceso con el identificador especificado.

            catch (ArgumentException)

            {

                return null;

            }

        }

 

        [ManagementTask]

        public bool StopServices(int milisecondsWait)

        {

            if (innerProcess.Id == 0)

                return false;

            ServiceController[] services = ServiceController.GetServices();

 

            bool oneFailed = false;

            using (SCM scm = new SCM())

            {

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

                {

                    ServiceController crtService = services[svcIndex];

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

                    if (processId == innerProcess.Id)

                    {

                        probar

                        {

                            crtService.Stop();

                            if (milisegundosWait != 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. Todos los derechos reservados.