Écriture de fournisseurs WMI couplés à l’aide de WMI.NET Provider Extension 2.0

Écriture de fournisseurs WMI couplés à l’aide de WMI.NET Provider Extension 2.0

Gabriel Ghizila

Microsoft Corporation

Janvier 2008

 

Résumé: Détails sur l’écriture d’un fournisseur WMI couplé à l’aide de WMI.NET’extension de fournisseur 2.0 fournie dans .NET Framework 3.5

Contenu

Introduction

Une classe .NET simple

Attribut au niveau de l’assembly

Attributs WMI.NET au niveau de la classe

Conditions requises pour le runtime

Inscription auprès de WMI

Extension des classes via l’héritage

Implémentation de méthodes

Rapports d’exceptions et d’erreurs

Autres conseils

Conclusion

Listing 1 – SCMInterop.cs

Listing 2 – WIN32ServiceHost.cs

 

Introduction

Windows Management Instrumentation (WMI) est une infrastructure largement utilisée pour gérer les applications Windows et Windows. Bien qu’ils soient très extensibles et populaires parmi les administrateurs système et les applications de gestion, de nombreux développeurs pensent à l’écriture de fournisseurs WMI en raison de la complexité des interfaces natives qui doivent être implémentées.

Alors que les versions initiales du .NET Framework étaient fournies avec un ensemble d’objets et de modèles pour implémenter des fournisseurs WMI, celles-ci étaient limitées à la gestion des applications, elles ne vous permettaient pas de définir des méthodes et les clés pour les instances étaient générées automatiquement. WMI.NET Provider Extension v2 (WMI.NET) est une nouvelle infrastructure dans Orcas (.NET Framework 3.5) qui vous permet d’implémenter un ensemble complet de fonctionnalités de fournisseur WMI. Cette nouvelle infrastructure coexiste avec le modèle de fournisseur WMI.NET des versions précédentes, mais elle est beaucoup plus puissante et extensible.

Le présent article se concentre sur la façon d’écrire des fournisseurs couplés WMI, qui est l’une des fonctionnalités les plus significatives dans WMI.NET. Il n’y a pas de différences significatives dans la façon dont les fournisseurs découplés sont écrits afin que l’article puisse donner un bon départ pour un lecteur qui tente d’écrire des fournisseurs WMI de tout type à l’aide de WMI.NET. L’article montre comment créer un fournisseur WMI couplé à partir d’une classe .NET simple, puis l’enrichir avec quelques fonctionnalités supplémentaires. L’objectif est de pouvoir énumérer les processus hébergeant des services Windows et de pouvoir énumérer les services Windows dans chacun de ces processus et être en mesure d’intégrer cette fonctionnalité dans WMI.

Une classe .NET simple

Pour commencer, nous allons créer une classe C# qui modélisera un processus hébergeant des services Windows. Chaque instance affiche une liste des services hébergés dans le processus associé. La classe a une méthode statique qui retourne toutes les instances associées aux hôtes de service s’exécutant dans le système.

    classe publique WIN32ServiceHost

    {

La classe est un wrapper autour de la classe Process. Le champ innerProcess stocke une référence à l’objet Process.

        Traiter innerProcess ;

La classe a un constructeur qui accepte un objet process en tant que paramètre. 

        public WIN32ServiceHost(Process innerProcess)

        {

            this.innerProcess = innerProcess;

        }

Nous incluons un accesseur pour l’ID de processus.

        public int ID

        {

            get { return this.innerProcess.Id; }

        }

La propriété Services renvoie un tableau avec les noms des services hébergés. Cette propriété a la valeur Null si aucun service n’est en cours d’exécution dans le processus.

        public string[] Services

        {

            get

            {

Le processus inactif n’héberge aucun service.

                if (innerProcess.Id == 0)

                    retourne null ;

Obtenir la liste de tous les services Windows sur le système

                ServiceController[] services = ServiceController.GetServices();

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

                using (SCM scm = new SCM())

                {

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

                    {

                        ServiceController crtService = services[svcIndex];

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

Comparez l’ID du processus dans lequel un service s’exécute à l’ID du processus actuel.

                        if (processId == innerProcess.Id)

                        {

                            servicesForProcess.Add(services[svcIndex]);

                        }

                    }

                }

 

                if (servicesForProcess.Count == 0)

                    retourne null ;

Préparer, remplir et retourner le tableau avec les noms du service

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

 

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

                {

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

                }

                return servicesNames;

            }

        }

EnumerateServiceHosts est une méthode statique qui retourne un IEnumerable pour passer par tous les hôtes de service en cours d’exécution.

        static public IEnumerable EnumerateServiceHosts()

        {

            Process[] processes = Process.GetProcesses();

            foreach (Process crtProcess dans les processus)

            {

                WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(crtProcess);

                if (crtServiceHost.Services != null)

                {

                    yield return crtServiceHost;

                }

            }

        }

    }

