Gravando provedores WMI acoplados usando WMI.NET Extensão de Provedor 2.0
Gravando provedores WMI acoplados usando WMI.NET Extensão de Provedor 2.0
Gabriel Ghizila
Microsoft Corporation
Janeiro de 2008
Resumo: Detalhes de como gravar um provedor WMI acoplado usando WMI.NET Extensão de Provedor 2.0 enviada no .NET Framework 3.5
Sumário
Introdução
Uma classe .NET simples
Atributo de nível de assembly
Atributos de WMI.NET de nível de classe
Requisitos de runtime
Registro com WMI
Estendendo classes por meio de herança
Implementando métodos
Exceções e relatórios de erros
Outras dicas
Conclusão
Listagem 1 – SCMInterop.cs
Listagem 2 – WIN32ServiceHost.cs
Introdução
A WMI (Instrumentação de Gerenciamento do Windows) é uma infraestrutura amplamente usada para gerenciar aplicativos Windows e Windows. Apesar de serem muito extensíveis e populares entre administradores do sistema e aplicativos de gerenciamento, muitos desenvolvedores ficam com medo de pensar em escrever provedores WMI devido à complexidade das interfaces nativas que precisam ser implementadas.
Embora as versões iniciais do .NET Framework tenham vindo com um conjunto de objetos e padrões para implementar provedores WMI, eles eram limitados ao gerenciamento de aplicativos, eles não permitiam que você definisse métodos e as chaves para instâncias fossem geradas automaticamente. WMI.NET Provider Extension v2 (WMI.NET) é uma nova infraestrutura em Orcas (.NET Framework 3.5) que permite implementar um conjunto completo de funcionalidades do provedor WMI. Essa nova infraestrutura coexiste com o modelo de provedor WMI.NET de versões anteriores, mas é muito mais poderosa e extensível.
O foco do artigo atual é como escrever provedores Acoplados WMI, que é um dos recursos mais significativos no WMI.NET. Não há diferenças significativas na forma como os provedores separados são escritos para que o artigo possa dar um bom começo para um leitor que tenta escrever provedores WMI de qualquer tipo usando WMI.NET. O artigo mostrará como criar um provedor WMI acoplado a partir de uma classe .NET simples e, em seguida, enriquecê-lo com alguns recursos extras. A meta é poder enumerar processos que hospedam serviços Windows e poder enumerar serviços Windows em cada processo desse tipo e ser capaz de integrar essa funcionalidade ao WMI.
Uma classe .NET simples
Para começar, criaremos uma classe C# que modelará um processo que hospeda serviços Windows. Cada instância mostrará uma lista de serviços hospedados no processo associado. A classe tem um método estático que retorna todas as instâncias associadas aos hosts de serviço em execução no sistema.
classe pública WIN32ServiceHost
{
A classe é um wrapper em torno da classe Process. O campo innerProcess armazenará uma referência ao objeto Process.
Processar innerProcess;
A classe tem um construtor que aceita um objeto de processo como parâmetro.
public WIN32ServiceHost(Process innerProcess)
{
this.innerProcess = innerProcess;
}
Incluímos um acessador para a ID do processo.
public int ID
{
get { return this.innerProcess.Id; }
}
A propriedade Services retornará uma matriz com os nomes dos serviços hospedados. Essa propriedade será nula se nenhum serviço estiver em execução no processo.
public string[] Services
{
get
{
O processo ocioso não hospeda nenhum serviço.
if (innerProcess.Id == 0)
retornar nulo;
Obter uma lista de todos os serviços windows no sistema
Serviços ServiceController[] = ServiceController.GetServices();
List<ServiceController> servicesForProcess = new List<ServiceController>();
using (SCM scm = new SCM())
{
for (int svcIndex = 0; svcIndex < services. Comprimento; svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm. GetProcessId(crtService.ServiceName);
Compare a ID do processo em que um serviço está sendo executado com a ID do processo atual.
if (processId == innerProcess.Id)
{
servicesForProcess.Add(services[svcIndex]);
}
}
}
if (servicesForProcess.Count == 0)
retornar nulo;
Preparar, preencher e retornar a matriz com os nomes do serviço
string[] servicesNames = new string[servicesForProcess.Count];
for (int serviceIdx = 0; serviceIdx < servicesForProcess.Count; serviceIdx++)
{
servicesNames[serviceIdx] = servicesForProcess[serviceIdx]. Servicename;
}
serviços de retornoNames;
}
}
EnumerateServiceHosts é um método estático que retornará um IEnumerable para percorrer todos os hosts de serviço em execução.
static public IEnumerable EnumerateServiceHosts()
{
Processos[] = Process.GetProcesses();
foreach (Processar crtProcess em processos)
{
WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(crtProcess);
if (crtServiceHost.Services != null)
{
yield return crtServiceHost;
}
}
}
}
Um desenvolvedor pode usar essa classe de qualquer aplicativo .NET. No entanto, nesse caso, vários outros aplicativos de gerenciamento não terão como usá-lo. WMI.NET tem os ganchos para expor a classe neste exemplo ao mundo WMI e explicaremos o processo nos parágrafos a seguir. O WMI fornece um modelo para permitir aproveitar esses objetos e integrá-los a aplicativos de gerenciamento de escala empresarial como o Servidor de Gerenciamento de Sistemas ou o Operations Manager, fornecer interação remota e permite que você veja e use essa classe de várias plataformas.
Atributo de nível de assembly
A primeira etapa para expor um assembly para instrumentação usando WMI.NET é definir um atributo WmiConfiguration no nível do assembly. Esse atributo marca o assembly como aquele que implementa um provedor de WMI.NET e permite configurar várias coisas sobre as classes implementadas pelo provedor, incluindo o namespace no qual elas serão expostas. Para nosso exemplo, definiremos o namespace WMI como root\Test e definiremos o modelo de hospedagem como modelo de provedor acoplado no contexto de segurança NetworkService. Observe que todas as operações serão executadas no contexto de segurança do usuário de chamada por meio de representação.
[assembly: WmiConfiguration(@"root\Test", HostingModel = ManagementHostingModel.NetworkService)]
Atributos de WMI.NET de nível de classe
Para instrumentar uma classe usando WMI.NET, a classe, seus métodos, campos e propriedades expostos a WMI.NET precisam ser públicos e marcados corretamente com atributos WMI.NET. Os atributos são usados para gerar os metadados exigidos pelo WMI para usar a classe C# existente como uma classe WMI.
Instrumentando uma classe .NET
O atributo ManagementEntity marca uma classe .NET como sendo instrumentada. No momento da implantação, a infraestrutura de WMI.NET gerará uma classe WMI correspondente, com o mesmo nome, no repositório WMI. Para modificar esse nome, precisamos fornecer o nome do parâmetro nomeado na lista de argumentos para o atributo ManagementEntity . O nome é usado como um parâmetro nomeado para alterar o nome da instrumentação para a maioria dos atributos. Para nosso exemplo, optamos por nomear a classe WMI WIN32_ServiceHost sem renomear a classe .NET.
[ManagementEntity(Name = "WIN32_ServiceHost")]
classe pública WIN32ServiceHost
Observe que a nomenclatura de qualquer entidade pode ser um pouco complicada. O C# diferencia maiúsculas de minúsculas, mas o WMI não. Portanto, todos os nomes serão tratados como diferenciais de maiúsculas e minúsculas da perspectiva da infraestrutura WMI.NET. Se os nomes de dois campos ou dois métodos que são membros da mesma classe forem diferentes apenas por caso, o assembly não será registrado no WMI.
Controlando o esquema de classe WMI
A carga de uma instância é fornecida por suas propriedades. Precisamos marcar todas as propriedades a serem refletidas no mundo WMI com atributos ManagementProbe, ManagementConfiguration ou ManagementKey . ManagementProbe é o atributo para marcar propriedades expostas como somente leitura no WMI. Em nosso exemplo, a propriedade Services é a propriedade payload que queremos expor ao mundo do gerenciamento. Ele mostra um estado de um processo que não pode ser modificado diretamente, portanto, vamos marcá-lo com o ManagementProbe. Para propriedades de leitura/gravação, o atributo ManagementConfiguration terá que ser usado, nesse caso, a própria propriedade precisa ter um setter e um getter.
[ManagementProbe]
public string[] Services
A ID também faz parte do conteúdo do WIN32ServiceHost, pois identifica o processo em si. As propriedades que identificam exclusivamente uma instância de sua classe são as chaves dessa classe no mundo WMI. Todas as classes em WMI.NET são necessárias para definir chaves e implementá-las para identificar exclusivamente instâncias, a menos que sejam abstratas, singleton ou herdadas de uma classe que já define chaves. As chaves são marcadas com o atributo ManagementKey . Neste exemplo, a ID do processo identifica exclusivamente um processo; portanto, ele identifica exclusivamente as instâncias de nossa classe.
[ManagementKey]
public int ID
Requisitos de runtime
Marcar a classe e sua coleção de propriedades permitirá expor o esquema de classe a ser exposto ao WMI, mas não será suficiente fazer com que as classes exponham dados reais. Precisamos definir pontos de entrada para obter, criar ou excluir uma instância, bem como enumerar instâncias. Forneceremos código para enumeração de instância, bem como recuperaremos uma instância específica que é praticamente o mínimo para um provedor WMI funcional.
Enumerando as instâncias
Para enumerar instâncias, WMI.NET espera um método público estático sem parâmetros que retorna uma interface IEnumerable . Ele precisa ser estático, pois implementa a funcionalidade relevante para toda a classe. Esse método precisa ser marcado com o atributo ManagementEnumerator . Já definido em nosso exemplo, EnumerateServiceHosts atende a todos os requisitos para que ele possa ser apenas atribuído e usado para essa finalidade.
[ManagementEnumerator]
static public IEnumerable EnumerateServiceHosts()
É realmente importante para esse método garantir que cada elemento retornado na enumeração seja uma instância da classe instrumentada. Caso contrário, um erro de runtime resultará.
Associação a uma instância
Para poder recuperar uma instância específica (que é chamada de associação em WMI.NET), precisamos ter um método que retorne uma instância com base nos valores das chaves que a identificam. Precisamos de um método que tenha o mesmo número de parâmetros que as chaves, com os parâmetros tendo os mesmos nomes e tipos que as chaves. O tipo de retorno deve ser a própria classe instrumentada. Usaremos um método estático e, para associar as chaves da classe aos parâmetros, precisamos especificar os mesmos nomes instrumentados para parâmetros como para chaves. Em nosso exemplo, ID é a chave para a classe WIN32_ServiceHost e precisamos nomear o parâmetro para nossa ID de método de associação ou usar o atributo ManagementName para expor o parâmetro ao WMI sob o nome "ID". A infraestrutura WMI.NET reconhecerá um método ou construtor de associação quando for marcado com o atributo ManagementBind .
[ManagementBind]
WIN32ServiceHost GetInstance([ManagementName("ID")] static public WIN32ServiceHost GetInstance([ManagementName("ID")] int processId)
{
experimentar
{
Processo de processo = Process.GetProcessById(processId);
WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(process);
if (crtServiceHost.Services != null)
{
retornar crtServiceHost;
}
else
{
retornar nulo;
}
}
gerado por GetProcessById se nenhum processo com a ID fornecida for encontrado
catch (ArgumentException)
{
retornar nulo;
}
}
É importante observar que, se a instância não for encontrada, o método retornará nulo. Em nosso caso, faremos isso se não encontrarmos o processo solicitado ou se o processo encontrado não estiver hospedando nenhum serviço windows.
Registro com WMI
A classe está pronta para fazer seu trabalho, mas ainda precisamos registrá-la no WMI e colocá-la em um local acessível no disco do qual ser carregado.
O GAC (Cache de Assembly Global) é o local onde queremos que o assembly esteja localizado. Dessa forma, o .NET pode recuperá-lo pelo nome completo do .NET. Observe que o assembly precisa ter um nome forte do .NET para ser registrado no GAC. Por exemplo, usaremos a ferramenta .NET gacutil.exe para armazenar o assembly no GAC.
Para obter as informações do assembly instrumentado em metadados WMI, precisamos usar a ferramenta .NET InstallUtil.exe para invocar uma classe WMI.NET chamada DefaultManagementInstaller. DefaultManagementInstaller sabe como analisar todo o assembly para classes instrumentadas e gerar os metadados WMI correspondentes. Como InstallUtil.exe precisa de uma classe marcada com o atributo RunInstaller , definiremos uma classe vazia derivada de DefaultManagementInstaller que InstallUtil.exe invocará.
[System.ComponentModel.RunInstaller(true)]
classe pública MyInstall: DefaultManagementInstaller
{
}
O registro com WMI pode ser feito offline ou online. O registro online armazenará os metadados de instrumentação de um assembly diretamente no repositório WMI. Para instalar diretamente os metadados no repositório WMI, InstallUtil.exe comando será invocado com o nome do assembly como parâmetro. O assembly precisa estar no GAC antes de executar InstallUtil.exe. Para o assembly gerado neste exemplo chamado WMIServiceHost.dll usaremos os seguintes comandos:
C:>gacutil.exe /i WMIServiceHost.dll
C:>Installutil.exe WMIServiceHost.dll
O registro offline requer duas etapas. A primeira etapa é gerar os metadados WMI associados ao assembly e armazená-los em um arquivo MOF. A segunda etapa é o registro real em computadores de destino usando a ferramenta mofcomp.exe ou como parte de um pacote de instalação. A vantagem do registro offline é que o arquivo MOF pode ser localizado e modificado conforme necessário. Para este exemplo, podemos gerar e armazenar os metadados WMI em um arquivo chamado WMIServiceHost.mof usando o parâmetro MOF da seguinte maneira:
C:>Installutil.exe /MOF=WMIServiceHost.mof WMIServiceHost.dll
Como no caso online, o assembly terá que estar no GAC no computador de destino. Para validar a implantação, podemos usar a ferramenta wmic.exe sistema para ver as instâncias e valores dessa classe.
C:>wmic /NAMESPACE:\\root\test PATH win32_servicehost get /value
Durante o desenvolvimento, é útil implantar os símbolos na mesma pasta no GAC em que o assembly é armazenado. Dessa forma, qualquer pilha relatada como resultado de um erro ou falha incluirá o caminho de origem completo e os números de linha que ajudarão a identificar a parte ofensiva do código.
Estendendo classes por meio de herança
WIN32_ServiceHost é sobre hosts de serviço e as informações que ele fornece são limitadas a processos que hospedam serviços windows. Será interessante estender essas informações para incluir informações específicas do processo, como uso de memória, caminho executável, ID de sessão e assim por diante. Para obter essas informações, podemos estender o esquema e escrever mais um pouco mais de código para recuperar o máximo de informações necessários. Uma boa alternativa para escrever código extra é aproveitar a classe WIN32_Process já existente que está no sistema operacional pronta para uso no namespace root\cimv2 e que fornece todas essas informações extras para qualquer processo em execução no sistema. Essa classe fornece informações abrangentes sobre um processo em execução e podemos usar a derivação WMI para estendê-la com nossa própria classe.
A herança WMI é convertida no modelo de codificação WMI.NET para a herança de classe. Como a classe da qual queremos derivar é uma classe do espaço WMI que não implementamos no código, precisamos marcá-la de maneiras específicas.
Antes de começarmos a escrever derivação, precisamos observar duas coisas importantes sobre a classe WIN32_Process . O primeiro WIN32_Process está no namespace root\cimv2 e a derivação exige que registremos a classe win32_servicehost no mesmo namespace. Portanto, alteraremos ligeiramente a instrução de atributo WmiConfiguration .
[assembly: WmiConfiguration(@"root\cimv2", HostingModel = ManagementHostingModel.NetworkService)]
Além disso, nossa superclasse, win32_process, tem a propriedade Handle definida como chave. Essa chave é do tipo CIM_STRING que se traduz em . System.String do NET. Teremos que parar de usar a ID como nossa propriedade de chave e usar a propriedade Handle .
Para definir uma classe externa em WMI.NET para corresponder win32_process simplesmente espelho seu esquema, mas incluímos apenas as propriedades que queremos ou precisamos usar. As chaves na hierarquia de classe são sempre necessárias. Neste momento, Handle é a única propriedade interessante, pois é a chave e precisaremos disso para associar a uma instância específica.
[ManagementEntity(External = true)]
classe pública abstrata Win32_Process
{
identificador de cadeia de caracteres protegida;
[ManagementKey]
identificador de cadeia de caracteres pública
{
get {
return this.handle;
}
}
}
Definir Externo como true no atributo ManagementEntity impede que a infraestrutura gere metadados WMI no momento da implantação, mas mantém as informações declaradas para uso em runtime quando será necessário localizar chaves e propriedades para classes derivadas. Observe que é muito importante controlar o conteúdo das chaves da classe base, pois as chaves são usadas pelo subsistema WMI para mesclar informações de vários provedores.
Para obter a classe WMI WIN32_ServiceHost herdar a classe WMI Win32Process, derivamos WIN32ServiceHost da classe abstrata recém-criada no mundo .NET
[ManagementEntity(Name = "WIN32_ServiceHost")]
classe pública WIN32ServiceHost: Win32_Process
Removemos a propriedade ID.
[ManagementKey]
public int ID
{
get { return this.innerProcess.Id; }
}
alterar o construtor para preencher a nova chave no campo de identificador da classe base
public WIN32ServiceHost(Process innerProcess)
{
this.innerProcess = innerProcess;
this.handle = innerProcess.Id.ToString();
}
modifique GetInstance para trabalhar com um argumento de cadeia de caracteres chamado Handle, apenas as primeiras linhas dele, pois o restante permanece o mesmo.
[ManagementBind]
static public WIN32ServiceHost GetInstance(string Handle)
{
int processId;
se (! Int32.TryParse(Handle, out processId))
{
retornar nulo;
}
experimentar
[...]
Precisamos recompilar e reimplantar o novo assembly no GAC. Usamos InstallUtil.exe para implantar o novo esquema. Do que podemos consultar o sistema usando um comando dewmic.exe ligeiramente modificado.
C:>wmic /NAMESPACE:\\root\cimv2 PATH win32_servicehost get /value
As instâncias retornadas serão preenchidas com informações de ambas as classes, win32_process e win32_servicehost. Na saída, os serviços virão de win32_servicehost enquanto todo o resto vem de win32_process. Para simplificar a saída, podemos especificar as colunas desejadas.
C:>wmic PATH win32_servicehost obter Handle,Caption,CommandLine,Services /value
Fica ainda mais interessante quando tentamos enumerar win32_process. Essa consulta retornará todos os processos e preencherá o campo Serviços apenas para as instâncias do win32_servicehost.
C:>wmic PATH win32_process get /value
A saída pode ser um pouco esmagadora, portanto, apenas despeje-a em um arquivo (adicionando > out.txt no final da linha de comando) e abra-a no bloco de notas para pesquisar a propriedade Services. Para entender o que está acontecendo, podemos mostrar as propriedades do sistema para identificar, para cada instância, de qual classe WMI ela é.
C:>wmic PATH win32_process obter Handle,CommandLine,__CLASS /value
Na lista resultante, escolha uma instância de win32_ServiceHost e exiba seus valores.
C:>wmic path WIN32_Process.Handle="536" get /value
Operações semelhantes podem ser executadas de qualquer aplicativo cliente WMI usando scripts do Windows, Microsoft PowerShell, código gerenciado ou nativo, o sistema tratará esse assembly da mesma maneira que trata qualquer outro provedor.
Implementando métodos
WMI.NET dá suporte a métodos, estáticos e por instância. Em nosso caso, adicionaremos um método para interromper todos os serviços hospedados por um processo, para permitir a interrupção limpa de um processo, sem matá-lo enquanto os serviços ainda estão em execução. Para tornar um método público visível no WMI, marcamos-o com o atributo ManagementTask .
[ManagementTask]
public bool StopServices(int millisecondsWait)
{
se (innerProcess.Id == 0)
retornar false;
ServiceController[] services = ServiceController.GetServices();
bool oneFailed = false;
using (SCM scm = new SCM())
{
para (int svcIndex = 0; svcIndex < services. Comprimento; svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm. GetProcessId(crtService.ServiceName);
if (processId == innerProcess.Id)
{
experimentar
{
crtService.Stop();
se (milissegundosWait != 0)
{
crtService.WaitForStatus( ServiceControllerStatus.Stopped,
new TimeSpan((long)milissegundosWait * 10000));
}
}
catch (System.ServiceProcess.TimeoutException)
{
oneFailed = true;
}
catch (System.ComponentModel.Win32Exception)
{
oneFailed = true;
}
catch (InvalidOperationException)
{
oneFailed = true;
}
}
}
}
return !oneFailed;
}
Para invocar esse método, precisamos de uma instância da classe win32_servicehost. Obtemos uma lista de hosts de serviço disponíveis digitando:
Caminho C:>wmic win32_servicehost obter identificador, Serviços
e escolha aquele com a lista mais benigna de serviços (para não baixar o sistema, além disso, alguns serviços também podem não ser stoppable) e usar sua propriedade Handle para identificar a instância da chamada.
Caminho C:>wmic win32_servicehost. Handle="540" CALL StopServices(0)
Exceções e relatórios de erros
As exceções são um aspecto importante da WMI.NET. A infraestrutura usa algumas exceções para comunicar informações e trata a maioria das exceções como sem tratamento.
Exceções aceitas
WMI.NET trata apenas algumas exceções que serão descritas mais adiante no artigo. Todas as outras exceções são consideradas como erros de programação e tratadas como exceções sem tratamento, fazendo com que o host do provedor WMI falhe. WMI.NET relata exceções sem tratamento no log de eventos do Windows.
As exceções aceitas são realmente convertidas em códigos de erro WMI e enviadas de volta para o código do cliente, conforme mostrado na Tabela 1. Portanto, os provedores de WMI.NET se comportarão como qualquer outro provedor 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 |
Da exceção interna, descrita mais detalhadamente no artigo |
Table1 – tradução de exceções em erros WMI
Na lista acima de tudo, exceto duas, há exceções gerais do .NET Framework. Todas as exceções listadas podem ser geradas para relatar ao cliente no status que essas exceções representam.
Duas novas exceções foram adicionadas no namespace System.Management.Instrumentation , conforme descrito mais adiante.
Instancenotfoundexception
Essa exceção serve para notificar que uma instância solicitada não foi encontrada. O exemplo neste artigo usa o método estático GetInstance para associação a uma determinada instância e retorna nulo quando a instância não é encontrada. Um construtor pode ser usado para a mesma finalidade, mas precisará gerar uma InstanceNotFoundException quando não for capaz de localizar a instância necessária. Aqui está o construtor para substituir o método estático GetInstance.
[ManagementBind]
public WIN32ServiceHost(string Handle)
{
int processId;
se (! Int32.TryParse(Handle, out processId))
{
gerar nova InstanceNotFoundException();
}
experimentar
{
Processo de processo = Process.GetProcessById(processId);
this.innerProcess = process;
this.handle = Handle;
se (isto. Serviços == nulo)
{
gerar nova InstanceNotFoundException();
}
}
gerado por GetProcessById se nenhum processo com a ID fornecida for encontrado
catch (ArgumentException)
{
gerar nova InstanceNotFoundException();
}
}
Instrumentationexception
InstrumentationException é o wrapper para exceções tratadas. Um provedor pode optar por informar o cliente de um erro que ocorreu ao seu lado. Para fazer isso, ele lançará uma InstrumentationException. Observe que o desenvolvedor deve ter em mente que a exceção está voltando para o sistema WMI. Portanto, WMI.NET tentará o melhor para transformá-lo em UM COM HRESULT. Para gerar um código de erro preciso de volta para o cliente, precisamos passar como uma exceção interna para InstrumentationException uma classe Exception que permite definir o HResult interno na classe de exceção base diretamente.
Relatório de erros
Qualquer exceção não listada na seção anterior acabará como uma exceção sem tratamento que resultará em uma falha relatada como "Uma exceção sem tratamento ('System.ExecutionEngineException') ocorreu em wmiprvse.exe[<NNNN>].", sendo NNNN um número de processo. O erro e a pilha serão relatados no eventlog. Ter símbolos na mesma pasta que o assembly mostrará a pilha ofensiva concluída, com o nome do arquivo e o número da linha.
Outro caso de erro é quando o assembly não pode ser carregado porque não foi implantado no GAC. WMI.NET retornará uma falha de carga do provedor (WBEM_E_PROVIDER_LOAD_FAILURE) para esse caso.
Um problema frequente durante o desenvolvimento do provedor é a incompatibilidade entre o esquema WMI e o código. Isso pode acontecer ao implantar o novo assembly sem implantar o esquema no WMI ou quando os problemas não são capturados ao implantar usando InstallUtil.exe porque as informações estão disponíveis apenas no runtime. Esse seria o caso, por exemplo, se um tipo incorreto fosse retornado durante uma enumeração. A infraestrutura WMI.NET relata isso ao cliente como uma falha de provedor (erro WMI WBEM_E_PROVIDER_FAILURE) e gerará uma mensagem no log de eventos do Windows que descreve o problema de runtime.
Outras dicas
Todos os atributos e o código WMI.NET estão localizados no namespace System.Management.Instrumentation . Para criar um assembly para WMI.NET, o projeto precisa ter referências a System.Core.dll e System.Management.Infrastructure.dll , pois contêm a definição de atributos e o código de runtime, respectivamente. O posterior sabe como carregar assemblies instrumentados, corresponder às classes instrumentadas com o repositório WMI e invocá-los adequadamente.
Mantenha todas as classes para um tipo específico de aplicativo no mesmo assembly. Isso facilita muito a manutenção e a implantação.
Todo o código no artigo precisa ser implantado e executado como administrador. No Windows Vista, executar como administrador exigirá um prompt de comando de segurança com privilégios elevados. Os provedores geralmente não exigem que os clientes sejam administradores, a menos que acessem dados do sistema especialmente protegidos como em nosso exemplo.
Conclusão
A nova biblioteca de gerenciamento no .NET Framework 3.5 oferece aos desenvolvedores uma ferramenta poderosa na busca por escrever provedores WMI. Dada a simplicidade do modelo de programação, escrever provedores WMI torna-se uma tarefa bastante fácil, permitindo que você implemente a maioria das funcionalidades do WMI. O exemplo neste artigo mostra como escrever apenas um provedor WMI simples, mas a biblioteca também fornece suporte para o desenvolvimento de provedores desacoplados e a implementação de singletons, herança, classes abstratas, referências e classes de associação, tornando WMI.NET Extensão de Provedor v2 uma alternativa séria ao desenvolvimento usando interfaces nativas WMI.
Listagem 1 – SCMInterop.cs
usando o sistema;
usando System.Runtime.InteropServices;
usando System.ServiceProcess;
namespace External.PInvoke
{
[StructLayout(LayoutKind.Sequential)]
struct interno SERVICE_STATUS_PROCESS
{
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
public static readonly int SizeOf = Marshal.SizeOf(typeof(SERVICE_STATUS_PROCESS));
}
[Sinalizadores]
enumeração interna SCM_ACCESS : uint
{
SC_MANAGER_CONNECT = 0x00001,
}
[Sinalizadores]
enumeração 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)
}
enumeração interna SC_STATUS_TYPE
{
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)
{
gerar novo System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(),
"SCM. QueryServiceStatusEx");
}
SetHandle(serviceHandle);
}
protected override bool ReleaseHandle()
{
retornar 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,
[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)] cadeia de caracteres 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()
{
retornar 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 = novo 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)
{
gerar novo System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(),
"SCM. QueryServiceStatusEx");
}
}
}
public int GetProcessId(string serviceName)
{
SERVICE_STATUS_PROCESS serviceStatus;
Este. QueryService(serviceName, out serviceStatus);
return (int)serviceStatus.dwProcessId;
}
}
}
Listagem 2 – WIN32ServiceHost.cs
usando o sistema;
usando System.Collections;
usando System.Collections.Generic;
usando System.Linq;
usando System.Text;
usando System.ServiceProcess;
usando System.Diagnostics;
usando External.PInvoke;
usando System.Management.Instrumentation;
[assembly: WmiConfiguration(@"root\cimv2", HostingModel = ManagementHostingModel.NetworkService)]
namespace TestWMI.Hosted
{
[System.ComponentModel.RunInstaller(true)]
classe pública MyInstall: DefaultManagementInstaller
{
}
[ManagementEntity(External = true)]
classe pública abstrata Win32_Process
{
identificador de cadeia de caracteres protegida;
[ManagementKey]
identificador de cadeia de caracteres pública
{
get {
return this.handle;
}
}
}
[ManagementEntity(Name = "WIN32_ServiceHost")]
classe pública WIN32ServiceHost: Win32_Process
{
Processar innerProcess;
<Resumo>
///
</Resumo>
<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
{
se (innerProcess.Id == 0)
retornar nulo;
ServiceController[] services = ServiceController.GetServices();
Listar<serviços ServiceControllerForProcess> = new List<ServiceController>();
using (SCM scm = new SCM())
{
para (int svcIndex = 0; svcIndex < services. Comprimento; svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm. GetProcessId(crtService.ServiceName);
if (processId == innerProcess.Id)
{
servicesForProcess.Add(services[svcIndex]);
}
}
}
if (servicesForProcess.Count == 0)
retornar nulo;
string[] servicesNames = new string[servicesForProcess.Count];
for (int serviceIdx = 0; serviceIdx < servicesForProcess.Count; serviceIdx++)
{
servicesNames[serviceIdx] = servicesForProcess[serviceIdx]. Servicename;
}
serviços de retornoNames;
}
}
[ManagementEnumerator]
static public IEnumerable EnumerateServiceHosts()
{
Processos[] = Process.GetProcesses();
foreach (Processar crtProcess em processos)
{
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))
{
gerar nova InstanceNotFoundException();
}
experimentar
{
Processo de processo = Process.GetProcessById(processId);
this.innerProcess = process;
this.handle = Handle;
se (isto. Serviços == nulo)
{
gerar nova InstanceNotFoundException();
}
}
gerado por GetProcessById se nenhum processo com a ID fornecida for encontrado
catch (ArgumentException)
{
gerar nova InstanceNotFoundException();
}
}
WIN32ServiceHost GetInstance(string Handle) público estático
{
int processId;
se (! Int32.TryParse(Handle, out processId))
{
retornar nulo;
}
experimentar
{
Processo de processo = Process.GetProcessById(processId);
WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(process);
if (crtServiceHost.Services != null)
{
retornar crtServiceHost;
}
else
{
retornar nulo;
}
}
gerado por GetProcessById se nenhum processo com a ID fornecida for encontrado
catch (ArgumentException)
{
retornar nulo;
}
}
[ManagementTask]
public bool StopServices(int millisecondsWait)
{
if (innerProcess.Id == 0)
retornar false;
Serviços ServiceController[] = ServiceController.GetServices();
bool oneFailed = false;
using (SCM scm = new SCM())
{
for (int svcIndex = 0; svcIndex < services. Comprimento; svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm. GetProcessId(crtService.ServiceName);
if (processId == innerProcess.Id)
{
experimentar
{
crtService.Stop();
if (milissegundosWait != 0)
{
crtService.WaitForStatus( ServiceControllerStatus.Stopped,
new TimeSpan((long)milissegundosWait * 10000));
}
}
catch (System.ServiceProcess.TimeoutException)
{
oneFailed = true;
}
catch (System.ComponentModel.Win32Exception)
{
oneFailed = true;
}
catch (InvalidOperationException)
{
oneFailed = true;
}
}
}
}
return !oneFailed;
}
}
}
© Microsoft Corporation 2008. Todos os direitos reservados.