Ei, Equipe de Scripts!Quem é você?

A Equipe de Scripts da Microsoft

Faça download do código deste artigo: HeyScriptingGuy2007-082007_08.exe (152KB)

Não faz muito tempo, um membro da Equipe de Scripts que escreve esta coluna assistiu a um jogo de beisebol na TV. Quando o jogo acabou, ele decidiu ler uma revista, mas deixou a TV ligada. Ao terminar a leitura, olhou para a TV e viu que estava passando um filme antigo da Segunda Guerra Mundial. Na verdade, ele olhou bem na hora em que os atores estavam em uma daquelas cenas famosas: um jovem soldado americano que estava de guarda à noite ouviu um barulho.

— Quem é? — ele perguntou.

— É o sargento Smith — disse uma voz.

— Sargento Smith? Não existe nenhum sargento Smith neste pelotão.

— Sou novo aqui. Acabei de ser transferido da Companhia A.

— É mesmo? Da Companhia A? Muito bem, Smith, então diga: quem venceu o Campeonato Mundial de 1934?

— O New York Yankees.

Resposta errada, "Smith"! Como era de se esperar, Smith é imediatamente capturado e preso. Todo bom americano, o verdadeiro, sabe que o St. Louis Cardinals — de Dizzy Dean e a Gas House Gang — venceu o Campeonato Mundial de 1934. Quem não souber só pode ser uma coisa: espião.

Se alguém ler a coluna deste mês e não souber que o St. Louis Cardinals venceu o Campeonato Mundial de 1934, só o que podemos dizer é: o jogo acabou, sabemos que é um espião. Esperamos que ele tenha a decência de procurar a unidade do FBI mais próxima e se entregue. Ou que, pelo menos, ligue para eles; quando se trata de espiões, o FBI faz o serviço completo: coleta e entrega.

Ao ver a cena, o membro da Equipe de Scripts que escreve esta coluna percebeu que temos uma boa chance de responder à segunda pergunta do soldado: quem venceu o Campeonato Mundial de 1966? O Baltimore Orioles. O Campeonato Mundial de 1960? Pittsburgh Pirates. O Campeonato Mundial de 1994? A-há! Uma pegadinha: não houve Campeonato Mundial em 1994.

Entretanto, até hoje, ainda é extremamente difícil responder à primeira pergunta: quem é? Não é uma pergunta tão fácil de responder atualmente quanto era em 1940. Afinal, só com o Active Directory®, os usuários possuem várias identidades diferentes, incluindo:

  • Nome (givenName).
  • Sobrenome (sn).
  • Nome de exibição (displayName).
  • Nome principal do usuário (userPrincipalName).
  • Nome de logon (samAccountName).
  • Nome distinto (distinguishedName).

Todos esses nomes identificam a mesma pessoa e , dependendo das circunstâncias, é importante conhecer todos. E esse é o problema. A maioria dos usuários sabe seu nome e sobrenome. Mas se você perguntar: "Qual é o seu nome mais conhecido?", apenas alguns responderão: "Ora, é simples: é o CN=Ken.Myer, OU=Finance, DC=fabrikam, DC=com, mas meus amigos me chamam de CN=Ken.Myer".

Um administrador de sistema ou profissional de assistência técnica também precisa conhecer esses nomes. Mas como você pode conseguir essa informação? Bem, uma abordagem seria apontar uma arma para seus usuários até que, finalmente, digam quais são seus nomes mais conhecidos. Essa tática poderia até funcionar, mas não é algo que o departamento de recursos humanos não aprovaria hoje. Então, você precisa recorrer ao Plano B: um script. Que tipo de script? Bem, para iniciantes, que tal o script da Figura 1?

Figure 1 Recuperando atributos de usuário com ADSystemInfo

On Error Resume Next

Set objSysInfo = CreateObject("ADSystemInfo")
strUser = objSysInfo.UserName

Set objUser = GetObject("LDAP://" & strUser)
WScript.Echo "First Name: " & objUser.givenName
WScript.Echo "Last Name: " & objUser.sn
WScript.Echo "Display Name: " & objUser.displayName
WScript.Echo "User Principal Name: " & objUser.userPrincipalName
WScript.Echo "SAM Account Name: " & objUser.sAMAccountName
WScript.Echo "Distinguished Name: " & objUser.distinguishedName

Rapaz, já pensou se o sargento Smith estudasse VBScript? Esse script usa um objeto ADSI pouco conhecido (mas extremamente útil) chamado ADSystemInfo. É um objeto pequeno e interessante que pode retornar todos os tipos de informações sobre o usuário conectado atualmente no computador local, bem como sobre o computador em si e o domínio ao qual ele pertence. Por exemplo, observe a Figura 2.

