Figure 3 登录脚本

 
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

我们正在执行何种操作?首先,我们要获取 UserName 和 ComputerName 属性的值并将它们存储在一对变量(strUser 和 strComputer)中。然后,我们在 Active Directory 中绑定到用户帐户(与前面的操作一样),检索 displayName 属性值(与前面的操作一样)并将该值存储在名为 strUserName 的变量中。非常简单,对吧?
这样,我们就能够使用以下代码行连接到 Active Directory 计算机帐户:
 
Set objComputer = GetObject("LDAP://" & _
    strComputer)
建立连接后,我们将用户的 displayName 分配给计算机的 Description 属性,然后调用 SetInfo 方法以将此更改写入 Active Directory:
 
objComputer.Description = strUserName
objComputer.SetInfo
为什么要这样做?道理很简单。假定 Ken Myer 登录计算机 atl-ws-01。请猜测一下,atl-ws-01 的 Description 属性值将是什么?答对了:Ken Myer,当前登录到该计算机的用户。想知道谁登录到了 atl-ws-01?只需查看 Description 属性即可。
现在,总的来说,此方案效果相当好 — 但如果您拥有一个注销脚本,能够在用户注销以后清除 Description 属性,那么效果将更好。但这并不是万无一失的解决方案。为什么呢?因为登录脚本不会始终运行。例如,如果用户使用 RAS 登录,那么通常它们就不会运行。同样,如果用户断开自己的网络连接,使用缓存的凭据登录,然后再将计算机连接到网络,那么登录脚本也不会运行。假定用户未注销就关闭了计算机。这就意味着他的注销脚本将永远不会运行。在此情况下,即使该计算机并未运行,Ken Myer 也仍可能能够登录到 atl-ws-01。换句话说,这是一项有用的技术,但也存在问题。
那么选项 2,使用 Win32_ComputerSystem 类,怎么样?同样,此选项通常有效,但 Win32_ComputerSystem 类的问题是它并不是始终返回已登录用户的名称,尤其是对于不具有管理员权限的用户(以及对于运行 Windows ® 2000 的计算机)。 图 4 中的脚本也许将告诉您登录到计算机的用户,但同样没有任何保证。
 
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

顺便说一下,在此情况下将以 domain\username 的格式返回 UserName 属性。换句话说:FABRIKAM\kenmyer。
噢,我们几乎忘了:即使确实返回了一个名称,也不意味着用户真的登录到了该计算机。当 Ken Myer 注销 atl-ws-01 后,他的名字以 UserName 属性值的形式被保留,并且直到他人登录时其姓名才会被取代。
真是让人厌烦。
但请稍候;这不会使我们成为间谍。我们可以采取下面的另一种方法。如果有人登录到某计算机,那么 Explorer.exe 进程必定正在运行。一般来讲,如果 Explorer.exe 没有运行,则没有人登录该计算机。由于 Explorer.exe 使用已登录用户的凭据运行,所以通过使用类似于 图 5 中所示脚本的脚本,我们几乎总是能够确定登录到某计算机的用户。
 
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

您可以看到,在这种情况下,我们将连接到远程计算机(准确地说,是 atl-ws-01)上的 WMI 服务。然后,我们使用以下代码行检索名为“Explorer.exe”的 Win32_Process 类的所有实例的集合:
 
Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where " & _
  "Name = 'explorer.exe'")
现在要做什么?嗯,正如我们刚刚所说的,如果 Explorer.exe 没有运行,那么很可能没有人登录到该计算机。我们如何知道 Explorer.exe 是否正在运行?一种非常简单的方法是查看该集合的 Count 属性值。如果 Count 等于 0,那么我们拥有的是空集合;而只有在 atl-ws-01 上没有运行 Explorer.exe 实例时,我们才会拥有空集合。如果是这样,我们将回显一条消息,表明没有人登录到该计算机:
 
Wscript.Echo "No one is logged on " & _
"to the computer."
如果 Count 不等于 0,我们将建立一个 For Each 循环,以遍历名为 Explorer.exe 的进程的集合(我们假定在集合中总是仅包含一个项目)。对于每个 Explorer.exe 实例,我们将调用 GetOwner 方法以确定 Explorer.exe 正在使用谁的帐户运行:
 
objProcess.GetOwner strUser, strDomain
请注意,我们将一对输出参数传递给 GetOwner:strUser 和 strDomain。输出参数仅仅是我们命名并提供给某一方法的变量;然后该方法将为这些输出参数赋值。在此例中,会将已登录用户的登录名 (kenmyer) 赋给 strUser,将已登录用户的域名 (FABRIKAM) 赋给 strDomain。我们接下来要做的是回显这两个输出参数的值:
 
Wscript.Echo strDomain & "\" & strUser
您知道吗?这相当好。但我们可以更进一步。当我们使用 GetOwner 方法时,我们将获得登录到该计算机的用户的登录名 (samAccountName)。这很好,但是,正如我们前面提到的,用户除了 samAccountName 外还拥有各种名称。要如实地回答“您是谁?”这个问题,最好知道用户的 displayName。但我们无法使用 GetOwner 确定用户的其他名称,是吧?
是的,无法确定。不过,我们可以获得 samAccountName,将其插入到 Active Directory 搜索脚本,然后使用该登录名查找并绑定到用户帐户(由于 samAccountNames 在域中必须是唯一的,该任务变得更容易)。而且,一旦绑定到用户帐户,我们就能够回显任一 Active Directory 属性的值,包括 displayName。
我们没有时间详细介绍 图 6 中的脚本;有关搜索 Active Directory 的更多信息,请访问“ 都德,我的打印机在哪儿?”。只需说明此脚本确定已登录用户的登录名,搜索 Active Directory 以查找具有该登录名 (samAccountName) 的用户,绑定到所涉及的用户帐户,然后回显该用户的 displayName 就足够了。所有这一切可谓轻而易举,不费吹灰之力!
 
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

那么从今天开始,我们的主要结论是什么?(顺便说一下,也就是 Microsoft 人员的意见。如果您希望将脚本专家逼得发疯,仅仅走近他,然后对他说一些如“我们需要排列所有利益相关方的主要结论、操作项和非目标的优先级”的话。)嗯,首先,现在我们知道如何获取有关登录到计算机(本地计算机或远程计算机)的用户的信息。更重要的是,如果我们能穿越时空,然后发现自己置身于第二次世界大战中,我们知道该干什么:确保您阅读了本专栏(所以您就可以回答“您是谁”这个问题了),还有 — 无论做什么事 — 要始终随身携带一个世界职业棒球大赛冠军的列表。毕竟,您不知道何时有人将问到您,“谁赢得了 1903 年的世界职业棒球大赛冠军?”
注意:Boston Red Sox。顺便说一下,这是第一届世界职业棒球大赛。现在,你认为谁赢得了 1904 年的世界职业棒球大赛?您什么意思,您不知道吗?请稍等我们片刻,我们需要打个电话....