Un développeur peut utiliser cette classe à partir de n’importe quelle application .NET. Toutefois, dans ce cas, d’autres applications de gestion n’auront aucun moyen de l’utiliser. WMI.NET a les crochets pour exposer la classe dans cet exemple au monde WMI et nous allons expliquer le processus dans les paragraphes suivants. WMI fournit un modèle permettant de tirer parti de ces objets et de les intégrer à des applications de gestion à l’échelle de l’entreprise comme Systems Management Server ou Operations Manager, de fournir une interaction à distance et vous permet de voir et d’utiliser cette classe à partir de plusieurs plateformes.

Attribut au niveau de l’assembly

La première étape pour exposer un assembly pour l’instrumentation à l’aide de WMI.NET consiste à définir un attribut WmiConfiguration au niveau de l’assembly. Cet attribut marque l’assembly comme un assembly qui implémente un fournisseur de WMI.NET et vous permet de configurer diverses choses sur les classes implémentées par le fournisseur, y compris l’espace de noms dans lequel elles seront exposées. Pour notre exemple, nous allons définir l’espace de noms WMI comme root\Test et définir le modèle d’hébergement sur le modèle de fournisseur couplé dans le contexte de sécurité NetworkService. Notez que toutes les opérations seront exécutées dans le contexte de sécurité de l’utilisateur appelant via l’emprunt d’identité.

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

Attributs WMI.NET au niveau de la classe

Pour instrumenter une classe à l’aide de WMI.NET, la classe, ses méthodes, ses champs et ses propriétés exposés à WMI.NET doivent être publics et correctement marqués avec des attributs WMI.NET. Les attributs sont utilisés pour générer les métadonnées requises par WMI pour utiliser la classe C# existante en tant que classe WMI.

Instrumentation d’une classe .NET

L’attribut ManagementEntity marque une classe .NET comme étant instrumentée. Au moment du déploiement, l’infrastructure WMI.NET génère une classe WMI correspondante, avec le même nom, dans le référentiel WMI. Pour modifier ce nom, nous devons fournir le paramètre nommé Name dans la liste d’arguments de l’attribut ManagementEntity . Name est utilisé comme paramètre nommé pour modifier le nom d’instrumentation de la plupart des attributs. Pour notre exemple, nous choisissons de nommer la classe WMI WIN32_ServiceHost sans renommer la classe .NET.

    [ManagementEntity(Name = « WIN32_ServiceHost »)]

    classe publique WIN32ServiceHost

Notez que le nommage d’une entité peut être un peu difficile.  C# respecte la casse, mais pas WMI. Par conséquent, tous les noms ne respectent pas la casse du point de vue de l’infrastructure WMI.NET. Si les noms de deux champs ou de deux méthodes membres de la même classe diffèrent uniquement selon la casse, l’assembly ne parvient pas à s’inscrire auprès de WMI.

Contrôle du schéma de classe WMI

La charge utile d’un instance est donnée par ses propriétés. Nous devons marquer toutes les propriétés à refléter dans le monde WMI avec les attributs ManagementProbe, ManagementConfiguration ou ManagementKey . ManagementProbe est l’attribut permettant de marquer les propriétés exposées comme lues uniquement dans WMI. Dans notre exemple, la propriété Services est la propriété de charge utile que nous voulons exposer au monde de gestion. Il montre un état d’un processus qui ne peut pas être modifié directement. Nous allons donc le marquer avec ManagementProbe. Pour les propriétés en lecture/écriture, l’attribut ManagementConfiguration doit être utilisé, auquel cas la propriété elle-même doit avoir à la fois un setter et un getter.

        [ManagementProbe]

        chaîne publique[] Services