Figure 2 Exibir todo tipo de informação

On Error Resume Next

Set objSysInfo = CreateObject("ADSystemInfo")

Wscript.Echo "User name: " & objSysInfo.UserName
Wscript.Echo "Computer name: " & objSysInfo.ComputerName
Wscript.Echo "Site name: " & objSysInfo.SiteName
Wscript.Echo "Domain short name: " & objSysInfo.DomainShortName
Wscript.Echo "Domain DNS name: " & objSysInfo.DomainDNSName
Wscript.Echo "Forest DNS name: " & objSysInfo.ForestDNSName
Wscript.Echo "PDC role owner: " & objSysInfo.PDCRoleOwner
Wscript.Echo "Schema role owner: " & objSysInfo.SchemaRoleOwner
Wscript.Echo "Domain is in native mode: " & objSysInfo.IsNativeMode

Hoje, porém, a única propriedade que nos interessa é UserName. O que UserName tem de tão especial? Bem, simplesmente, ela corresponde à propriedade distinguishedName. E o que distinguishedName tem de tão especial? Bem, distinguishedName é semelhante a um caminho de arquivo UNC: assim como um caminho UNC nos permite identificar exclusivamente um arquivo em um local na rede, distinguishedName (por exemplo, CN=Ken.Myer, OU=Finance, DC=fabrikam, DC=com) permite identificar exclusivamente uma conta de usuário no Active Directory. Isso, por sua vez, nos permite ligá-lo àquela conta de usuário. E, assim que a conexão for feita, poderemos recuperar qualquer informação que desejarmos sobre um usuário, incluindo quem é ele.

E é exatamente o que fazemos no primeiro script que será mostrado. Para começar, criamos uma instância do objeto ADSystemInfo e, em seguida, atribuímos o valor da propriedade UserName a uma variável chamada strUser:

Set objSysInfo = CreateObject("ADSystemInfo")
strUser = objSysInfo.UserName

Observe que não há muito que fazer aqui. Por exemplo, não temos de especificar o nome de usuário, o domínio de usuário nem o OU no qual a conta de usuário reside; ADSystemInfo faz tudo isso sozinho. Depois de obtermos o distinguishedName do usuário, podemos fazer a ligação com essa conta de usuário usando a linha de código:

Set objUser = GetObject("LDAP://" & strUser)

E, novamente, depois de fazermos a conexão, poderemos recuperar os valores de qualquer atributo do Active Directory dessa conta. Em nosso script de exemplo, recuperamos apenas algumas propriedades do nome de usuário, mas poderíamos ter recuperado facilmente o número de telefone, o endereço do escritório, o email etc.

Muito bem. Tudo o que você tem a fazer é entregar esse script a todos os usuários e nunca mais eles terão de se perguntar: "Quem sou eu?" (ou, se o fizerem, poderão descobrir rapidamente, sem dificuldade). Mas e quanto à outra questão? Quem é? É fácil identificar o usuário conectado ao computador local. Mas como você pode determinar quem está conectado a um computador remoto? Como era de se esperar, trata-se de uma tarefa um pouco mais difícil.

E, sinto dizer, também pensamos nisso; infelizmente, você não pode simplesmente apontar o nosso script ADSystemInfo para um computador remoto. Isso porque o objeto ADSystemInfo só pode ser criado localmente. Restam-nos três possibilidades:

  • Criar um script de logon que registre o nome do usuário conectado a um local prontamente acessível.
  • Usar a classe WMI Win32_ComputerSystem e a propriedade UserName.
  • Fazer outra coisa.

A primeira opção parece bem interessante. Como você deve ter notado, o objeto ADSystemInfo pode retornar o nome distinto do computador (a propriedade ComputerName), bem como o nome distinto do usuário. Considere, por exemplo, o script mostrado na Figura 3.

Figure 3 Script de logon

On Error Resume Next

Set objSysInfo = CreateObject("ADSystemInfo")

strUser = objSysInfo.UserName
strComputer = objSysInfo.ComputerName

Set objUser = GetObject("LDAP://" & _
    strUser)
strUserName = objUser.displayName

Set objComputer = GetObject("LDAP://" & _
    strComputer)
objComputer.Description = strUserName
objComputer.SetInfo

O que estamos fazendo aqui? Para começar, usamos os valores das propriedades UserName e ComputerName e os armazenamos em um par de variáveis (strUser e strComputer). Em seguida, fazemos a ligação com a conta de usuário no Active Diretory (da mesma forma que antes), recuperamos o valor do atributo displayName (como antes) e o armazenamos em uma variável chamada strUserName. Bem simples, não?

