Scrittura di provider WMI associati con estensione provider WMI.NET 2.0
Scrittura di provider WMI associati con estensione provider WMI.NET 2.0
Gabriel Ghisila
Microsoft Corporation
Gennaio 2008
Riepilogo: Dettagli su come scrivere un provider WMI associato usando WMI.NET Estensione provider 2.0 fornita in .NET Framework 3.5
Contenuto
Introduzione
Classe .NET semplice
Attributo a livello di assembly
Attributi di WMI.NET livello di classe
Requisiti di runtime
Registrazione con WMI
Estensione delle classi tramite ereditarietà
Implementazione dei metodi
Eccezioni e segnalazione errori
Altri suggerimenti
Conclusione
Elenco 1 - SCMInterop.cs
Elenco 2 – WIN32ServiceHost.cs
Introduzione
Strumentazione gestione Windows (WMI) è un'infrastruttura ampiamente usata per gestire applicazioni Windows e Windows. Nonostante sia molto estendibile e diffuso tra gli amministratori di sistema e le applicazioni di gestione, molti sviluppatori pensano alla scrittura di provider WMI a causa della complessità delle interfacce native che devono essere implementate.
Sebbene le versioni iniziali di .NET Framework siano state create con un set di oggetti e modelli per implementare provider WMI, tali versioni erano limitate alla gestione delle applicazioni, non consentono di definire metodi e le chiavi per le istanze vengono generate automaticamente. WMI.NET Provider Extension v2 (WMI.NET) è una nuova infrastruttura in Orcas (.NET Framework 3.5) che consente di implementare un set completo di funzionalità del provider WMI. Questa nuova infrastruttura coesiste con il modello provider di WMI.NET delle versioni precedenti, ma è molto più potente ed estendibile.
L'obiettivo del presente articolo è come scrivere provider di coppia WMI, che è una delle funzionalità più significative in WMI.NET. Non esistono differenze significative nel modo in cui i provider disaccoppiati vengono scritti in modo che l'articolo possa dare un buon inizio per un lettore che tenta di scrivere provider WMI di qualsiasi tipo usando WMI.NET. L'articolo illustra come creare un provider WMI associato a partire da una semplice classe .NET e quindi arricchire con alcune funzionalità aggiuntive. L'obiettivo è quello di poter enumerare i processi che ospitano i servizi Windows e di poter enumerare i servizi Windows in ogni processo e poter integrare questa funzionalità in WMI.
Classe .NET semplice
Per iniziare, creeremo una classe C# che modellare un processo che ospita i servizi Windows. Ogni istanza mostrerà un elenco di servizi ospitati nel processo associato. La classe ha un metodo statico che restituisce tutte le istanze associate agli host di servizio in esecuzione nel sistema.
classe public WIN32ServiceHost
{
La classe è un wrapper intorno alla classe Process. Il campo innerProcess archivierà un riferimento all'oggetto Process.
Process innerProcess;
La classe ha un costruttore che accetta un oggetto process come parametro.
public WIN32ServiceHost(Process innerProcess)
{
this.innerProcess = innerProcess;
}
È inclusa una funzione di accesso per l'ID processo.
public int ID
{
get { return this.innerProcess.Id; }
}
La proprietà Services restituirà una matrice con i nomi dei servizi ospitati. Questa proprietà è Null se non vengono eseguiti servizi nel processo.
public string[] Services
{
get
{
Il processo inattiva non ospita alcun servizio.
if (innerProcess.Id == 0)
restituisce Null;
Ottenere un elenco di tutti i servizi windows nel sistema
ServiceController[] services = ServiceController.GetServices();
Elencare<i servizi ServiceControllerForProcess> = nuovo Elenco<ServiceController>();
using (SCM scm = new SCM())
{
for (int svcIndex = 0; svcIndex < services. Lunghezza; svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm. GetProcessId(crtService.ServiceName);
Confrontare l'ID del processo in esecuzione in un servizio all'ID del processo corrente.
if (processId == innerProcess.Id)
{
servicesForProcess.Add(services[svcIndex]);
}
}
}
if (servicesForProcess.Count == 0)
restituisce Null;
Preparare, popolare e restituire la matrice con i nomi del servizio
string[] servicesNames = new string[servicesForProcess.Count];
for (int serviceIdx = 0; serviceIdx < servicesForProcess.Count; serviceIdx++)
{
servicesNames[serviceIdx] = servicesForProcess[serviceIdx]. Servicename;
}
return servicesNames;
}
}
EnumerateServiceHosts è un metodo statico che restituirà un oggetto IEnumerable per passare attraverso tutti gli host del servizio in esecuzione.
EnumerateHosts() statico pubblico IEnumerable EnumerateServiceHosts()
{
Processi[] = Process.GetProcesses();
foreach (Processo crtProcess nei processi)
{
WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(crtProcess);
if (crtServiceHost.Services != null)
{
restituisce crtServiceHost;
}
}
}
}
Uno sviluppatore può usare questa classe da qualsiasi applicazione .NET. Tuttavia, in questo caso, varie altre applicazioni di gestione non avranno modo di usarlo. WMI.NET ha gli hook per esporre la classe in questo esempio al mondo WMI e verrà illustrato il processo nei paragrafi seguenti. WMI fornisce un modello per consentire l'uso di tali oggetti e l'integrazione in applicazioni di gestione su scala aziendale come Systems Management Server o Operations Manager, fornire interazioni remote e consente di visualizzare e usare questa classe da più piattaforme.
Attributo a livello di assembly
Il primo passaggio per esporre un assembly per la strumentazione usando WMI.NET imposta un attributo WmiConfiguration a livello di assembly. Questo attributo contrassegna l'assembly come uno che implementa un provider di WMI.NET e consente di configurare varie cose sulle classi implementate dal provider, incluso lo spazio dei nomi in cui verranno esposti. Per l'esempio si definirà lo spazio dei nomi WMI da radice\Test e si imposta il modello di hosting sul modello di provider associato nel contesto di sicurezza di NetworkService. Si noti che tutte le operazioni verranno eseguite nel contesto di sicurezza dell'utente chiamante tramite rappresentazione.
[assembly: WmiConfiguration(@"root\Test", HostingModel = ManagementHostingModel.NetworkService)]
Attributi di WMI.NET livello di classe
Per instrumentare una classe usando WMI.NET, la classe, i relativi metodi, campi e proprietà esposti a WMI.NET devono essere pubblici e contrassegnati correttamente con attributi WMI.NET. Gli attributi vengono usati per generare i metadati richiesti da WMI per usare la classe C# esistente come classe WMI.
Strumentazione di una classe .NET
L'attributo ManagementEntity contrassegna una classe .NET come instrumentata. In fase di distribuzione, l'infrastruttura WMI.NET genererà una classe WMI corrispondente, con lo stesso nome, nel repository WMI. Per modificare questo nome, è necessario specificare il nome del parametro denominato nell'elenco degli argomenti per l'attributo ManagementEntity . Il nome viene usato come parametro denominato per modificare il nome della strumentazione per la maggior parte degli attributi. Per l'esempio scelto di assegnare un nome alla classe WMI WIN32_ServiceHost senza rinominare la classe .NET.
[ManagementEntity(Name = "WIN32_ServiceHost")]
classe public WIN32ServiceHost
Si noti che la denominazione di qualsiasi entità può essere un po 'difficile. C# è distinzione tra maiuscole e minuscole, ma WMI non è. Di conseguenza, tutti i nomi verranno considerati senza distinzione tra maiuscole e minuscole dal punto di vista dell'infrastruttura WMI.NET. Se i nomi di due campi o due metodi membri della stessa classe differiscono solo in base al caso, l'assembly non riuscirà a registrare con WMI.
Controllo dello schema della classe WMI
Il payload di un'istanza viene assegnato dalle relative proprietà. È necessario contrassegnare tutte le proprietà da riflettere nel mondo WMI con attributi ManagementProbe, ManagementConfiguration o ManagementKey . ManagementProbe è l'attributo per contrassegnare le proprietà esposte come lette solo in WMI. Nell'esempio la proprietà Services è la proprietà payload che si vuole esporre al mondo di gestione. Mostra uno stato di un processo che non può essere modificato direttamente, quindi lo contrassegneremo con ManagementProbe. Per le proprietà di lettura/scrittura, l'attributo ManagementConfiguration deve essere usato, in questo caso la proprietà stessa deve avere sia un setter che un getter.
[ManagementProbe]
public string[] Services
L'ID fa parte anche del payload di WIN32ServiceHost perché identifica il processo stesso. Le proprietà che identificano in modo univoco un'istanza della classe sono le chiavi per tale classe nel mondo WMI. Tutte le classi in WMI.NET sono necessarie per definire le chiavi e implementarle per identificare in modo univoco le istanze, a meno che non siano astratte, singleton o ereditano da una classe che definisce già le chiavi. Le chiavi sono contrassegnate con l'attributo ManagementKey . In questo esempio l'ID processo identifica in modo univoco un processo; pertanto identifica in modo univoco le istanze della classe.
[ManagementKey]
public int ID
Requisiti di runtime
Contrassegnando la classe e la relativa raccolta di proprietà, sarà possibile esporre lo schema della classe da esporre a WMI, ma non sarà sufficiente che le classi espongono dati effettivi. È necessario definire i punti di ingresso per ottenere, creare o eliminare un'istanza e enumerare le istanze. Verrà fornito il codice per l'enumerazione dell'istanza e il recupero di un'istanza specifica che è praticamente il minimo per un provider WMI funzionale.
Enumerazione delle istanze
Per enumerare le istanze, WMI.NET prevede un metodo pubblico statico senza parametri che restituisce un'interfaccia IEnumerable . Deve essere statico perché implementa funzionalità rilevanti per l'intera classe. Questo metodo deve essere contrassegnato con l'attributo ManagementEnumerator . Già definito nell'esempio EnumerateServiceHosts soddisfa tutti i requisiti in modo che possa essere semplicemente attribuito e usato per questo scopo.
[ManagementEnumerator]
EnumerateHosts() statico pubblico IEnumerable EnumerateServiceHosts()
È davvero importante per questo metodo assicurarsi che ogni elemento restituito nell'enumerazione sia un'istanza della classe instrumentata. In caso contrario, verrà generato un errore di runtime.
Associazione a un'istanza
Per recuperare un'istanza specifica (denominata binding in WMI.NET), è necessario disporre di un metodo che restituisce un'istanza in base ai valori delle chiavi che lo identificano. È necessario un metodo con lo stesso numero di parametri delle chiavi, con i parametri con gli stessi nomi e tipi delle chiavi. Il tipo restituito deve essere la classe instrumentata stessa. Verrà usato un metodo statico e, per associare le chiavi della classe ai parametri, è necessario specificare gli stessi nomi strumenti per i parametri come per le chiavi. Nell'esempio ID è la chiave per la classe WIN32_ServiceHost e è necessario assegnare un nome al parametro per l'ID del metodo di associazione o usare l'attributo ManagementName per esporre il parametro a WMI sotto il nome "ID". L'infrastruttura WMI.NET riconoscerà un metodo di associazione o un costruttore quando viene contrassegnato con l'attributo ManagementBind .
[ManagementBind]
static public WIN32ServiceHost GetInstance([ManagementName("ID")] int processId)
{
provare
{
Processo = Process.GetProcessById(processId);
WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(process);
if (crtServiceHost.Services != null)
{
restituire crtServiceHost;
}
else
{
restituisce Null;
}
}
generata da GetProcessById se non viene trovato alcun processo con l'ID specificato
catch (ArgumentException)
{
restituisce Null;
}
}
È importante notare che se l'istanza non viene trovata, il metodo restituirà Null. Nel nostro caso, lo faremo se non troviamo il processo richiesto in tutto o se il processo trovato non ospita alcun servizio windows.
Registrazione con WMI
La classe è pronta a eseguire il suo lavoro, ma è comunque necessario registrarla con WMI e inserirla in una posizione accessibile sul disco da cui eseguire il caricamento.
Global Assembly Cache (GAC) è il luogo in cui si vuole che l'assembly si trovi. In questo modo, .NET può recuperarlo con il relativo nome .NET completo. Si noti che l'assembly deve avere un nome sicuro .NET da registrare nella gaC. Ad esempio, si userà lo strumento .NET gacutil.exe per archiviare l'assembly nella gaC.
Per ottenere le informazioni dall'assembly instrumentato nei metadati WMI, è necessario usare lo strumento .NET InstallUtil.exe per richiamare una classe WMI.NET denominata DefaultManagementInstaller. DefaultManagementInstaller sa come analizzare l'intero assembly per le classi instrumentate e generare i metadati WMI corrispondenti. Poiché InstallUtil.exe necessita di una classe contrassegnata con l'attributo RunInstaller , verrà definita una classe vuota derivata da DefaultManagementInstaller che InstallUtil.exe richiamerà.
[System.ComponentModel.RunInstaller(true)]
public class MyInstall : DefaultManagementInstaller
{
}
La registrazione con WMI può essere eseguita offline o online. La registrazione online archivierà i metadati di strumentazione di un assembly direttamente nel repository WMI. Per installare direttamente i metadati nel repository WMI, InstallUtil.exe comando verrà richiamato con il nome dell'assembly come parametro. L'assembly deve trovarsi in GAC prima di eseguire InstallUtil.exe. Per l'assembly generato in questo esempio denominato WMIServiceHost.dll verranno usati i comandi seguenti:
C:>gacutil.exe /i WMIServiceHost.dll
C:>Installutil.exe WMIServiceHost.dll
La registrazione offline richiede due passaggi. Il primo passaggio consiste nel generare i metadati WMI associati all'assembly e archiviarlo in un file MOF. Il secondo passaggio è la registrazione effettiva nei computer di destinazione usando lo strumento mofcomp.exe o come parte di un pacchetto di installazione. Il vantaggio della registrazione offline è che il file MOF può essere localizzato e modificato in base alle esigenze. Per questo esempio è possibile generare e archiviare i metadati WMI in un file denominato WMIServiceHost.mof usando il parametro MOF come indicato di seguito:
C:>Installutil.exe /MOF=WMIServiceHost.mof WMIServiceHost.dll
Come nel caso online, l'assembly dovrà trovarsi nella gaC nel computer di destinazione. Per convalidare la distribuzione, è possibile usare lo strumento di sistemawmic.exe per visualizzare le istanze e i valori di questa classe.
C:>wmic /NAMESPACE:\\root\test PATH win32_servicehost ottenere /value
Durante lo sviluppo, è utile distribuire i simboli nella stessa cartella nella gaC in cui viene archiviato l'assembly. In questo modo, qualsiasi stack segnalato come risultato di un errore o un arresto anomalo, includerà il percorso di origine completo e i numeri di riga che consentono di identificare il pezzo di codice offensivo.
Estensione delle classi tramite ereditarietà
WIN32_ServiceHost riguarda gli host del servizio e le informazioni fornite sono limitate ai processi che ospitano servizi windows. Sarà interessante estendere queste informazioni in modo da includere informazioni specifiche del processo, ad esempio l'utilizzo della memoria, il percorso eseguibile, l'ID sessione e così via. Per ottenere queste informazioni, è possibile estendere lo schema e scrivere un altro codice per recuperare la quantità di informazioni necessarie. Un'ottima alternativa alla scrittura di codice aggiuntivo consiste nel sfruttare la classe WIN32_Process già esistente che si trova nel sistema operativo fuori dalla casella nello spazio dei nomi root\cimv2 e che fornisce tutte queste informazioni aggiuntive per qualsiasi processo in esecuzione nel sistema. Questa classe fornisce informazioni dettagliate su un processo in esecuzione e è possibile usare la derivazione WMI per estenderla con la propria classe.
L'ereditarietà WMI viene tradotta nel modello di codifica WMI.NET in ereditarietà della classe. Poiché la classe da cui si vuole derivare è una classe dallo spazio WMI che non implementiamo effettivamente nel codice, è necessario contrassegnarla in modi specifici.
Prima di iniziare a scrivere derivazione, è necessario prendere nota di due aspetti importanti sulla classe WIN32_Process . Prima WIN32_Process si trova nello spazio dei nomi root\cimv2 e la derivazione richiede di registrare la classe win32_servicehost nello stesso spazio dei nomi. Pertanto, verrà modificata leggermente l'istruzione attributo WmiConfiguration .
[assembly: WmiConfiguration(@"root\cimv2", HostingModel = ManagementHostingModel.NetworkService)]
Inoltre, la superclasse, win32_process, ha la proprietà Handle definita come chiave. Questa chiave è di tipo CIM_STRING che si traduce in . System.String di NET. Sarà necessario interrompere l'uso dell'ID come proprietà chiave e usare invece la proprietà Handle .
Per definire una classe esterna in WMI.NET per corrispondere win32_process è sufficiente eseguire il mirroring dello schema, ma includere solo le proprietà da usare o che devono essere usate. Le chiavi nella gerarchia di classi sono sempre necessarie. In questo momento Handle è l'unica proprietà interessante perché è la chiave e sarà necessario che per l'associazione a un'istanza specifica.
[ManagementEntity(External = true)]
classe pubblica astratta Win32_Process
{
handle di stringa protetto;
[ManagementKey]
handle di stringa pubblica
{
get {
restituisce this.handle;
}
}
}
L'impostazione di External su true nell'attributo ManagementEntity impedisce all'infrastruttura di generare metadati WMI in fase di distribuzione, ma mantiene le informazioni dichiarate per l'utilizzo in fase di esecuzione quando sarà necessario trovare chiavi e proprietà per le classi derivate. Si noti che è molto importante controllare il contenuto delle chiavi della classe base perché le chiavi vengono usate dal sottosistema WMI per unire informazioni da vari provider.
Per ottenere la classe WMI WIN32_ServiceHost ereditare la classe WMI Win32Process, si deriva WIN32ServiceHost dalla classe astratta appena creata in .NET world
[ManagementEntity(Name = "WIN32_ServiceHost")]
classe pubblica WIN32ServiceHost: Win32_Process
La proprietà ID viene rimossa.
[ManagementKey]
PUBLIC INT ID
{
get { return this.innerProcess.Id; }
}
modificare il costruttore per popolare la nuova chiave nel campo handle della classe di base
public WIN32ServiceHost(Process innerProcess)
{
this.innerProcess = innerProcess;
this.handle = innerProcess.Id.ToString();
}
modificare GetInstance per lavorare con un argomento stringa denominato Handle, ma solo le prime righe di esso rimangono invariate.
[ManagementBind]
static public WIN32ServiceHost GetInstance(string Handle)
{
int processId;
se (! Int32.TryParse(Handle, out processId))
{
restituisce null;
}
provare
[...]
È necessario ricompilare e ridistribuire il nuovo assembly nella GAC. Viene usato InstallUtil.exe per distribuire il nuovo schema. È possibile eseguire una query sul sistema usando un comando wmic.exe leggermente modificato.
C:>wmic /NAMESPACE:\\root\cimv2 PATH win32_servicehost get /value
Le istanze restituite verranno popolate con informazioni provenienti da entrambe le classi, win32_process e win32_servicehost. Nell'output, i servizi provengono da win32_servicehost mentre tutto il resto proviene da win32_process. Per semplificare l'output, è possibile specificare le colonne desiderate.
C:>wmic PATH win32_servicehost get Handle,Caption,CommandLine,Services /value
Diventa ancora più interessante quando si tenta di enumerare win32_process. Tale query restituirà tutti i processi e popola il campo Servizi solo per le istanze di win32_servicehost.
C:>wmic PATH win32_process get /value
L'output può risultare un po' eccessivo, quindi è sufficiente eseguirne il dump in un file (aggiungendo > out.txt alla fine della riga di comando) e aprirlo nel Blocco note per cercare la proprietà Services. Per comprendere cosa sta succedendo, è possibile visualizzare le proprietà di sistema da identificare, per ogni istanza, da quale classe WMI proviene.
C:>wmic PATH win32_process get Handle,CommandLine,__CLASS /value
Dall'elenco risultante selezionare un'istanza di win32_ServiceHost e visualizzarne i valori.
Percorso C:>wmic WIN32_Process.Handle="536" get /value
Le operazioni simili possono essere eseguite da qualsiasi applicazione client WMI usando script di Windows, Microsoft PowerShell, codice gestito o nativo, il sistema gestirà questo assembly allo stesso modo in cui tratta qualsiasi altro provider.
Implementazione di metodi
WMI.NET supporta metodi, sia statici che per istanza. In questo caso verrà aggiunto un metodo per arrestare tutti i servizi ospitati da un processo, per consentire di arrestare un processo in modo pulito, senza ucciderlo mentre i servizi sono ancora in esecuzione. Per rendere visibile un metodo pubblico in WMI, contrassegnarlo con l'attributo ManagementTask .
[ManagementTask]
public bool StopServices(int millisecondsWait)
{
if (innerProcess.Id == 0)
restituisce false;
ServiceController[] services = ServiceController.GetServices();
bool oneFailed = false;
using (SCM scm = new SCM())
{
for (int svcIndex = 0; servizi svcIndex < . Lunghezza; svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm. GetProcessId(crtService.ServiceName);
if (processId == innerProcess.Id)
{
provare
{
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;
}
}
}
}
restituisce !oneFailed;
}
Per richiamare questo metodo è necessaria un'istanza della classe win32_servicehost. Viene visualizzato un elenco degli host del servizio disponibili digitando:
Percorso C:>wmic win32_servicehost ottenere handle, Servizi
e selezionare quello con l'elenco dei servizi più non dannoso (in modo da non portare il sistema inattivo, anche alcuni servizi potrebbero non essere arrestabili) e usare la relativa proprietà Handle per identificare l'istanza della chiamata.
Percorso C:>wmic win32_servicehost. Handle="540" CALL StopServices(0)
Eccezioni e segnalazione errori
Le eccezioni sono un aspetto importante della WMI.NET. L'infrastruttura usa alcune eccezioni per comunicare informazioni e considera la maggior parte delle eccezioni come non gestite.
Eccezioni accettate
WMI.NET gestisce solo poche eccezioni che verranno descritte più avanti nell'articolo. Tutte le altre eccezioni vengono considerate come errori di programmazione e considerate come eccezioni non gestite che causano l'arresto anomalo dell'host del provider WMI. WMI.NET segnala eccezioni non gestite nel registro eventi di Windows.
Le eccezioni accettate vengono effettivamente convertite in codici di errore WMI e restituite al codice client, come illustrato nella tabella 1. Pertanto, i provider di WMI.NET si comportano come qualsiasi altro provider 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 |
Dall'eccezione interna, descritta più avanti nell'articolo |
Table1: conversione delle eccezioni in errori WMI
Nell'elenco precedente, ma due sono eccezioni generali di .NET Framework. Tutte le eccezioni elencate possono essere generate per segnalare al client lo stato specifico rappresentato da queste eccezioni.
Due nuove eccezioni sono state aggiunte nello spazio dei nomi System.Management.Instrumentation , come descritto di seguito.
Instancenotfoundexception
Questa eccezione serve a notificare che non è stata trovata un'istanza richiesta. L'esempio in questo articolo usa il metodo statico GetInstance per l'associazione a una determinata istanza e restituisce Null quando l'istanza non viene trovata. Un costruttore può essere usato per lo stesso scopo, ma dovrà generare un'eccezione InstanceNotFoundException quando non è in grado di trovare l'istanza richiesta. Ecco il costruttore per sostituire il metodo statico GetInstance.
[ManagementBind]
public WIN32ServiceHost(string Handle)
{
int processId;
se (! Int32.TryParse(Handle, out processId))
{
throw new InstanceNotFoundException();
}
provare
{
Processo di elaborazione = Process.GetProcessById(processId);
this.innerProcess = process;
this.handle = Handle;
se (questo. Services == null)
{
throw new InstanceNotFoundException();
}
}
generata da GetProcessById se non viene trovato alcun processo con l'ID specificato
catch (ArgumentException)
{
throw new InstanceNotFoundException();
}
}
Instrumentationexception
InstrumentationException è il wrapper per le eccezioni gestite. Un provider può scegliere di informare il client di un errore che si è verificato sul lato. A tale scopo, genererà un'eccezione InstrumentationException. Si noti che lo sviluppatore deve tenere presente che l'eccezione torna al sistema WMI. Di conseguenza, WMI.NET cercherà di trasformarlo in un HRESULT COM. Per generare un codice di errore preciso al client, è necessario passare come eccezione interna per InstrumentationException una classe Exception che consente di impostare direttamente HResult interno nella classe di eccezione di base.
Errore di segnalazione
Qualsiasi eccezione non elencata nella sezione precedente finirà come un'eccezione non gestita che causerà un arresto anomalo segnalato come "Si è verificata un'eccezione non gestita ('System.ExecutionEngineException') in wmiprvse.exe[<NNNN>].", NNNN come numero di processo. L'errore e lo stack verranno segnalati nel log eventi. La presenza di simboli nella stessa cartella dell'assembly mostrerà lo stack che causa l'offesa, con il nome del file e il numero di riga.
Un altro caso di errore è quando l'assembly non può essere caricato perché non è stato distribuito nella GAC. WMI.NET restituirà un errore di caricamento del provider (WBEM_E_PROVIDER_LOAD_FAILURE) per questo caso.
Un problema frequente durante lo sviluppo del provider è la mancata corrispondenza tra lo schema WMI e il codice. Ciò può verificarsi quando si distribuisce il nuovo assembly senza distribuire lo schema in WMI o quando i problemi non vengono rilevati durante la distribuzione tramite InstallUtil.exe perché le informazioni sono disponibili solo in fase di esecuzione. In questo caso, ad esempio, se durante un'enumerazione è stato restituito un tipo non corretto. L'infrastruttura WMI.NET segnala che al client viene segnalato un errore del provider (errore WMI WBEM_E_PROVIDER_FAILURE) e verrà generato un messaggio nel registro eventi di Windows che descrive il problema di runtime.
Altri suggerimenti
Tutti gli attributi e il codice WMI.NET si trovano nello spazio dei nomi System.Management.Instrumentation . Per compilare un assembly per WMI.NET, il progetto deve avere riferimenti a System.Core.dll e System.Management.Infrastructure.dll in quanto contengono rispettivamente la definizione di attributi e il codice di runtime. Successivamente sa come caricare gli assembly instrumentati, abbinare le classi instrumentate al repository WMI e richiamarle di conseguenza.
Mantenere tutte le classi per un particolare tipo di applicazione nello stesso assembly. Ciò rende molto più semplice la manutenzione e la distribuzione.
Tutto il codice nell'articolo deve essere distribuito ed eseguito come amministratore. In Windows Vista, l'esecuzione come amministratore richiederà un prompt dei comandi di sicurezza con privilegi elevati. I provider in genere non richiedono che i client siano amministratori, a meno che non accingano a dati di sistema protetti in modo speciale, come nell'esempio.
Conclusione
La nuova libreria di gestione in .NET Framework 3.5 offre agli sviluppatori un potente strumento per la scrittura di provider WMI. Data la semplicità del modello di programmazione, la scrittura di provider WMI diventa un'attività piuttosto semplice che consente di implementare la maggior parte delle funzionalità WMI. L'esempio riportato in questo articolo illustra come scrivere solo un semplice provider WMI, ma la libreria fornisce anche il supporto per lo sviluppo di provider disaccoppiati e l'implementazione di singleton, ereditarietà, classi astratte, riferimenti e classi di associazione che rendono WMI.NET Provider Extension v2 un'alternativa seria allo sviluppo usando interfacce native WMI.
Listato 1 : SCMInterop.cs
using System;
utilizzando System.Runtime.InteropServices;
utilizzando System.ServiceProcess;
spazio dei nomi External.PInvoke
{
[StructLayout(LayoutKind.Sequential)]
SERVICE_STATUS_PROCESS struct interno
{
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));
}
[Flag]
enumerazione interna SCM_ACCESS : uint
{
SC_MANAGER_CONNECT = 0x00001,
}
[Flag]
enumerazione interna 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 di enumerazione interna
{
SC_STATUS_PROCESS_INFO = 0
}
classe 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);
}
override protetto bool ReleaseHandle()
{
restituire SCM. CloseServiceHandle(base.handle);
}
public override bool IsInvalid
{
get { return IsClosed || handle == IntPtr.Zero; }
}
}
classe interna SCM : SafeHandle
{
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", CharSet = CharSet.Unicode,
SetLastError = true)]
public static extern IntPtr OpenSCManager(string machineName,
string databaseName,
[MarshallalAs(UnmanagedType.U4)] SCM_ACCESS dwAccess);
[DllImport("advapi32.dll", EntryPoint = "OpenServiceW", CharSet = CharSet.Unicode,
SetLastError = true)]
public static extern IntPtr OpenService(SafeHandle hSCManager,
[MarshallalAs(UnmanagedType.LPWStr)] string lpServiceName,
[MarshallalAs(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 bool CloseServiceHandle(IntPtr hServiceService);
public SCM()
: base(IntPtr.Zero, true)
{
IntPtr handle = OpenSCManager(null, null, SCM_ACCESS. SC_MANAGER_CONNECT);
Base. SetHandle(handle);
}
override protetto bool ReleaseHandle()
{
restituire 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;
Questo. QueryService(serviceName, out serviceStatus);
return (int)serviceStatus.dwProcessId;
}
}
}
Elenco 2 – WIN32ServiceHost.cs
using System;
uso di System.Collections;
using System.Collections.Generic;
usando System.Linq;
using System.Text;
uso di System.ServiceProcess;
uso di System.Diagnostics;
using External.PInvoke;
usando System.Management.Instrumentation;
[assembly: WmiConfiguration(@"root\cimv2", HostingModel = ManagementHostingModel.NetworkService)]
spazio dei nomi TestWMI.Hosted
{
[System.ComponentModel.RunInstaller(true)]
public class MyInstall : DefaultManagementInstaller
{
}
[ManagementEntity(External = true)]
classe pubblica astratta Win32_Process
{
handle stringa protetto;
[ManagementKey]
handle di stringa pubblica
{
get {
restituire this.handle;
}
}
}
[ManagementEntity(Name = "WIN32_ServiceHost")]
public class WIN32ServiceHost: Win32_Process
{
Process innerProcess;
<Riepilogo>
///
</Riepilogo>
<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)
restituisce Null;
ServiceController[] services = ServiceController.GetServices();
Elencare<i servizi ServiceControllerForProcess> = nuovo Elenco<ServiceController>();
using (SCM scm = new SCM())
{
for (int svcIndex = 0; svcIndex < services. Lunghezza; svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm. GetProcessId(crtService.ServiceName);
if (processId == innerProcess.Id)
{
servicesForProcess.Add(services[svcIndex]);
}
}
}
if (servicesForProcess.Count == 0)
restituisce Null;
string[] servicesNames = nuova stringa[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 (Processo crtProcess nei processi)
{
WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(crtProcess);
if (crtServiceHost.Services != null)
{
yield return crtServiceHost;
}
}
}
[ManagementBind]
public WIN32ServiceHost(string Handle)
{
int processId;
se (! Int32.TryParse(Handle, out processId))
{
throw new InstanceNotFoundException();
}
provare
{
Processo di elaborazione = Process.GetProcessById(processId);
this.innerProcess = process;
this.handle = Handle;
se (questo. Services == null)
{
throw new InstanceNotFoundException();
}
}
generata da GetProcessById se non viene trovato alcun processo con l'ID specificato
catch (ArgumentException)
{
throw new InstanceNotFoundException();
}
}
static public WIN32ServiceHost GetInstance(string Handle)
{
int processId;
se (! Int32.TryParse(Handle, out processId))
{
restituisce null;
}
provare
{
Processo di elaborazione = Process.GetProcessById(processId);
WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(process);
if (crtServiceHost.Services != null)
{
restituire crtServiceHost;
}
else
{
restituisce null;
}
}
generata da GetProcessById se non viene trovato alcun processo con l'ID specificato
catch (ArgumentException)
{
restituisce null;
}
}
[ManagementTask]
public bool StopServices(int millisecondsWait)
{
if (innerProcess.Id == 0)
restituisce false;
ServiceController[] services = ServiceController.GetServices();
bool oneFailed = false;
using (SCM scm = new SCM())
{
for (int svcIndex = 0; servizi svcIndex < . Lunghezza; svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm. GetProcessId(crtService.ServiceName);
if (processId == innerProcess.Id)
{
provare
{
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;
}
}
}
}
restituisce !oneFailed;
}
}
}
© 2008 Microsoft Corporation. Tutti i diritti sono riservati.