L’ID fait également partie de la charge utile de WIN32ServiceHost, car il identifie le processus lui-même. Les propriétés qui identifient de manière unique un instance de leur classe sont les clés de cette classe dans le monde WMI. Toutes les classes de WMI.NET doivent définir des clés et les implémenter pour identifier de manière unique des instances, sauf si elles sont abstraites, singleton ou héritent d’une classe qui définit déjà des clés. Les clés sont marquées avec l’attribut ManagementKey . Dans cet exemple, l’ID de processus identifie un processus de manière unique ; par conséquent, il identifie de manière unique les instances de notre classe.

        [ManagementKey]

        ID d’int public

Conditions requises pour l’exécution

Le marquage de la classe et de sa collection de propriétés permet d’exposer le schéma de classe à WMI, mais il ne suffit pas que les classes exposent les données réelles. Nous devons définir des points d’entrée pour l’obtention, la création ou la suppression d’un instance ainsi que l’énumération d’instances. Nous fournirons du code pour instance énumération ainsi que la récupération d’un instance particulier, ce qui est pratiquement le minimum pour un fournisseur WMI fonctionnel. 

Énumération des instances

Pour énumérer des instances, WMI.NET attend une méthode publique statique sans paramètres qui retourne une interface IEnumerable . Il doit être statique car il implémente des fonctionnalités pertinentes pour l’ensemble de la classe. Cette méthode doit être marquée avec l’attribut ManagementEnumerator . Déjà défini dans notre exemple, EnumerateServiceHosts répond à toutes les conditions requises afin qu’il puisse être simplement attribué et utilisé à cette fin.

        [ManagementEnumerator]

        IEnumerable EnumerateServiceHosts() public statique

Il est vraiment important pour cette méthode de s’assurer que chaque élément retourné dans l’énumération est un instance de la classe instrumentée. Sinon, une erreur d’exécution se produit.

Liaison à un instance

Pour pouvoir récupérer une instance particulière (appelée liaison dans WMI.NET), nous devons disposer d’une méthode qui retourne un instance en fonction des valeurs des clés qui l’identifient. Nous avons besoin d’une méthode qui a le même nombre de paramètres que les clés, les paramètres ayant les mêmes noms et types que les clés. Le type de retour doit être la classe instrumentée elle-même. Nous allons utiliser une méthode statique et, pour associer les clés de classe aux paramètres, nous devons spécifier les mêmes noms instrumentés pour les paramètres que pour les clés.  Dans notre exemple, ID est la clé de la classe WIN32_ServiceHost et nous devons nommer le paramètre pour notre ID de méthode de liaison ou utiliser l’attribut ManagementName pour exposer le paramètre à WMI sous le nom « ID ». L’infrastructure WMI.NET reconnaît une méthode de liaison ou un constructeur lorsqu’elle est marquée avec l’attribut ManagementBind .

        [ManagementBind]

        public statique WIN32ServiceHost GetInstance([ManagementName(« ID »)] int processId)

        {

            Essayer

            {

                Processus = Process.GetProcessById(processId) ;

                WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(process) ;

                if (crtServiceHost.Services != null)

                {

                    retourner crtServiceHost ;

                }

                else

                {

                    retour null ;

                }

            }

            levée par GetProcessById si aucun processus avec l’ID donné n’est trouvé

            catch (ArgumentException)

            {

                retour null ;

            }

        }

Il est important de noter que si le instance est introuvable, la méthode retourne null. Dans notre cas, nous le ferons si nous ne trouvons pas du tout le processus demandé ou si le processus trouvé n’héberge aucun service Windows.

Inscription auprès de WMI

La classe est prête à effectuer son travail, mais nous devons toujours l’inscrire auprès de WMI et la placer dans un emplacement accessible sur le disque à partir duquel le chargement doit être effectué.

Le Global Assembly Cache (GAC) est l’emplacement où nous voulons que l’assembly se trouve. De cette façon, .NET peut le récupérer par son nom complet .NET. Notez que l’assembly doit avoir un nom fort .NET pour être inscrit dans le GAC. Pour notre exemple, nous allons utiliser l’outil .NET gacutil.exe pour stocker l’assembly dans le GAC.

