Hey, Scripting Guy!소개

Microsoft Scripting Guys

이 기사의 코드 다운로드: HeyScriptingGuy2007-082007_08.exe (152KB)

얼마 전 이 칼럼을 쓰는 Scripting Guy가 TV로 야구 중계를 시청하고 있었습니다. 경기가 끝난 후 그는 TV를 켜 놓은 채 잡지를 읽기 시작했습니다. 잡지를 다 읽고 무심코 TV를 봤는데 제2차 세계 대전에 관한 영화가 방영되고 있었습니다. 그리고 마침 진짜 같은 이야기가 전개되고 있었습니다. 늦은 밤, 젊은 미군 한 명이 보초를 서고 있는데 인기척을 듣게 된 것이죠.

"거기 누구냐?"고 초병이 소리쳤습니다.

"스미스 중사다"라는 대답이 들려왔습니다.

"스미스 중사? 우리 부대에 스미스 중사는 없다."

"방금 A 중대에서 이곳으로 전근했다."

"아, 그러시다고요? A 중대에서 말이죠? 그렇다면 중사님. 1934 월드 시리즈 우승 팀이 어디입니까?"

"뉴욕 양키즈지."

어처구니가 없군요. "스미스"! (뭐, 다들 예상 했겠지만 이 사람은 바로 체포된 후 영창으로 보내졌습니다.) 선량한 미국 시민이라면, 진짜 미국 시민이라면 누구나 Gas House Gang이라 불리던 전설적인 Dizzy Dean이 활약한 세인트루이스 카디널스가 1934년 월드 시리즈 우승을 차지한 사실을 알고 있습니다. 이 사실을 모른다면 모두 스파이라는 말이죠.

이달의 칼럼을 읽는 독자 중에도 세인트루이스 카디널스가 1934년 월드 시리즈 우승 팀이라는 사실을 모르는 사람이 있다면 이렇게 말해 주고 싶군요. "이제 모든 게 들통났습니다. 정체를 밝히시죠!"라고 말입니다. 지금 당장 순순히 가까운 FBI 지서에 자수할 것을 권하는 바입니다. 뭐, 아니면 전화만 한 통 주시면 FBI에서 친절히 모셔갈 것입니다.

이 칼럼을 집필하는 Scripting Guy는 영화를 계속 보면서 만약 초병이 질문을 더 했더라도 자기는 충분히 대답할 수 있었을 것이라는 생각을 했습니다. 1966 월드 시리즈 우승 팀은? 볼티모어 오리올스. 1960 월드 시리즈 우승 팀은? 피츠버그 파이러츠. 1994 월드 시리즈 우승 팀은? 호! 이 질문은 속임수군요. 1994년에는 월드 시리즈가 열리지 않았습니다.

그러나 요즘 맨 처음 질문에 대답하기가 너무 어렵다는 생각이 듭니다. "너는 누구냐?"라는 질문 말입니다. 1940년대에 비해 이 질문에 대답하기가 너무 어려워진 것 같습니다. Active Directory®만 해도 사용자가 다음과 같이 여러 가지 ID를 사용할 수 있으니까요.

  • 이름(givenName).
  • 성(sn).
  • 표시 이름(displayName).
  • UPN(userPrincipalName).
  • 로그온 이름(samAccountName).
  • 고유 이름(distinguishedName).

이러한 이름은 모두 동일인을 나타내고, 상황에 따라 모두 필요한 이름들입니다. 이것이 바로 문제입니다. 자신의 성과 이름을 모르는 사람은 없겠지만, "고유 이름은 무엇입니까?"라고 물었을 때 "간단합니다. CN=Ken.Myer, OU=Finance, DC=fabrikam, DC=com입니다. 친구들은 다들 CN=Ken.Myer라고 부르죠."라고 대답할 수 있는 사람은 몇 안 될 테니까요.

시스템 관리자 또는 지원 담당자도 이러한 사용자의 이름을 알고 있어야 합니다. 그렇다면 이 정보는 과연 어떻게 얻어야 하는 것일까요? 고유 이름을 실토할 때까지 무섭게 다그치는 것도 한 방법이 될 수 있겠죠. 뭐, 이 방법도 먹히기는 하겠지만 현대의 인사 부서에서 이런 방법을 사용할 수는 없는 노릇입니다. 그렇다면 차선책으로 스크립트를 사용할 수 있습니다. 어떤 스크립트를 사용해야 할까요? 먼저 그림 1의 스크립트부터 살펴보겠습니다.

Figure 1 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

스미스 중사라고 주장한 사람이 VBScript만 공부했어도 잡히지는 않았겠죠? 이 스크립트는 사람들이 잘 모르지만 매우 유용한 ADSystemInfo라는 ADSI 개체를 활용합니다. ADSystemInfo는 현재 로컬 컴퓨터에 로그온한 사용자는 물론, 로컬 컴퓨터 자체와 컴퓨터가 속한 도메인에 대한 정보를 모두 반환하는 유용한 개체입니다. 그 예로 그림 2를 살펴보시기 바랍니다.