Agora, podemos usar esta linha de código para fazer a conexão com a conta do computador do Active Directory:

Set objComputer = GetObject("LDAP://" & _
    strComputer)

Depois de fazer a conexão, atribuímos o displayName do usuário à propriedade Description do computador e chamamos o método SetInfo para escrever essa alteração no Active Directory:

objComputer.Description = strUserName
objComputer.SetInfo

Por que fazemos isso? É fácil. Suponha que Ken Myer faça logon no computador atl-ws-01. Adivinhe qual será o valor da propriedade Description de atl-ws-01? Isso mesmo: Ken Myer, a pessoa que está atualmente conectada ao computador. Quer saber quem está conectado a atl-ws-01? Basta verificar a propriedade Description.

Agora, em geral, esse cenário funciona muito bem, mas funcionará ainda melhor se você tiver um script de logoff que limpe a propriedade Description sempre que o usuário fizer logoff. Entretanto, não é uma solução à prova de erros. Por que não? Bem, os scripts de logon nem sempre são executados. Por exemplo, normalmente, eles não são executados quando a pessoa se conecta usando RAS. Da mesma forma, um script de logon não será executado se um usuário desligar a conexão de rede, fizer logon usando credenciais armazenadas em cache e conectar o computador novamente à rede. E suponha que um usuário apenas desligue o computador sem fazer logoff. Isso significa que seu script de logoff nunca será executado. Nesse caso, Ken Myer supostamente continuará conectado a atl-ws-01, muito embora seu computador não esteja ligado. Em outras palavras, é uma técnica útil, mas...

Então, que tal a segunda opção: usar a classe Win32_ComputerSystem? Isso, em geral, também funciona, mas o problema da classe Win32_ComputerSystem é que ela nem sempre retorna o nome do usuário conectado, especialmente para usuários que não possuem direitos de administrador (e também para computadores que executam o Windows® 2000). O script na Figura 4 provavelmente dirá quem está conectado ao computador, mas, novamente, sem nenhuma garantia.

Figure 4 Descobrir quem está conectado ao WMI

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * From Win32_ComputerSystem")

For Each objItem in colItems
  Wscript.Echo objItem.UserName
Next

Por acaso, nesse exemplo, a propriedade UserName é relatada no formato domain\username. Em outras palavras: FABRIKAM\kenmyer.

Ah, quase esquecemos: se um nome for retornado, isso não significa que um usuário está realmente conectado ao computador. Quando Ken Myer faz logoff em atl-ws-01, seu nome é retido como o valor da propriedade UserName e não é substituído até que outra pessoa faça logon.

Pois é.

Mas, espere aí, isso não significa que somos espiões. Veja outra abordagem possível. Se alguém se conectar a um computador, o processo Explorer.exe certamente será executado. Em geral, se o Explorer.exe não é executado é porque ninguém está conectado ao computador. E como o Explorer.exe é executado sob as credenciais do usuário que fez logon, quase sempre podemos determinar quem se conectou a um computador usando um script semelhante ao da Figura 5.

Figure 5 Determinar o proprietário de Explorer.exe

strComputer = "atl-ws-01"

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where Name = 'explorer.exe'")

If colItems.Count = 0 Then
  Wscript.Echo "No one is logged on to the computer."
Else
  For Each objProcess in colItems
    objProcess.GetOwner strUser, strDomain
    Wscript.Echo strDomain & "\" & strUser
  Next
End If

Como você pode ver, nesse caso, estamos nos conectando ao serviço WMI em um computador remoto (atl-ws-01, para ser mais exato). Então, usamos esta linha de código para recuperar uma coleção de todas as instâncias da classe Win32_Process que possui o nome Explorer.exe:

Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where " & _
  "Name = 'explorer.exe'")

E agora? Bem, como observamos há pouco, se o Explorer.exe não estiver em execução, é bem provável que ninguém tenha feito logon no computador. Como saberemos se o Explorer.exe está em execução? Uma maneira bastante simples é verificar o valor da propriedade Count da coleção. Se Count for igual a 0, a coleção estará vazia, e a coleção só ficará vazia quando nenhuma instância do Explorer.exe estiver em execução no atl-ws-01. Se for esse o caso, emitiremos uma mensagem de volta informando que ninguém está conectado ao computador:

Wscript.Echo "No one is logged on " & _
"to the computer."