Pour obtenir les informations de l’assembly instrumenté dans les métadonnées WMI, nous devons utiliser l’outil .NET InstallUtil.exe pour appeler une classe WMI.NET nommée DefaultManagementInstaller. DefaultManagementInstaller sait comment analyser l’ensemble de l’assembly pour les classes instrumentées et générer les métadonnées WMI correspondantes. Comme InstallUtil.exe a besoin d’une classe marquée avec l’attribut RunInstaller , nous allons définir une classe vide dérivée de DefaultManagementInstaller que InstallUtil.exe appellera.

    [System.ComponentModel.RunInstaller(true)]

    classe publique MyInstall : DefaultManagementInstaller

    {

    }

L’inscription auprès de WMI peut être effectuée hors connexion ou en ligne. L’inscription en ligne stocke les métadonnées d’instrumentation d’un assembly directement dans le référentiel WMI. Pour installer directement les métadonnées dans le référentiel WMI, InstallUtil.exe commande est appelée avec le nom de l’assembly comme paramètre. L’assembly doit être dans GAC avant d’exécuter InstallUtil.exe. Pour l’assembly généré dans cet exemple nommé WMIServiceHost.dll nous allons utiliser les commandes suivantes :

C :>gacutil.exe /i WMIServiceHost.dll

C :>Installutil.exe WMIServiceHost.dll

L’inscription hors connexion nécessite deux étapes. La première étape consiste à générer les métadonnées WMI associées à l’assembly et à le stocker dans un fichier MOF. La deuxième étape est l’inscription réelle sur les machines cibles à l’aide de l’outilmofcomp.exe ou dans le cadre d’un package d’installation. L’avantage de l’inscription hors connexion est que le fichier MOF peut être localisé et modifié en fonction des besoins. Pour cet exemple, nous pouvons générer et stocker les métadonnées WMI dans un fichier nommé WMIServiceHost.mof à l’aide du paramètre MOF comme suit :

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

Comme dans le cas en ligne, l’assembly doit se trouver dans le GAC sur l’ordinateur cible. Pour valider le déploiement, nous pouvons utiliser l’outil système wmic.exe pour voir les instances et les valeurs de cette classe.

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

Pendant le développement, il est utile de déployer les symboles dans le même dossier dans le GAC où l’assembly est stocké. De cette façon, toute pile signalée à la suite d’une erreur ou d’un incident inclura le chemin d’accès source complet et les numéros de ligne qui vous aideront à identifier le morceau de code incriminé.

Extension des classes via l’héritage

WIN32_ServiceHost concerne les hôtes de service et les informations fournies sont limitées aux processus hébergeant des services Windows. Il sera intéressant d’étendre ces informations pour inclure des informations spécifiques au processus telles que l’utilisation de la mémoire, le chemin exécutable, l’ID de session, etc. Pour obtenir ces informations, nous pouvons étendre le schéma et écrire du code supplémentaire pour récupérer autant d’informations que nécessaire. Une bonne alternative à l’écriture de code supplémentaire consiste à tirer parti de la classe WIN32_Process déjà existante qui se trouve sur le système d’exploitation prête à l’emploi dans l’espace de noms root\cimv2 et qui fournit toutes ces informations supplémentaires pour tout processus s’exécutant dans le système. Cette classe fournit des informations détaillées sur un processus en cours d’exécution et nous pouvons utiliser la dérivation WMI pour l’étendre avec notre propre classe.

L’héritage WMI est traduit dans le modèle de codage WMI.NET en héritage de classe. Étant donné que la classe dont nous voulons dériver est une classe de l’espace WMI que nous n’implémentons pas réellement dans le code, nous devons la marquer de manière spécifique.

Avant de commencer à écrire la dérivation, nous devons noter deux points importants concernant la classe WIN32_Process . La première WIN32_Process se trouve dans l’espace de noms root\cimv2 et la dérivation nous oblige à inscrire la classe win32_servicehost dans le même espace de noms. Par conséquent, nous allons modifier légèrement l’instruction d’attribut WmiConfiguration .

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

En outre, notre superclasse, win32_process, a la propriété Handle définie comme clé. Cette clé est de type CIM_STRING qui se traduit par . System.String de NET. Nous devrons arrêter d’utiliser ID comme propriété de clé et utiliser la propriété Handle à la place.