Figure 2 모든 종류의 도메인 정보 표시

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

그러나 지금은 UserName 속성에 대해서만 알면 됩니다. UserName은 무엇이 그렇게 특별할까요? 뭐, 따지자면 단지 distinguishedName 속성에 해당할 뿐입니다. 그렇다면 distinguishedName은 무엇이 그렇게 특별할까요? distinguishedName도 UNC 파일 경로와 유사할 뿐입니다. 즉, UNC 경로를 사용하여 네트워크에서 파일을 고유하게 식별할 수 있는 것과 마찬가지로 distinguishedName(예: CN=Ken.Myer, OU=Finance, DC=fabrikam, DC=com)을 사용하면 Active Directory에서 사용자 계정을 고유하게 식별할 수 있습니다. 그리고 결과적으로 해당 사용자 계정에 사용자를 바인딩할 수 있는 겁니다. 이러한 연결이 만들어지면 사용자가 누구인지에 관한 것부터, 사용자에 대한 모든 정보를 검색할 수 있게 됩니다.

앞서 살펴본 첫 번째 스크립트가 바로 이러한 역할을 합니다. 먼저 ADSystemInfo 개체의 인스턴스를 만든 후에 UserName 속성의 값을 strUser라는 변수에 할당합니다.

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

여기서는 별다르게 작업할 내용이 없습니다. 다시 말해 사용자 이름, 사용자 도메인 또는 사용자 계정이 있는 OU를 지정할 필요가 없습니다. 이 모든 작업을 ADSystemInfo가 대신 다 해주니까요. 사용자의 distinguishedName만 있으면 다음 코드 줄을 사용하여 해당 사용자의 사용자 계정에 바인딩할 수 있습니다.

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

그리고 앞서 언급했듯이 연결만 만들면 해당 계정의 모든 Active Directory 특성 값을 출력할 수 있습니다. 샘플 스크립트에서는 사용자의 이름 속성 중 일부만 출력했지만 원한다면 전화 번호, 사무실 위치, 전자 메일 주소 등도 간단하게 검색할 수 있습니다.

멋지지 않습니까? 이 스크립트를 모든 사용자에게 배포하기만 하면 사용자가 "자신의 정보"를 궁금해하는 일은 없을 것입니다. 또한 자신의 고유 정보가 필요한 경우에도 쉽고 빠르게 알아낼 수 있습니다. 하지만 이와 관련한 질문, "너는 누구냐?"의 경우에는 어떨까요. 로컬 컴퓨터에 로그온한 사용자는 이렇게 식별할 수 있다 하더라도, 원격 컴퓨터에 로그온한 사용자가 누구인지는 어떻게 확인해야 할까요? 이미 눈치챘겠지만 이는 훨씬 어려운 문제입니다.

그리고 안타깝게도 원격 컴퓨터를 대상으로 ADSystemInfo 스크립트를 사용할 수는 없습니다. 그 이유는 바로 ADSystemInfo 개체가 로컬로만 만들어지기 때문입니다. 따라서 이 과제는 다음 세 가지 방법으로만 해결할 수 있습니다.

  • 바로 액세스 가능한 위치에 로그온한 사용자의 이름을 기록하는 로그온 스크립트를 작성합니다.
  • Win32_ComputerSystem WMI 클래스와 UserName 속성을 사용합니다.
  • 다른 방법을 찾아봅니다.

가장 합리적으로 보이는 첫 번째 방법을 사용해 보도록 하겠습니다. 이미 알고 있겠지만 ADSystemInfo 개체는 사용자의 고유 이름뿐만 아니라 컴퓨터의 고유 이름(ComputerName 속성)도 반환할 수 있습니다. 그림 3의 샘플 스크립트를 살펴보십시오.

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에 로그온한 상태로 나타납니다. 한마디로 유용한 방법이긴 하지만 완벽한 해결책이 될 수 없다는 겁니다.

자, 그렇다면 Win32_ComputerSystem 클래스를 사용하는 두 번째 방법은 어떨까요? 이 방법도 대부분의 경우 문제가 없지만, Win32_ComputerSystem 클래스가 항상 로그온한 사용자의 이름을 반환하는 것은 아니라는 문제가 있습니다. 특히 관리자 권한이 없는 사용자에 대해서나 Windows® 2000을 실행하는 컴퓨터의 경우에 이러한 문제가 많이 발생합니다. 예를 들어 그림 4의 스크립트를 통해 컴퓨터에 로그온한 사용자를 확인할 수도 있지만 100% 보장되지는 않습니다.

Figure 4 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

이 예제의 경우 UserName 속성이 domain\username의 형식으로 다시 보고됩니다. 즉, FABRIKAM\kenmyer로 나타납니다.

아, 중요한 사실을 잊을 뻔했군요. 이렇게 이름이 다시 반환되더라도 해당 사용자가 컴퓨터에 실제로 로그온한 상태라는 보장은 없습니다. Ken Myer가 atl-ws-01에서 로그오프하면 이 사용자의 이름이 UserName 속성의 값으로 보존되고 다른 사용자가 로그온할 때까지는 바뀌지 않습니다.

