Hey, Scripting Guy!심도 있는 WMI 탐색

Microsoft Scripting Guys

목차

네임스페이스
WMI 클래스
속성
메서드

Scripting Guys 중 한 명은 젊었을 때 발효 음료를 마시고 겨울 캠핑을 하는 두 가지 취미에 심취했었다고 합니다. 아마도 그 두 가지가 서로 관련되어 있다고 생각했었던 모양인데 그 당시 친구들은 "더 열심히 해봐"라고 부추기고는 했다고 합니다. 이번 달에는 이 예전 친구들의 조언을 받아들여서 WMI(Windows Management Instrumentation)의 세계로 깊숙히 들어가 보기로 했습니다.

다행스러운 것은 이번에는 하이킹을 준비할 필요 없이 커피 메이커와 의자만 있으면 된다는 것입니다. 이제부터 스크립트를 사용하여 WMI의 깊은 부분을 탐험해 보겠습니다.

잘 아시다시피 Scripting Guys는 정말 실용적인 사람들입니다. 우리는 실제로는 몇 가지 세부 정보를 빠뜨리고는 잘난 체하는 설명보다는 실제로 무엇인가를 달성할 수 있도록 문제에 대한 실질적인 해결책을 제시하려고 하며 앞서의 부류와 차별화하기 위해 노력하고 있습니다. 이 칼럼은 특정한 시스템 관리 작업을 수행하는 데 초점을 맞추고 있지는 않지만 실용적인 목표가 있습니다. 기본적인 목표는 WMI 인프라에 대한 정보를 제공하는 것입니다. 그리고 이번에는 몇 가지 유용한 탐색 스크립트를 제공하고자 합니다. 메모장을 여십시오. 이번에는 깊게 들어갑니다!

네임스페이스

WMI 리포지토리는 데이터베이스이며 CIM(Common Information Model)을 저장하는 데 사용됩니다. 이 모델은 개체 지향적이므로 WMI가 관리할 수 있는 항목을 나타내는 설명(WMI 클래스)의 집합으로 모델이 구성된다는 것을 의미합니다. 예를 들어 Win32_Process WMI 클래스는 프로세스를 나타냅니다. WMI 클래스는 WMI 리포지토리의 다른 섹션에 저장됩니다. WMI 리포지토리의 섹션은 네임스페이스로 알려져 있습니다. WMI 리포지토리에 대해 가장 먼저 알 수 있는 사실은 상위 네임스페이스로 구분된다는 것입니다. 그림 1에 스크립트가 나와 있습니다.

그림 1 네임스페이스 표시

strComputer = "."
Call EnumNameSpaces("root")