Pour définir une classe externe dans WMI.NET pour qu’elle corresponde à win32_process nous miroir simplement son schéma, mais n’incluons que les propriétés que nous voulons ou devons utiliser. Les clés de la hiérarchie de classes sont toujours requises. À ce stade, Handle est la seule propriété intéressante, car il s’agit de la clé et nous en aurons besoin pour la liaison à un instance spécifique.

    [ManagementEntity(External = true)]

    Win32_Process de classe publique abstraite

    {

        handle de chaîne protégé ;

 

        [ManagementKey]

        public string Handle

        {

            get {

                retourne this.handle ;

            }

        }

    }

La définition de External sur true sur l’attribut ManagementEntity empêche l’infrastructure de générer des métadonnées WMI au moment du déploiement, mais conserve les informations déclarées à des fins d’utilisation au moment de l’exécution lorsqu’elle doit trouver des clés et des propriétés pour les classes dérivées. Notez qu’il est très important de contrôler le contenu des clés de la classe de base, car les clés sont utilisées par le sous-système WMI pour fusionner les informations de différents fournisseurs.

Pour obtenir la classe WMI WIN32_ServiceHost hériter de la classe WMI Win32Process, nous dérivons WIN32ServiceHost de la classe abstraite nouvellement créée dans le monde .NET

    [ManagementEntity(Name = « WIN32_ServiceHost »)]

    classe publique WIN32ServiceHost : Win32_Process

   

Nous supprimons la propriété ID.

        [ManagementKey]

        public int ID

        {

            get { return this.innerProcess.Id; }

        }

modifier le constructeur pour remplir la nouvelle clé dans le champ handle de la classe de base

        public WIN32ServiceHost(Process innerProcess)

        {

            this.innerProcess = innerProcess;

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

        }

