Estou tirando a poeira do blog trazendo um assunto que me queimou uns bons neurônios nestes últimos dias.
Estou desenvolvendo uma aplicação em C# com .NET4 do tipo WindowsService que irá realizar diversas funções em um computador rodando Windows. Uma destas funções é registrar os logons e logoffs dos usuários que utilizarem tais computadores em um banco de dados SQL Server remoto para posterior consulta e auditoria.
Pesquisando na web, encontrei diversas formas de codificar a captura destes eventos. Enquanto algumas se mostraram muito simples, outras eram bem complexas e cheias de código. Por fim, elas não apresentaram o resultado como eu gostaria de visualizar.
Depois de alguns dias escovando bit, cheguei a uma solução utilizando um pouco de tudo que li nesses dias. O resultado eu gostaria de compartilhar com vocês e, caso conheçam uma forma mais simples, não deixem de postar.Afinal de contas, o blog foi criado para compartilharmos informações.
Vamos então à minha solução:
Toda classe derivada de ServiceBase possui a propriedade CanHandleSessionChangeEvent que quando definida como true, faz com que a classe derivada seja notificada caso haja alterações nas sessões do Windows.
Para tratar as notificações, devemos implementar o seguinte evento:
A estrutura SessionChangeDescription possui 2 propriedades:
- Reason: descreve o tipo do evento (SessionLogon, SessionLogoff, RemoteConnect, RemoteDisconnect, SessionLock, SessionUnlock, dentre outros);
- SessionId: contém o número da sessão em que o evento ocorreu (1, 2, 3, ...).
Até aqui foi sopinha no mel...
A coisa começou a complicar quando tentei descobrir qual era o usuário conectado na sessão do evento e descobri que no .NET não há uma forma "nativa" de fazer isso. Nem uma classe, método, evento, nada... Pelo menos, eu não encontrei. E olha que revirei vários links do Google.
O máximo que encontrei, foi o pessoal utilizando APIs de Terminal Services com códigos extremamente grandes fazendo chamadas a DLLs externas e que ainda sim, não traziam o resultado que eu queria: Listavam os usuários conectados, mas não as sessões.
Ou eu não fiz a codificação direita ou deixei algo passar despercebido ou eu sou muito burro mesmo, mas enfim... Não fui muito feliz com o lance das APIs. Além do que, parece que essas APIs são compatíveis apenas com o Vista ou superior e eu preciso que o código rode também no XP. É o XP ainda não morreu por aqui...
Foi então que voltei minha atenção a uma tecnologia que tenho utilizado nos últimos anos e que tem me ajudado bastante: Windows Management Instrumentation ou simplesmente WMI.
Pesquisando as classes do namespace root\CIMv2 encontrei a classe Win32_Process que lista os processos atualmente em execução no computador e, ao selecionar um processo específico, eu tenho a propriedade SessionId que mostra em qual sessão o processo está sendo executado. E para minha alegria esta mesmíssima classe possui um método chamado GetOwner que retorna o nome do usuário que está executando aquele processo... Bingo! Bastava agora encontrar uma forma de filtrar os processos. E minha solução para todo este caos foi a seguinte:
Antes de mais nada, adicione System.Management à lista de referências do seu projeto.
Criei uma estrutura simples para armazenar as sessões de usuário e declarei-a no escopo do meu serviço:
Criei um método para atualizar as sessões:
E um método para esperar por uma sessão:
No OnStart do serviço, chamei o método RefreshUserSessions() para que o serviço saiba quais sessões já estão ativas no computador. E reescrevi o evento OnSessionChange da seguinte forma:
Na parte de //Código... entra a minha codificação para o banco de dados. Mas para saber qual é o usuário da sessão basta usar, por exemplo:
string userName = UserSessions[changeDescription.SessionId];
Testei esta solução no XP, 7 e 8.1 e funcionou perfeitamente.
Desculpem por não postar o código como um todo, mas a verdade que o meu código original é bem diferente do postado aqui, pois algumas coisas eu transformei em classes distintas para reusabilidade em outras partes do meu projeto. Mas a essência da minha solução é esta.
Desculpem também por postar os códigos como imagem, mas a formatação do Blogspot para a endentações e cores não ajuda muito.
Espero que minha solução sirva para mais alguém ou que alguém apresente algo mais "requintado". Comentários são sempre bem-vindos.
Até o próximo post.
Nenhum comentário:
Postar um comentário