Se Count não for igual a 0, configuramos um loop For Each para passar pela coleção de processos chamados Explorer.exe (e, sim, estamos supondo que nossa coleção, invariavelmente, tenha apenas um item). Para cada instância do Explorer.exe, chamamos o método GetOwner para determinar em qual conta o Explorer.exe está sendo executado:

objProcess.GetOwner strUser, strDomain

Observe que passamos para GetOwner um par de parâmetros de saída: strUser e strDomain. Os parâmetros de saída são simplesmente variáveis que nomeamos e às quais fornecemos um método; o método é que atribuirá os valores aos parâmetros de saída. Nesse caso, a strUser será atribuído o nome de logon do usuário conectado (kenmyer) e a strDomain será atribuído o nome de domínio para o usuário conectado (FABRIKAM). Tudo que temos a fazer é recuperar os valores desses dois parâmetros de saída:

Wscript.Echo strDomain & "\" & strUser

Sabe de uma coisa? Está muito bom. Mas podemos ir além. Quando usamos o método GetOwner, recuperamos o nome de logon (samAccountName) para o usuário conectado ao computador. Tudo bem, mas, como observamos antes, os usuários possuem muitos nomes, além do samAccountName. Para realmente responder à questão: "Quem é?", seria interessante conhecermos, digamos, o displayName do usuário. Mas não é possível determinar esses outros nomes usando GetOwner, não é mesmo?

De fato, não é possível. Entretanto, podemos usar samAccountName, incluí-lo em um script de pesquisa do Active Directory, localizar a conta de usuário e ligá-lo a ela com um nome de logon (uma tarefa facilitada pelo fato de que samAccountName deve ser exclusivo dentro de um domínio). E, depois de fazermos a ligação com a conta de usuário, podemos recuperar os valores de qualquer propriedade do Active Directory, incluindo displayName.

Não temos tempo para explicar em detalhes o script da Figura 6; para obter mais informações sobre como pesquisar no Active Directory, consulte "Cara, cadê minha impressora?". Não é preciso dizer que esse script determina o nome de logon do usuário conectado, pesquisa o usuário no Active Directory com esse nome de logon (samAccountName), faz a conexão com a conta de usuário em questão e recupera o displayName do usuário. E tudo com uma mão amarrada às costas!

Figure 6 Fazer a ligação com o usuário conectado

strComputer = "atl-ws-01"

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where Name = 'explorer.exe'")

If colItems.Count = 0 Then
  Wscript.Echo "No one is logged on to the computer."
Else
  For Each objProcess in colItems
    objProcess.GetOwner strUser,strDomain
  Next
End If

Const ADS_SCOPE_SUBTREE = 2

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection

objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 

objCommand.CommandText = "SELECT displayName FROM " & _
  "'LDAP://DC=wingroup,DC=fabrikam,DC=com' WHERE " & _
    "objectCategory='user' " & _
    "AND samAccountName = '" & strUser & "'"
Set objRecordSet = objCommand.Execute

objRecordSet.MoveFirst

Do Until objRecordSet.EOF
  Wscript.Echo objRecordSet.Fields("displayName").Value
  objRecordSet.MoveNext
Loop

Então, quais são as pedidas de hoje? (A propósito, é assim que o pessoal da Microsoft fala. Se você quiser deixar a Equipe de Script completamente louca, basta dizer algo como: "Precisamos fazer uma triagem das principais pedidas, medidas e contramedidas para nossos acionistas".) Bem, para começar, agora sabemos como obter informações sobre o usuário conectado a um computador, seja o computador local ou o remoto. Acima de tudo, sabemos o que devemos fazer se um dia entrarmos na máquina do tempo e cairmos na Segunda Guerra Mundial: carregar uma cópia desta coluna (para poder responder à questão: "Quem é?") e, aconteça o que acontecer, sempre ter em mãos uma lista dos vencedores de Campeonatos Mundiais. Afinal, nunca se sabe. Alguém pode perguntar: "Quem ganhou o Campeonato Mundial de 1903?".

Observação: o Boston Red Sox. A propósito, esse foi o primeiro Campeonato Mundial. Agora, quem você acha que ganhou o Campeonato Mundial de 1904? Então você não sabe? Dá licença um minutinho? Precisamos atender a uma ligação...

A Equipe de Scripts da Microsoft trabalha para a – bem, é empregada da – Microsoft. Quando não está jogando/treinando/assistindo beisebol (e diversas outras atividades), ela administra o Script Center da TechNet. Confira no site www.scriptingguys.com.

© 2008 Microsoft Corporation e CMP Media, LLC. Todos os direitos reservados. A reprodução parcial ou completa sem autorização é proibida..