modifiez GetInstance pour qu’il fonctionne avec un argument de chaîne nommé Handle, les premières lignes de celui-ci étant les mêmes que le reste.

        [ManagementBind]

        static public WIN32ServiceHost GetInstance(string Handle)

        {

            int processId;

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

            {

                retourne null ;

            }

 

            Essayer

            [...]

Nous devons recompiler et redéployer le nouvel assembly dans GAC. Nous utilisons InstallUtil.exe pour déployer le nouveau schéma. Nous pouvons interroger le système à l’aide d’une commande wmic.exe légèrement modifiée.

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

Les instances retournées sont remplies avec les informations des deux classes, win32_process et win32_servicehost. Dans la sortie, les services proviennent de win32_servicehost tandis que tout le reste provient de win32_process. Pour simplifier la sortie, nous pouvons spécifier les colonnes souhaitées.

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

Cela devient encore plus intéressant lorsque nous essayons d’énumérer win32_process. Une telle requête retourne tous les processus et remplit le champ Services uniquement pour les instances de win32_servicehost.

C:>wmic PATH win32_process get /value

La sortie peut être un peu écrasante. Il suffit donc de la vider dans un fichier (en ajoutant > out.txt à la fin de la ligne de commande) et de l’ouvrir dans le Bloc-notes pour rechercher la propriété Services. Pour comprendre ce qui se passe, nous pouvons afficher les propriétés système pour identifier, pour chaque instance, de quelle classe WMI il provient.

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

Dans la liste résultante, choisissez un win32_ServiceHost instance et affichez ses valeurs.

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

Des opérations similaires peuvent être effectuées à partir de n’importe quelle application cliente WMI à l’aide de scripts Windows, de Microsoft PowerShell, de code managé ou natif. Le système traitera cet assembly de la même façon qu’il traite n’importe quel autre fournisseur.

Implémentation de méthodes

WMI.NET prend en charge les méthodes, statiques et par instance. Dans notre cas, nous allons ajouter une méthode pour arrêter tous les services hébergés par un processus, afin de permettre d’arrêter proprement un processus, sans le tuer pendant que les services sont en cours d’exécution. Pour rendre une méthode publique visible dans WMI, nous la marqueons avec l’attribut ManagementTask .

        [ManagementTask]

        public bool StopServices(int millisecondsWait)

        {

            if (innerProcess.Id == 0)

                retourne false ;

            ServiceController[] services = ServiceController.GetServices();

 

            bool oneFailed = false;

            using (SCM scm = new SCM())

            {

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

                {

                    ServiceController crtService = services[svcIndex];

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

                    if (processId == innerProcess.Id)

                    {

                        Essayer

                        {

                            crtService.Stop();

                            if (millisecondesWait != 0)

                            {

                                crtService.WaitForStatus( ServiceControllerStatus.Stopped,

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

                            }

                        }

                        catch (System.ServiceProcess.TimeoutException)

                        {

                            oneFailed = true;

                        }

                        catch (System.ComponentModel.Win32Exception)

                        {

                            oneFailed = true;

                        }

                        catch (InvalidOperationException)

                        {

                            oneFailed = true;

                        }

                    }

                }

            }

            return !oneFailed;

        }

 Pour appeler cette méthode, nous avons besoin d’une instance de win32_servicehost classe. Nous obtenons la liste des hôtes de service disponibles en tapant :

Chemin D’accès C:>wmic win32_servicehost obtenir le handle,Services

et choisissez celui avec la liste de services la plus bénigne (afin de ne pas mettre votre système hors service, certains services peuvent également ne pas être arrêtables) et utilisez sa propriété Handle pour identifier le instance de l’appel.

Chemin D’accès C:>wmic win32_servicehost. Handle="540 » CALL StopServices(0)

Rapports d’exceptions et d’erreurs

Les exceptions sont un aspect important de WMI.NET. L’infrastructure utilise certaines exceptions pour communiquer des informations et traite la plupart des exceptions comme non gérées.

Exceptions acceptées

WMI.NET ne gère que quelques exceptions qui seront décrites plus en détail dans l’article. Toutes les autres exceptions sont considérées comme des erreurs de programmation et traitées comme des exceptions non gérées provoquant le blocage de l’hôte du fournisseur WMI. WMI.NET signale des exceptions non gérées dans le journal des événements Windows.

Les exceptions acceptées sont en fait traduites en codes d’erreur WMI et renvoyées au code client, comme indiqué dans le tableau 1. Par conséquent, les fournisseurs de WMI.NET se comportent comme n’importe quel autre fournisseur natif.

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

À partir de l’exception interne, décrite plus en détail dans l’article

Table1 : traduction d’exceptions en erreurs WMI

Dans la liste ci-dessus, sauf deux, figurent des exceptions .NET Framework générales. Toutes les exceptions répertoriées peuvent être levées pour signaler au client la status ces exceptions représentent.

Deux nouvelles exceptions ont été ajoutées dans l’espace de noms System.Management.Instrumentation , comme décrit plus loin.

InstanceNotFoundException

Cette exception sert à notifier qu’un instance demandé est introuvable. L’exemple de cet article utilise la méthode statique GetInstance pour la liaison à un instance donné et retourne null lorsque le instance est introuvable. Un constructeur peut être utilisé dans le même but, mais il doit lever une instance InstanceNotFoundException lorsque n’est pas en mesure de trouver le instance requis. Voici le constructeur pour remplacer la méthode statique GetInstance.

        [ManagementBind]

        public WIN32ServiceHost(handle de chaîne)

        {

            int processId ;

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

            {

                lever la nouvelle InstanceNotFoundException();

            }

 

            Essayer

            {

                Processus = Process.GetProcessById(processId) ;

                this.innerProcess = processus ;

                this.handle = Handle ;

                si (ceci. Services == null)

                {

                    lever la nouvelle InstanceNotFoundException();

                }

            }

            levée par GetProcessById si aucun processus avec l’ID donné n’est trouvé

            catch (ArgumentException)

            {

                lever la nouvelle InstanceNotFoundException();

            }

        }

InstrumentationException

InstrumentationException est le wrapper pour les exceptions gérées. Un fournisseur peut choisir d’informer le client d’une erreur qui s’est produite de son côté. Pour ce faire, une instrumentationException est levée. Notez que le développeur doit avoir à l’esprit que l’exception revient dans le système WMI. Par conséquent, WMI.NET fera de son mieux pour le transformer en COM HRESULT. Pour renvoyer un code d’erreur précis au client, nous devons transmettre en tant qu’exception interne pour instrumentationException une classe exception qui permet de définir directement le HResult interne dans la classe d’exception de base.

Rapport d’erreurs

Toute exception non répertoriée dans la section précédente se termine par une exception non prise en charge, ce qui entraîne un incident signalé comme « Une exception non prise en charge (« System.ExecutionEngineException ») s’est produite dans wmiprvse.exe[<NNNN>]. », NNNN étant un numéro de processus. L’erreur et la pile seront signalées dans le journal des événements. Le fait d’avoir des symboles dans le même dossier que l’assembly affiche la pile incriminable terminée, avec le nom de fichier et le numéro de ligne.

Un autre cas d’erreur est lorsque l’assembly ne peut pas être chargé, car il n’a pas été déployé dans le GAC. WMI.NET retourne un échec de charge du fournisseur (WBEM_E_PROVIDER_LOAD_FAILURE) dans ce cas.

Un problème fréquent pendant le développement du fournisseur est l’incompatibilité entre le schéma WMI et le code. Cela peut se produire lors du déploiement du nouvel assembly sans déployer le schéma dans WMI ou lorsque les problèmes ne sont pas détectés lors du déploiement à l’aide de InstallUtil.exe, car les informations sont disponibles uniquement au moment de l’exécution. C’est le cas, par exemple, si un type incorrect a été retourné lors d’une énumération. L’infrastructure WMI.NET signale cela au client en tant que défaillance du fournisseur (WBEM_E_PROVIDER_FAILURE d’erreur WMI) et génère un message dans le journal des événements Windows décrivant le problème d’exécution.

Autres conseils

Tous les attributs et le code WMI.NET se trouvent dans l’espace de noms System.Management.Instrumentation . Pour générer un assembly pour WMI.NET, le projet doit avoir des références à System.Core.dll et System.Management.Infrastructure.dll , car ils contiennent respectivement la définition des attributs et le code d’exécution. Ce dernier sait comment charger des assemblys instrumentés, faire correspondre les classes instrumentées sur le référentiel WMI et les appeler en conséquence.

Conservez toutes les classes d’un type particulier d’application dans le même assembly. Cela facilite grandement la maintenance et le déploiement. 

Tout le code de l’article doit être déployé et exécuté en tant qu’administrateur. Sur Windows Vista, l’exécution en tant qu’administrateur nécessite une invite de commandes de sécurité avec élévation de privilèges. Les fournisseurs n’exigent généralement pas que les clients soient administrateurs, sauf s’ils accèdent à des données système spécialement sécurisées, comme dans notre exemple. 

Conclusion

La nouvelle bibliothèque de gestion de .NET Framework 3.5 offre aux développeurs un outil puissant dans la quête d’écriture de fournisseurs WMI. Compte tenu de la simplicité du modèle de programmation, l’écriture de fournisseurs WMI devient une tâche assez facile qui vous permet d’implémenter la plupart des fonctionnalités WMI. L’exemple de cet article montre comment écrire uniquement un fournisseur WMI simple, mais la bibliothèque fournit également la prise en charge du développement de fournisseurs découplés et l’implémentation de singletons, d’héritage, de classes abstraites, de références et de classes d’association faisant de WMI.NET’extension de fournisseur v2 une alternative sérieuse au développement à l’aide d’interfaces natives WMI.

Listing 1 – SCMInterop.cs

using System;

à l’aide de System.Runtime.InteropServices ;

à l’aide de System.ServiceProcess ;

 

 

espace de noms External.PInvoke

{

    [StructLayout(LayoutKind.Sequential)]

    SERVICE_STATUS_PROCESS de structure interne

    {

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

    }

 

    [Indicateurs]

    SCM_ACCESS d’énumération interne : uint

    {

        SC_MANAGER_CONNECT = 0x00001,

    }

 

    [Indicateurs]

    enum interne 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)

    }

 

    SC_STATUS_TYPE d’énumération interne

    {

        SC_STATUS_PROCESS_INFO = 0

    }

 

    classe interne 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()

        {

            retourne le SCM. CloseServiceHandle(base.handle) ;

        }

 

        public override bool IsInvalid

        {

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

        }

    }

 

    SCM de classe interne : 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,

                                    Chaîne [MarshalAs(UnmanagedType.LPWStr)] 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()

        {

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

                si (!scmRet)

                {

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

                                                                    « SCM. QueryServiceStatusEx »);

                }

            }

        }

 

        public int GetProcessId(string serviceName)

        {

            SERVICE_STATUS_PROCESS serviceStatus ;

            Ce. QueryService(serviceName, out serviceStatus) ;

            return (int)serviceStatus.dwProcessId ;

        }

    }

}