Sub EnumNameSpaces(strNameSpace)
    On Error Resume Next
    WScript.Echo strNameSpace
    Set objWMIService=GetObject _
        ("winmgmts:{impersonationLevel=impersonate}\\" & _ 
            strComputer & "\" & strNameSpace)

    Set colNameSpaces = objWMIService.InstancesOf("__NAMESPACE")

    For Each objNameSpace in colNameSpaces
        Call EnumNameSpaces(strNameSpace & "\" & objNameSpace.Name)
    Next
End Sub

strComputer(마침표는 로컬 컴퓨터를 나타냄)에 지정된 컴퓨터의 WMI 리포지토리에 있는 모든 네임스페이스의 이름을 표시합니다.

네임스페이스는 하위 네임스페이스를 포함할 수 있으므로 전체가 디렉터리 구조와 비슷하다고 생각할 수 있습니다. 따라서 최상위 루트 네임스페이스에서 시작하여 모든 네임스페이스에 대해서만 묻는다면 하위 네임스페이스를 놓치고 루트 내의 네임스페이스의 첫 번째 수준만 얻게 됩니다. 이 문제를 해결하기 위해 재귀라는 트릭을 사용하여 네임스페이스를 인자로 받고 해당하는 모든 하위 네임스페이스를 반환하는 EnumNameSpaces라는 서브루틴을 만들었습니다.

먼저 루트를 매개 변수로 지정하고 EnumNameSpaces를 호출했습니다. 이렇게 하면 루트 네임스페이스 내의 모든 네임스페이스가 반환됩니다. 그런 다음 재귀를 수행합니다. EnumNameSpaces는 식별한 각 하위 네임스페이스를 전달하고 다시 자신을 호출합니다. 커피 한 잔을 마시면서 이에 대해서 생각해 보십시오. 결과적으로 모든 네임스페이스가 처리되고 하위 네임스페이스가 있는 경우 모두 표시됩니다.

서브루틴의 시작 부분에 On Error Resume Next 문이 포함되어 있음을 알 수 있습니다. 이것은 모든 네임스페이스에 대한 액세스가 없는 보안 컨텍스트에서 스크립트를 실행한 경우를 대비한 것입니다. 이 경우에는 스크립트가 시간 제한을 기다려야 하므로 다소 느리지만 실행은 됩니다.

물론 이러한 결과는 wbemtest.exe(WMI가 설치된 모든 컴퓨터에 있음)나 Scriptomatic(go.microsoft.com/fwlink/?LinkId=125976)을 사용하더라도 얻을 수 있습니다. 그러나 여러분의 훌륭한 스크립팅 기술을 활용하여 약간의 작업으로 이러한 네임스페이스를 필터링하거나, Excel로 출력을 내보내는 것은 물론이고, 두 컴퓨터의 네임스페이스를 비교할 수도 있습니다.

이제 리포지토리가 어떻게 분리되는지 알 수 있으므로 이러한 각 섹션에 무엇이 있는지 살펴볼 수 있는 스크립트를 개발해 보겠습니다. 이러한 각 클래스에 WMI 클래스가 저장되어 있다는 것은 알고 있으므로 이를 나열하는 것부터 시작하겠습니다.

WMI 클래스

WMI 리포지토리가 CIM(Common Information Model)을 저장한다고 설명한 내용을 기억할 것입니다. 이 모델은 CIMV2(V2는 버전 2를 의미) 네임스페이스 내에 저장됩니다. CIMV2 네임스페이스를 자세히 들여다보면 모델을 구성하는 모든 WMI 클래스를 볼 수 있습니다. 다음 스크립트가 이러한 작업을 수행합니다.

strComputer = "."
Set objWMIService=GetObject("winmgmts: _
    {impersonationLevel=impersonate}!\\" & _
    strComputer & "\root\cimv2")

For Each objclass in 
    objWMIService.SubclassesOf()
    Wscript.Echo objClass.Path_.Class
Next

이 스크립트가 정확히 어떻게 작동하는지 살펴보겠습니다. GetObject를 호출하면 SWbemServices 개체가 반환됩니다. GetObject는 스크립트 가능한 COM 개체에 대한 참조를 반환하는 VBScript 함수입니다. 이 경우에는 "winmgmts:…" 문자열을 전달하므로 GetObject는 WMI Scripting Library로부터 개체를 반환합니다. GetObject로 전달하는 문자열에는 연결하려는 네임스페이스가 포함되며 이 경우에 문자열은 \root\cimv2입니다. 즉, 우리가 사용해야 하는 SWbemServices 개체는 우리가 지정하는 특정 네임스페이스에 바인딩됩니다. 스크립트에서 수행할 수 있는 작업에 대해서는 SWbemServices 설명서를 참조하십시오.

ExecQuery에 대해서는 아마도 상당히 익숙할 것입니다. 이 메서드를 사용하면 사용자가 연결된 네임스페이스에 대해 WQL(Windows Management Instrumentation Query Language)을 실행할 수 있습니다. SWbemServices 개체를 네임스페이스와 연결한 다음에는 다양한 작업을 수행할 수 있습니다.

여기에서는 네임스페이스 내의 모든 WMI 클래스를 보려는 것이며 이를 위해 SubClassesOf를 사용할 수 있습니다. 설명서에는 SWbemObjectSet를 반환한다고 나와 있습니다. 그다지 친숙한 이름은 분명히 아니지만 마지막 세 개의 글자인 Set에만 초점을 맞추십시오. 그리고 WMI 스크립트 개발자라면 For Each를 사용하여 집합을 대상으로 반복할 수 있다는 것을 알고 있을 것입니다.

당연한 이야기지만 SWbemObjectSet의 각 멤버는 SWbemObject입니다. 이러한 각 SWbemObject는 CIMV2 네임스페이스의 WMI 클래스 하나를 나타냅니다. SWbemObject에 대한 설명서를 읽어 보면 이러한 클래스에 대한 출력 가능한 모든 정보를 확인할 수 있습니다.

스크립트에서는 클래스의 이름만 표시하도록 했으며 이를 위해 Path_ property에 액세스했습니다. 사실은 Path_ property 자체도 SWbemObjectPath라는 개체이며 여기에는 다양한 자체 속성이 있습니다. 여기에서는 클래스의 이름으로 Class를 사용합니다.

즉, 네임스페이스의 모든 WMI 클래스를 표시할 수 있는 것은 물론이고 이러한 클래스와 관련된 다양한 다른 항목을 표시하도록 간단하게 업데이트할 수 있는 스크립트를 작성한 것입니다. 예를 들어 WMI 클래스는 다른 WMI 클래스의 확장이 될 수 있습니다. 자동차(Win32_Car)를 모델링하고 있지만 스테이션왜건을 관리해야 한다고 가정해 보십시오. 자동차의 모델에 대한 모든 사항은 스테이션왜건에도 적용됩니다.

그러나 환상적인 원목 패널 채용 여부를 나타내는 부울과 같은 몇 가지 추가 항목이 필요합니다. 이러한 경우에 Win32_Car 기능을 모두 다시 만드는 방법보다는 Win32_Car 클래스를 확장하여 모든 새로운 속성을 포함시킬 수 있는 메커니즘을 원할 것입니다. WMI에는 이러한 메커니즘이 포함되어 있습니다.

WMI 클래스가 다른 WMI 클래스에서 속성을 상속하는지 보려면 해당 WMI 클래스와 연결된 SWbemObject 클래스의 Derivation_ 속성을 확인하면 됩니다. 그림 2에는 CIMV2 네임스페이스의 WMI 클래스를 이러한 클래스가 파생되는 클래스 목록과 함께 표시하는 스크립트가 나와 있습니다.

그림 2 CIMV2 클래스 파생

strComputer = "."
Set objWMIService=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
    strComputer & "\root\cimv2")

For Each objclass in objWMIService.SubclassesOf()
    WScript.StdOut.Write objclass.Path_.Class
    arrDerivativeClasses = objClass.Derivation_ 
    For Each strDerivativeClass in arrDerivativeClasses 
       WScript.StdOut.Write " <- " & strDerivativeClass
    Next
    WScript.StdOut.Write vbNewLine
Next

    For Each objNameSpace in colNameSpaces
        Call EnumNameSpaces(strNameSpace & "\" & objNameSpace.Name)
    Next
End Sub

이 스크립트의 시작 부분은 앞서 보았던 스크립트와 마찬가지이며 문자열을 표시한 다음 자동으로 새 줄을 추가하지 않도록 WScript.Echo 대신 WScript.StdOut.Write를 사용하고 있습니다. 참고로 Wscript.exe 대신 Cscript.exe를 사용하여 스크립트를 실행해야 StdOut.Write가 작동합니다.

SWbemObject 설명서를 보면 Derivation_ 속성이 있음을 알 수 있습니다. 이 속성은 현재 클래스가 파생된 클래스의 이름을 포함하는 문자열의 배열입니다. 우리의 스크립트에서는 For Each를 사용하여 이 배열을 대상으로 반복하여 모든 클래스를 ASCII 화살표로 분리하여 표시했습니다. GetObject가 반환하는 SWbemServices 개체를 사용하는 데 익숙해지고 WMI Scripting Library 설명서에 나와 있는 가능성에 대해 충분히 읽어 본 다음에는 이렇게 이름이 이상한 개체의 속성과 메서드를 테스트하여 무엇이 가능한지 알아낼 수 있습니다.

스크립트에서 언제 어떤 WMI Scripting Library 개체를 사용할지 이해하면 여러분의 WMI 스크립팅 실력을 한 차원 발전시킬 수 있습니다. 앞에서 작성한 스크립트를 그냥 사용하는 데서 그치지 않고 ExecQuery를 실행할 수 있는 이유나 Properties_ 속성 참조를 사용할 수 있는 이유를 이해할 수 있습니다. 그리고 그 이상으로 발전시킬 수 있습니다.

Scriptomatic 도구가 여러분의 기대에 미치지 않습니까? 걱정하지 마십시오. 여러분이 직접 원하는 대로 수정하면 됩니다. 나아가서 여러분이 개발한 도구를 판매할 수도 있을 것입니다. 이 경우에는 로열티 수표를 받을 수 있는 방법을 scripter@microsoft.com으로 알려 주십시오.

속성

각 WMI 클래스는 속성과 메서드 집합을 사용하여 관리할 수 있는 어떤 대상을 모델링합니다. 속성은 대상의 특성입니다. 예를 들어 프로세스는 ID와 우선 순위가 있으며 일정한 양의 메모리를 사용합니다. 이러한 속성은 모두 Win32_Process WMI 클래스에 포함되어 있습니다.

엔터티를 관리할 클래스를 식별한 다음에는 사용 가능한 속성을 조사하여 관리하려는 항목이 관리 모델에 있는지 확인하십시오. SWbemObject 클래스에는 Properties_라는 속성이 포함되어 있습니다. 재미있지 않습니까? 이 속성의 값은 SWbemProperty 개체의 컬렉션을 포함하는 SWbemPropertySet 개체입니다. 이러한 각 SWbemProperty 개체는 SWbem­Object와 연결된 WMI 클래스에 있는 한 속성에 해당됩니다. SWbem* 형식의 이름 때문에 상당히 복잡해 보이지만 실제로는 그렇게 복잡하지 않습니다. 그림 3을 살펴보시기 바랍니다.

fig03.gif

그림 3 바인딩된 WMI 클래스의 속성을 공개하는 SWbemObject (더 크게 보려면 이미지를 클릭하십시오.)

SWbem*으로 시작하는 클래스는 WMI Scripting Object Library의 멤버이며 개발자가 WMI를 사용할 수 있게 해 주는 개체입니다. 관리할 수 있는 항목의 WMI 모델에 포함되는 것이 아닙니다.

그림 3에서 SWbemObject는 Property_1, Property_2 및 Property_3 속성을 가지는 WMI 클래스인 Win32_SomeClass를 나타내며, 자체 Properties_ 속성을 통해 이를 공개하고 있습니다. 물론 예를 들어 다른 WMI 클래스인 Win32_SomeOther­Class에 바인딩된 경우 해당 속성 이름은 변경되지 않고 여전히 Properties_이지만 바인딩되는 클래스 속성의 경우에는 다를 것입니다.

기본적으로 SWbemObject는 바인딩되는 특정 WMI 클래스의 속성을 맡아서 처리하지만 동일한 Properties_ 메커니즘으로 그러한 다른 속성에 접근할 수 있도록 해 줍니다. 이해가 되십니까? 다시 커피 한 잔을 들이키고 이 다이어그램을 찬찬히 들여다보십시오. 어떤 의미인지 이해될 것입니다.

그림 4의 스크립트는 SWbemObject와 해당 Properties_ 속성을 사용하여 Win32_Service WMI 클래스의 모든 속성을 검색하고 표시하고 있습니다. 스크립트의 시작 부분은 이제 익숙할 것입니다. 여기에서 기본적인 변경 사항은 네임스페이스와 WMI 클래스를 외부로 내보내서 변경하기 쉽게 했다는 것입니다. 예를 들어 strClass의 값을 Win32_Service 대신 Win32_BIOS로 변경하여 해당 클래스의 속성을 볼 수 있습니다. For Each 루프에서는 SWbemPropertySet 컬렉션(objClass.Properties)을 대상으로 반복하여 각 SWbemProperty의 Name을 표시했습니다.

그림 4 Win32_Service의 속성 얻기

strComputer = "."
strNameSpace = "root\cimv2"
strClass = "Win32_Service"

Set objClass = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ 
    strComputer & "\" & strNameSpace & ":" & strClass)

WScript.Echo strClass & " Class Properties"
WScript.Echo "------------------------------"

For Each objClassProperty in objClass.Properties_
    WScript.Echo objClassProperty.Name
Next

메서드

마지막으로, 일부 WMI 클래스는 관리 가능한 개체의 속성이나 특성을 모델링하는 것에서 그치지 않고 엔터티가 수행할 수 있는 동작이나 작업, 또는 엔터티에 대해 수행할 수 있는 작업에 대한 액세스를 제공하는 메서드를 포함하고 있습니다.

WMI 클래스의 모든 메서드를 반환하는 스크립트(그림 5 참조)의 형식은 속성을 반환하는 스크립트와 거의 비슷합니다.

그림 5 WMI 클래스의 메서드 얻기

strComputer = "."
strNameSpace = "root\cimv2"
strClass = "Win32_Service"

Set objClass = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ 
    strComputer & "\" & strNameSpace & ":" & strClass)

WScript.Echo strClass & " Class Methods"
WScript.Echo "---------------------------"

For Each objClassMethod in objClass.Methods_
    WScript.Echo objClassMethod.Name
Next

물론 Properties_ 속성 대신 Methods_ 속성이 사용되었다는 것이 차이점입니다. 그렇다면 이러한 메서드가 받는 매개 변수의 형식을 표시하는 것도 가능할까요? 실제로 메서드가 있는 WMI 클래스만 표시하는 것은 어떨까요? 이 칼럼을 읽은 다음에는 여러분 스스로에게 이러한 질문을 하고 스크립트를 작성해 보도록 하십시오.

이번 기사가 WMI의 깊은 곳을 탐험하는 방법에 대한 유용한 내용이 되었기를 기대합니다. 울창한 SWbem*의 숲을 헤치고 나가는 일은 이제 여러분의 몫입니다. 이러한 여행에 스크립트는 간단하고 훌륭한 동반자가 될 것입니다. 겨울 캠핑을 좋아했던 두 친구는 그다지 운이 없었나 봅니다. 한겨울에는 발효 음료를 충분하게 가지고 다니기 힘들었는지 그다지 깊은 곳까지 정복하지는 못했다고 전해집니다.

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