역시 이 방법도 안 되겠군요.

잠깐! 그렇다고 해서 잘못된 사용자가 표시되는 것도 아니지 않습니까? 흠, 그러면 이런 방법을 사용해 볼 수도 있겠군요. 누군가 컴퓨터에 로그온하면 Explorer.exe라는 프로세스가 실행됩니다. 대개 Explorer.exe가 실행 중이지 않다면 컴퓨터에 사용자가 로그온하지 않은 경우가 많습니다. 또한 Explorer.exe는 로그온한 사용자의 자격 증명 하에서만 실행되므로 그림 5와 같은 스크립트를 사용하면 컴퓨터에 로그온한 사용자를 거의 항상 정확하게 확인할 수 있습니다.

Figure 5 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

그리고 그림에서 보듯이 이 스크립트에서는 원격 컴퓨터(정확히는 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이라는 출력 매개 변수가 한 쌍 전달됩니다. 출력 매개 변수는 우리가 지정하여 메서드에 제공하는 변수로, 메서드에서 이러한 출력 매개 변수에 값을 할당하게 됩니다. 예제의 경우 strUser에 로그온한 사용자의 로그온 이름(kenmyer)이, strDomain에 로그온한 사용자의 도메인 이름(FABRIKAM)이 각각 할당됩니다. 이제 다음과 같이 이 두 출력 매개 변수의 값을 출력하기만 하면 됩니다.

Wscript.Echo strDomain & "\" & strUser

이 방법도 상당히 괜찮긴 하지만 더 효과적인 방법이 있습니다. GetOwner 메서드를 사용하면 컴퓨터에 로그온한 사용자의 로그온 이름(samAccountName)이 반환됩니다. 뭐, 이것이 문제가 되지는 않지만 앞에서 설명한 바와 같이 사용자에게는 samAccountName 외에도 다양한 이름이 할당됩니다. 따라서 "너는 누구냐?"라는 질문에 확실히 대답하려면 사용자의 displayName과 같은 다른 이름도 확인하는 것이 좋습니다. 하지만 GetOwner로 다른 이름을 확인할 수는 없습니다.

그러나 samAccountName을 Active Directory 검색 스크립트에 삽입하고 해당 로그온 이름을 찾아 사용자 계정에 바인딩할 수 있습니다. samAccountName은 도메인 내에서 고유해야 하므로 이 작업은 쉽게 실행할 수 있습니다. 또한 displayName을 비롯한 모든 Active Directory 속성은 일단 사용자 계정에 바인딩하기만 하면 값을 출력할 수 있습니다.

그림 6의 스크립트를 자세히 설명하기에는 지면이 부족하기 때문에, Active Directory 검색에 대한 자세한 내용은 "내 프린터는 어디에?"라는 기사를 참조하시기 바랍니다. 여기서는 이 스크립트가 로그온한 사용자의 로그온 이름을 확인하고, Active Directory에서 로그온 이름에 해당하는 사용자(samAccountName)를 검색하여 해당 사용자 계정에 바인딩하고, 사용자의 displayName을 출력한다는 정도만 설명하겠습니다. 이러한 모든 과정이 손쉽게 이루어지는 것입니다.

Figure 6 로그온한 사용자에 바인딩

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 직원들의 말투가 이런 식입니다. Scripting Guy는 "모든 이해 관계자의 성과, 작업 항목, 불필요한 사항을 우선 순위별로 나누어 보아야 해"와 같은 말에 진절머리가 나 있죠.) 일단 로컬 컴퓨터나 원격 컴퓨터에 로그온한 사용자에 대한 정보를 얻는 방법을 배웠습니다. 그리고 혹시 과거로 돌아가 제2차 세계 대전에 참전하게 되면 "누구냐?"라는 질문에 대답할 수 있도록 이 칼럼을 복사하고 월드 시리즈 우승 팀 목록을 준비해야 한다는 사실도 배웠습니다. 뭐, 꼭 이런 경우가 아니더라도 언제 "1903 월드 시리즈 우승 팀은?"이라는 질문을 받을 지 모르는 일이니까요.

참고로 답은 보스턴 레드삭스입니다. 최초로 월드 시리즈 우승을 차지한 해이죠. 그렇다면 1904 월드 시리즈는 누가 우승했을까요? 모르겠다고요? 흠, 그렇다면 FBI에 신고를 할 수 밖에 없겠습니다.

Microsoft Scripting Guys는 Microsoft에서 고용되어 일하고 있는 Microsoft의 직원들입니다. 이들은 좋아하는 야구 경기와 기타 여러 활동을 하는 시간을 제외하고는 항상 TechNet 스크립트 센터를 운영합니다. 자세한 내용은 www.scriptingguys.com에서 확인하십시오.

© 2008 Microsoft Corporation 및 CMP Media, LLC. All rights reserved. 이 문서의 전부 또는 일부를 무단으로 복제하는 행위는 금지됩니다..