Listing 2 – WIN32ServiceHost.cs

using System;

à l’aide de System.Collections ;

using System.Collections.Generic;

à l’aide de System.Linq ;

using System.Text;

 

à l’aide de System.ServiceProcess ;

à l’aide de System.Diagnostics ;

à l’aide de External.PInvoke ;

à l’aide de System.Management.Instrumentation ;

 

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

 

 

espace de noms TestWMI.Hosted

{

    [System.ComponentModel.RunInstaller(true)]

    classe publique MyInstall : DefaultManagementInstaller

    {

    }

 

 

    [ManagementEntity(External = true)]

    Win32_Process de classe publique abstraite

    {

        handle de chaîne protégé ;

 

        [ManagementKey]

        handle de chaîne publique

        {

            get {

                retourne this.handle ;

            }

        }

    }

 

    [ManagementEntity(Name = « WIN32_ServiceHost »)]

    classe publique WIN32ServiceHost : Win32_Process

    {

        Traiter innerProcess ;

 

        <Résumé>

        ///

        </Résumé>

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

        public WIN32ServiceHost(Process innerProcess)

        {

            this.innerProcess = innerProcess ;

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

        }

 

        ID d’int public

        {

            get { return this.innerProcess.Id; }

        }

 

        [ManagementProbe]

        chaîne publique[] Services

        {

            get

            {

                if (innerProcess.Id == 0)

                    retour null ;

                ServiceController[] services = ServiceController.GetServices();

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

                using (SCM scm = new SCM())

                {

                    pour (int svcIndex = 0 ; services svcIndex < . Longueur; svcIndex++)

                    {

                        ServiceController crtService = services[svcIndex];

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

                        if (processId == innerProcess.Id)

                        {

                            servicesForProcess.Add(services[svcIndex]);

                        }

                    }

                }

 

                if (servicesForProcess.Count == 0)

                    retour 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 dans les processus)

            {

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

            }

 

            Essayer

            {

                Process process = Process.GetProcessById(processId);

                this.innerProcess = process;

                this.handle = Handle;

                si (ceci. Services == null)

                {

                    throw new InstanceNotFoundException();

                }

            }

            levée par GetProcessById si aucun processus avec l’ID donné n’est trouvé

            catch (ArgumentException)

            {

                throw new InstanceNotFoundException();

            }

        }

 

        static public WIN32ServiceHost GetInstance(string Handle)

        {

            int processId;

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

            {

                retourne null ;

            }

 

            Essayer

            {

                Process process = Process.GetProcessById(processId);

                WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(process);

                if (crtServiceHost.Services != null)

                {

                    return crtServiceHost;

                }

                else

                {

                    retourne null ;

                }

            }

            levée par GetProcessById si aucun processus avec l’ID donné n’est trouvé

            catch (ArgumentException)

            {

                retourne null ;

            }

        }

 

        [ManagementTask]

        public bool StopServices(int millisecondsWait)

        {

            if (innerProcess.Id == 0)

                retourne false ;

            ServiceController[] services = ServiceController.GetServices();

 

            bool oneFailed = false;

            using (SCM scm = new SCM())

            {

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

                {

                    ServiceController crtService = services[svcIndex];

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

                    if (processId == innerProcess.Id)

                    {

                        Essayer

                        {

                            crtService.Stop();

                            if (millisecondesWait != 0)

                            {

                                crtService.WaitForStatus( ServiceControllerStatus.Stopped,

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

                            }

                        }

                        catch (System.ServiceProcess.TimeoutException)

                        {

                            oneFailed = true;

                        }

                        catch (System.ComponentModel.Win32Exception)

                        {

                            oneFailed = true;

                        }

                        catch (InvalidOperationException)

                        {

                            oneFailed = true;

                        }

                    }

                }

            }

            return !oneFailed;

        }

    }

}

 

 

 

© Microsoft Corporation 2008. Tous droits réservés.