예전에는 개발 환경이 간단히 로컬 데이터베이스에 접속하는 기능만 있으면 충분했었지만 현재의 애플리케이션은 원격 애플리케이션 서버에 존재하는 미들웨어를 통해 데이터베이스에 접속하거나 SOA(Service Oriented Architecture) 환경에서는 웹 서비스 프로토콜을 이용하여 웹 서버를 통해야만 미들웨어에 접근할 수 있는 경우도 많다. 서버라고는 1, 2대로 충분했던 시절이 지금은 웹 서버만 50대가 넘는 경우도 어렵지 않게 찾아볼 수 있다.
전체적인 시스템의 구조가 복잡해짐에 따라 크게 대두되는 문제 중 하나는 ‘이들 복잡한 시스템을 어떻게 관리할 것인가’이다. 기업이 어느 기술이나 시스템을 도입하는 데 있어서 중요한 의사 결정 사항 중 하나는 관리의 편의성이다. 즉 다운사이징(down-sizing)이라는 개념 하에 값싼 서버를 다수 사용함으로써 대형 서버를 사용하는 것보다 적은 비용으로 동등하거나 더 높은 성능을 낼 수 있는 것이 사실이다.
하지만 시간이 흘러감에 따라 수많은 서버를 관리해야 하는데 소요되는 비용 역시 무시할 수는 없는 것이다. 여러분이 기업 전산실에 근무하고 있고 관리해야 할 웹 서버가 50대에 이른다고 생각해 보자. 그리고 상사가 각 서버에 어떤 운영체제가 설치되어 있고 서비스 팩(패치)은 어떤 것이 설치되어 있으며, 응용 프로그램들은 어떤 것들이 설치되어 있는지 보고서를 작성하라고 한다면 미칠(?) 노릇일 것이다. MOM(Microsoft Operation Manager)나 IBM의 티볼리와 같은 관리 솔루션이 없다면 야근 작업으로 보고서를 작성해야 할 것이며 이와 같은 작업은 신종 바이러스가 출현하고 핫픽스(hot-fix)가 등장하면 서버들의 핫픽스 설치 여부를 확인하기 위해 반복될 수도 있다.
WMI 앞의 예처럼 반복적인 관리 이슈를 해결하기 위해 마이크로소프트(이하 MS)는 윈도우 운영체제에 대한 관리를 위한 인프라를 제공한다. 이것이 WMI(Windows Management Instrumentation)이며 WMI를 통해 개발자 혹은 관리자는 윈도우 시스템의 다양한 정보를 얻거나 시스템 관리에 필요한 행동을 취할 수 있다.
WMI로 할 수 있는 유용한 작업이 무엇인가를 보여주는 좋은 예를 살펴보자. <리스트>은 시스템에 설치된 핫픽스의 목록을 나열하는 VBScript 코드이다. 이 코드는 WMI가 제공하는 모니터(winmgmts)를 통해 Win32_QuickFixEngineering의 목록을 나열하는 것이다. 물론 Win32_QuickFixEnginerring이 의미하는 것이 시스템에 설치된 핫픽스임에는 두말할 나위도 없다(Win32_QuickFixEnginerring은 WMI 클래스이다. WMI 클래스에 대한 내용은 후에 상세히 다루겠다).
리스트> | <리스트> Listing System Hot-Fix(VBScript 버전)리스트> |
| |
| Option Explicit Dim objSWbemService, colSWbemObjectSet, objSWbemObject Dim strComputer
strComputer = "." Set objSWbemService = GetObject("winmgmts:\" & strComputer) Set colSWbemObjectSet = _ objSWbemService.InstancesOf("Win32_QuickFixEngineering")
For Each objSWbemObject in colSWbemObjectSet If objSWBemObject.Description <> "" Then WScript.Echo objSWbemObject.Description End If Next> |
| |
<리스트> 역시 동일한 작업을 하는 C# 코드로서 WMI를 액세스하는 닷넷 예제 코드이다. <리스트>이나 <리스트>와 같은 작업뿐만 아니라 WMI를 통해 시스템에 다양한 정보를 취득할 수 있다. 예를 들어, 현재 시스템의 Win32 서비스들의 목록, 이들 중에서 현재 ‘작동중’인 서비스들의 목록, 혹은 시스템에 설치된 응용 프로그램들과 같은 시스템 소프트웨어에 대한 정보들을 얻을 수 있으며, CPU 정보, 물리 메모리 양, 네트워크 디바이스 정보, 디스크에 대한 정보 등 하드웨어 정보 역시 WMI를 통해 모두 얻을 수 있다. 이러한 정적인 시스템 정보뿐만 아니라 WMI를 통해 시스템을 리부팅한다든지, 특정 서비스를 시작하거나 중단하는 등의 작업을 수행할 수도 있다. WMI는 이처럼 윈도우 운영체제에 전반적인 관리 기능을 제공하는 중앙집중적인 서비스를 제공한다.
| <리스트> Listing System Hot-Fix(C# 버전)리스트> |
| |
| using System; using System.Management;
public class ListHotFixes { public static void Main() { ManagementClass cls = new ManagementClass(@"\\.\root\cimv2:Win32_QuickFixEngineering"); ManagementObjectCollection col = cls.GetInstances();
foreach(ManagementObject obj in col) { string desc = (string)obj["Description"]; if (desc != "") Console.WriteLine(desc); } } } |
| |
WMI 정의 WMI가 유용하다고 느껴지는가? 그렇다면 다른 사람이 WMI가 무엇이냐고 물었을 때 대답해 줄 수 있도록 구체적으로 WMI가 무엇인지 정의를 해보자. WMI는 윈도우 시스템과 일반 소프트웨어에 대한 관리와 Instrumentation을 위한 단일 인터페이스를 제공하는 윈도우의 인프라이다. DMTF(Distributed Management Task Force)는 네트워크에서 분산되어 있는 다양한 시스템, 디바이스에 대한 관리 표준을 정하기 위해 WBEM(Web-Based Enterprise Management) 표준을 제정했고 이것을 마이크로소프트에서 구현한 것이 WMI인 것이다. 말이 나왔으니 표준에 대해서 약간 더 이야기 하자면 DMTF는 WBEM 표준에서 다양하고 분산된 시스템 자원들에 대한 정보를 액세스하기 위해 단일 데이터 스키마를 표준으로 제정하고 이 스키마를 기준으로 자원 정보를 액세스하도록 권고하고 있다. 이 데이터 스키마 표준은 CIM(Common Information Model)로서 WMI 역시 CIM 스키마를 따르고 있다.
용어에서 혼동하지 말아야 할 것이 있다. 필자 역시 처음 WMI를 접했을 때 용어가 혼란스러웠는데 WBEM이란 용어에 걸맞지 않게 CIM이나 WMI는 웹과 별 관계가 없다. 웹 기반의 관리인 듯하지만 실제로 웹을 통해 시스템을 관리하는 내용은 WMI와 거의 무관하다. 둘째로 DMFT에서 정의한 표준은 관리 대상이 되는 자원의 정보를 중앙집중적으로 저장하는 방법에 대한 표준일 뿐이지 이 정보에 접근하는 표준은 정의하고 있지 않다.
예를 들어 윈도우에서 CIM을 구현한 WMI는 DCOM(Distributed-COM) 프로토콜과 COM 객체를 통해서 CIM Repository에 접근하는 반면, 썬의 솔라리스에서는 자바 API를 사용하고 하위 네트워크 프로토콜 역시 윈도우의 그것과 매우 다르다는 점이다. 마지막으로 한 가지 당부할 내용은 앞서 언급한 ‘데이터 스키마 표준’에서 스키마는 XML 스키마의 그것과 무관하다. XML 관점에서의 스키마라기보다는 데이터베이스 관점에서의 스키마라고 생각하는 것이 더 적절할 것이다. 이제 곧 이 스키마의 의미에 대해서 상세히 논하게 될 것이니 아직까지는 이 정도로만 이해해두기 바란다.
WMI의 핵심은 다양한 시스템 자원을 단일한 인터페이스와 일관된 데이터 스키마를 통해 관리하고자 하는 것이다. 좀 더 구체적으로 예를 들어 보자. 만약 여러분이 관리하는 서버의 물리 메모리의 양을 프로그래밍적으로 알고자 한다면 Win32 API(실제로 필자도 어떤 API를 호출해야 하는지 모른다)를 생각할 것이다. 그리고 현재 시스템에 설치된 핫픽스가 어떤 것인가를 알고자 한다면 레지스트리를 살펴볼 것이다. 이처럼 어떤 자원에 대한 정보를 얻거나 작업을 시도할 때, 그 자원에 종속적인 API나 프로토콜을 사용해야 하는데 이것이 매우 불합리하고 복잡하다는 것이다. 하지만 WMI는 WMI가 제공하는 데이터 스키마와 API를 통해 다양한 관리 작업을 단일하게 수행할 수 있도록 하기 위함이다. 멋지지 않은가?
WMI 인프라스트럭처 WMI는 WBEM 표준에 의거하여 하부 구조를 가지고 있다(<그림>). 가장 핵심적인 부분은 CIMOM(CIM Object Manager)으로서 CIM 표준이 제시하는 데이터 스키마를 저장하는 CIM Repository를 관리하고 다양한 정보를 수집하며 클라이언트로부터의 요청을 서비스하는 역할을 수행한다. WMI에서 CIMOM은 winmgmts.exe라 불리는 프로세스에 의해서 서비스된다. 윈도우 98/Me의 운영체제에서 이 프로세스는 일반 프로세스와 동일하게 수행되지만 윈도우 NT/2000/XP/2003에서는 Win32 서비스로서 작동된다.
CIMOM은 운영체제, 하드웨어, 소프트웨어, 응용 프로그램 등 다양한 관리 대상으로부터 정보를 수집해야 하기 때문에 각 관리 대상에 대한 WMI 프로바이더(provider)와 통신하게 된다. 예를 들어 Win32 WMI 프로바이더는 윈도우 운영체제에 대한 정보와 하드웨어, 파일시스템 등에 대한 전반적인 시스템 정보를 제공하는 한편 이벤트 로그(Event Log) 프로바이더는 윈도우의 이벤트 로그로부터 다양한 정보를 CIMOM에게 제공한다. 비슷하게 다양한 프로바이더에서 다양한 정보들이 CIMOM으로 제공되며 CIMOM은 이들 정보를 WMI 클라이언트에게 제공하게 된다. 이들 프로바이더는 윈도우 운영체제에 기본적으로 포함되고 설치되는 것들이 있는가 하면, 추가로 CD에서 설치해야 하는 것들도 있다. 대표적인 예로 MSI 프로바이더는 윈도우 인스톨러에 의해 설치된 설치 패키지에 대한 정보를 제공하는 프로바이더이다. 이 프로바이더는 윈도우 2000 이상에 포함되어 있지만 디폴트로 설치되지 않는다. 따라서 추가 설치가 필요한 프로바이더이다. 반면 SQL*Server가 제공하는 WMI 프로바이더도 존재한다. 이 프로바이더는 SQL*Server가 설치된 경우에만 사용이 가능하다.
예상할 수 있듯이, 커스텀 프로바이더를 제작할 수도 있다. 프로바이더는 WMI에서 정의하는 일련의 COM 인터페이스를 구현하는 COM 객체일 따름이다. 따라서 여러분의 애플리케이션의 작동 상황을 보고하거나 현재 상태를 보고하는 메커니즘으로 WMI 프로바이더를 작성할 수도 있다. 닷넷 프레임워크는 이러한 커스텀 프로바이더를 이미 제공하고 있으므로 손쉽게 여러분의 애플리케이션 상태를 WMI를 통해 제공할 수도 있다. 상세한 내용은 잠시 후에 다루기로 하자. WMI에서 제공하는 정보를 액세스하고자 하는 클라이언트는 CIMOM에 접근해야 한다. CIMOM에 접근하기 위해서 WMI는 일련의 API를 제공한다. 이 API는 C++를 위한 COM 인터페이스와 VBScript, VB 6.0, 델파이 등의 개발 도구를 위한 COM 객체의 집합이다.
<리스트>은 Scripting API for WMI를 이용해 WMI에 접근한 예제이다. 닷넷 프레임워크 역시 WMI를 위한 클래스 라이브러리를 제공한다. 이들 클래스는 System.Management 네임스페이스 하에 존재하며 이 네임스페이스는 System.Management.dll 어셈블리 내에 정의되어 있다. SystemManagement 네임스페이스의 클래스들은 WMI의 COM API를 닷넷 환경에서 사용이 용이하도록 랩핑(wrapping)하고 있으며 추가적으로 WMI 프로바이더 역할도 수행할 수 있도록 되어 있다.
| <그림> WMI 인프라스트럭처그림> |
CIM 구조 WBEM은 CIM 데이터 스키마를 표준으로서 정의한다고 언급했었다. CIM 데이터 스키마 표준에 대해서 좀 더 상세히 알아보도록 하자. CIM은 크게 CIM 클래스, 인스턴스(instance), 네임스페이스로 구성되어 있다. CIM 클래스, 인스턴스, 네임스페이스에 대해 모두 상세히 언급하고자 하면 책 한 권이 나올 분량이므로 여기서는 생략하도록 하겠다.
CIM 클래스와 인스턴스 CIM은 시스템의 여러 자원에 대한 정보를 표준 데이터 구조로서 제공할 수 있도록 정의된 표준이다. CIM의 구조는 객체지향 데이터베이스와 매우 유사하다. 데이터베이스는 테이블을 정의하고 테이블의 각 row는 테이블이 정의하는 데이터 엔티티에 대한 구체적인 인스턴스로 볼 수 있다. 마찬가지로 CIM은 제공하고자 하는 정보의 구조를 클래스로서 정의한다. 데이터베이스의 테이블이 CIM의 클래스에 대응된다고 생각하면 이해가 쉽겠다.
CIM 클래스는 속성(property)과 메쏘드(method) 그리고 한정자(qualifier)를 갖는다. 속성과 메쏘드는 쉽게 이해가 갈 것이다. 그렇지만 한정자는 무엇일까? 한정자는 닷넷의 특성(attribute)과 동등한 성격과 기능을 갖는다. 닷넷에서 닷넷 클래스에 대해 [serializable]이나 [Transaction]과 같은 특성을 부여하는 것과 마찬가지로 CIM 클래스에 대해 메타 데이터로서 다양한 한정자를 설정할 수 있다. 한정자는 WMI 인프라(CIMOM)와 프로바이더가 인식하는 한정자이거나 단순한 description과 같은 한정자가 존재하기도 한다. 한정자는 어떠한 프로바이더에 의해 클래스가 사용되는가에 따라 매우 상이하다. 따라서 클래스를 정의하는 프로바이더에 대한 문서를 살펴봐야만 한다. WMI에서 사용되는 대부분의 프로바이더에 대한 정보는 Platform SDK의 문서나 MSDN 문서를 참고하기 바란다.
CIM 클래스의 속성은 클래스마다 고유의 속성이 정의될 수도 있지만 모든 클래스에 공통적으로 갖는 속성이 있다. 이들 속성은 시스템 속성이라 불리며 모든 클래스는 이 속성들을 갖는다. 이들 속성은 클래스의 경로(path), 네임스페이스, 슈퍼클래스 등을 기술하고 있다. 경로, 네임스페이스, 슈퍼 클래스 등에 대한 내용은 뒤에서 점차로 설명하기로 한다. 모든 클래스는 시스템 속성을 갖고 있으며 이들은 클래스 혹은 인스턴스의 메타 정보를 기술하는 용도로 사용된다는 점만 기억해 두자. 구체적인 시스템 속성들의 목록이 궁금하다면 WMI SDK 문서를 참고하기 바란다.
데이터베이스의 테이블과는 다르게 CIM 클래스는 상속이 가능하다. 따라서 수퍼 클래스(CIM에서는 상위 클래스를 SUPERCLASS라는 용어를 사용한다)의 속성/메쏘드/한정자를 파생 클래스는 상속받게 된다. CIM 클래스들은 인스턴스를 갖을 수 있는가 여부에 따라서 abstract 클래스와 concrete 클래스로 나뉜다. abstract 클래스는 클래스의 abstract 한정자가 존재하고 그 값이 true이면 abstract 클래스가 되며 그렇지 않다면 concrete 클래스가 된다. CIM 클래스에 대해 이 정도의 이해를 했다면 WBEM이 정의하는 CIM 데이터 스키마 표준이 무엇인가를 이해할 수 있다. WBEM은 다양한 시스템 자원 정보를 위한 CIM 클래스들을 표준으로 제공하고 있으며 WBEM에서 미리 정의된 클래스들이 CIM 표준 스키마가 되는 것이다.
실제로 WMI에는 CIM 표준 스키마로서 다양한 클래스들이 존재한다. 이들 클래스들은 모두 CIM_으로 시작하는 클래스 이름을 갖고 있다. 예들 들어 CIM_ManagedSystemElement, CIM_LogicalElement, CIM_PhysicalElement, CIM_Service, CIM_Thread 등 300여 개의 표준 클래스들이 존재한다. 이들 클래스의 대부분은 abstract 클래스로서 제공된다. WBEM 표준을 따르는 관리 소프트웨어(WMI와 같은)는 이들 추상 클래스로부터 상속받아 구체적인 정보를 제공하는 클래스를 정의함으로써 표준을 준수하는 것이다. WMI 역시 이들 표준으로부터 상속받아 구체적인 윈도우의 정보를 제공하는데 CIM_Service는 WBEM의 표준이며, CIM_Service로부터 상속받은 WMI의 클래스는 Win32_BaseService, Win32_Service 클래스이다. CIM_Service 클래스는 abstract 클래스이므로 인스턴스가 존재하지 않으며 Win32_BaseService는 WMI의 구현이지만 역시 abstract 클래스이므로 인스턴스가 존재하지 않는다. 반면 Win32_Service 클래스는 concrete 클래스로서 인스턴스가 존재한다.
<리스트>에서 사용한 Win32_QuickFixEngineering 클래스는 CIM_LogicalElement 클래스에서 파생된 클래스이며 abstract 한정자가 정의되어 있지 않다. 즉 concrete 클래스이다. 따라서 인스턴스가 존재하며 이 클래스의 인스턴스는 시스템에 설치되어 있는 핫픽스의 목록이 되는 것이다. 비슷하게 Win32_Service 클래스 역시 concrete 클래스이며 인스턴스가 존재하고 이 인스턴스는 시스템의 Win32 서비스 목록이 되는 것이다.
Win32_Service 클래스의 조상 클래스인 CIM_Service 클래스는 서비스의 시작 여부를 가르키는 Started 속성(property)과 시작 유형을 나타내는 StartMode 등의 속성을 가지고 있다. 또 Win32_BaseService 클래스는 서비스가 ‘일시중지’, ‘중지’를 지원하는가 여부를 나타내는 AcceptPause, AcceptStop 등의 속성을 갖는다. 따라서 Win32_Service 클래스의 인스턴스들은 Started, StartMode, AcceptPause, AcceptStop 등 속성을 모두 가지며 Win32_Service 인스턴스에 대해 Started 속성을 검사해 서비스가 수행중인가를 알아낼 수 있는 것이다.
클래스의 인스턴스들은 서로를 구분할 수 있는 키 값을 가져야 한다. 키는 클래스의 속성들 중 한 개 이상이 키 프로퍼티로 설정되며 한 클래스의 인스턴스들이 동일한 키를 가질 수 없다. 어느 속성이 키임을 알리기 위해서는 속성에 대한 key 한정자(클래스, 속성, 메쏘드에 모두 한정자를 설정할 수 있다)의 값이 true인가를 확인하면 된다.
WMI 네임스페이스 WMI는 약 6000여 개의 클래스를 제공하고 있다. 이렇게 많은 클래스들이 존재하기 때문에 클래스의 성격이나 WMI 프로바이더에 의해 클래스들을 분류하게 되는데 클래스 분류는 WMI 네임스페이스에 의해 구분된다. WMI 네임스페이스는 클래스들을 논리적으로 서로 다른 공간에 배치함으로써 클래스를 찾거나 사용하기 용이하다는 점이다. 네임스페이스는 루트 네임스페이스로서 root가 존재하고 그 하위에 다양한 네임스페이스들이 존재한다. 가장 핵심적인 네임스페이스는 root/cimv2로서 CIM 표준 클래스들(CIM_XXXX 클래스들)과 그들에 대한 WMI의 구현 클래스(Win32_XXXX)들이 존재한다. 그 외에 root/default 네임스페이스에는 레지스트리 변경 이벤트를 제공하는 클래스들이 존재한다. 또한 root/WMI 네임스페이스에는 CIM과 무관하게 WMI에서만 제공하는 윈도우 트레이스(trace) 정보 등에 관련된 클래스들이 존재한다.
이외에도 root 네임스페이스 하위에는 다양한 네임스페이스들이 존재하는데 이들 네임스페이스는 별도의 애플리케이션과 이 애플리케이션이 제공하는 WMI 프로바이더에 의해 제공되는 클래스들이 포함된다. 예를 들어 root\MSAPPS11은 MS 오피스 2003과 함께 생성되는 네임스페이스로서 다양한 클래스들이 워드, 엑셀, 파워포인트 등에 대한 정보를 제공한다. 예를 들어 Win32_Word11Template 클래스는 현재 열려있는 워드 문서들에서 사용 중인 워드 템플릿 파일(*.dot)의 목록을 반환한다.
네임스페이스는 손쉽게 생성할 수 있다. 네임스페이스를 생성하는 일반적인 규칙은 CIM 표준으로부터 상속받거나 윈도우 시스템 정보에 대한 클래스들은 일반적으로 root/cimv2 네임스페이스에 존재하는 것이며 그외에 특정 애플리케이션에 대한 정보를 제공하는 클래스는 root 밑에 애플리케이션을 구별할 수 있는 별도의 네임스페이스를 생성하는 것이다. 나중에 닷넷에서 네임스페이스를 직접 작성하고 클래스를 생성하는 예제를 다뤄볼 것이다.
System 클래스 모든 네임스페이스에는 두 개의 __(under score)로 시작하는 클래스들이 있다. __SystemClass, __Provider, __Namespace 등이 그 예인데, 이들 클래스는 모두 CIMOM 자체가 제공하는 클래스로서 CIM Repository 자체에 대한 정보를 제공한다. __SystemClass 클래스는 시스템 클래스들에 대한 추상 클래스 역할을 하며 __Provider는 CIM 표준 클래스로서 구체적인 클래스는 __Win32Provider 클래스이다. __Win32Provider 시스템 클래스는 네임스페이스 내에 등록된 WMI 프로바이더에 대한 정보를 제공한다. 비슷하게 __Namespace 클래스는 해당 네임스페이스 내에 존재하는 하위 네임스페이스들의 정보를 제공한다. <리스트>은 root 네임스페이스의 하위 네임스페이스를 열거하는 예제 코드이다. 물론 네임스페이스는 트리 구조를 가지므로 WMI의 모든 네임스페이스를 열거하고자 한다면 <리스트>의 코드를 약간 수정하여 재귀 호출(recursive call)을 통해 WMI의 모든 네임스페이스를 열거할 수 있을 것이다. 이처럼 시스템 클래스는 CIM Repository 자체에 대한 정보나 CIMOM 수준에서 제공되는 정보를 담는데 사용된다.
| <리스트> 네임스페이스를 열거하는 닷넷 예제 코드리스트> |
| |
| using System; using System.Management;
public class EnumWmiNamespaces { public static void Main() { ManagementClass cls = new ManagementClass(@"\\.\root:__Namespace", null); ManagementObjectCollection col = cls.GetInstances();
foreach(ManagementObject obj in col) { string desc = (string)obj["Name"]; Console.WriteLine(desc); } } } |
| |
Association 클래스 WMI의 CIM Repository는 데이터베이스와 흡사하다. 하지만 관계형 데이터베이스가 갖는 특성인 JOIN과 같은 관계 연산은 제공하지 않는다. 대신 유사한 관계 클래스(association class)를 제공한다. 관계 클래스는 두 클래스의 관계를 나타내는 클래스로서 관계형 데이터베이스에서 두 테이블의 관계를 기술하는 테이블과 유사하게 생각하면 된다. 관계 클래스는 클래스의 association 한정자의 값이 true인 클래스를 말한다.
구체적으로 예를 들어보자. 컴퓨터 시스템을 나타내는 Win32_ComputerSystem 클래스가 있다. 그리고 현재 수행중인 프로세스를 나타내는 Win32_Process 클래스가 있다. 그렇다면 특정 컴퓨터에서 수행중인 프로세스들은 어떻게 표현할까? 이러한 표현을 위해 Win32_SystemProcesses 관계 클래스가 존재하고 이 클래스는 Win32_ComputerSystem과 관련 있는 Win32_Process를 나타낸다.
즉 Win32_SystemProcesses 클래스의 인스턴스는 특정 컴퓨터 시스템과 프로세스의 관계를 나타내며 그 관계의 의미는 ‘해당 컴퓨터에서 수행중인 프로세스’가 될 것이다. 관계 클래스는 상위 관리 객체로부터 하위 객체로 내비게이션(navigation)해 나갈 때 매우 유용하다. 만약 여러분이 Win32_ComputerSystem 클래스의 인스턴스를 갖고 있다면 관계 클래스의 인스턴스들을 이용해 해당 컴퓨터에서 수행중인 프로세스들, 디스크, 파티션 구조, 프로그램 그룹 등의 정보를 드릴다운(Drill-down)해 나갈 수 있다. WMI에는 다양한 관계 클래스들이 존재하며 이들에 대한 상세 목록은 WMI 프로바이더에 따라서 차이가 나므로 WMI SDK 문서를 참조하기 바란다.
이벤트와 Event 클래스 WMI는 클래스와 인스턴스를 통해 정보를 제공하는 것 외에도 이벤트를 발생시킬 수 있다. 예를 들어 특정 프로세스가 종료되면 어떤 행동을 취하는 WMI 클라이언트를 작성할 수 있는 것이다. WMI가 제공하는 이벤트는 인스턴스 이벤트와 커스텀 이벤트로 두 가지로 나누어 볼 수 있다. 인스턴스 이벤트는 인스턴스가 생성/삭제/변경됨을 알리는 이벤트이다. 매우 간단하게 보이지만 이 이벤트의 활용도는 무궁무진하다. 예를 들어 보자. 앞서 언급한 프로세스가 종료되었음을 알고자 한다면 어떻게 하면 될까? 윈도우의 프로세스를 나타내는 WMI 클래스(CIM 클래스)는 Win32_Process 클래스이다. 프로세스가 종료된다는 것은 Win32_Process 클래스의 인스턴스가 삭제됨을 의미하므로 Win32_Process 인스턴스의 삭제 이벤트를 받는다면 프로세스의 종료를 감지할 수 있을 것이다.
WMI는 인스턴스 이벤트로 __InstanceOperationEvent 시스템 클래스에서 파생된 4개의 이벤트 크래스를 정의하고 있다. 이들은 __InstanceCreationEvent, __InstanceDeletionEvent, __InstanceModificationEvent, __MethodInvocationEvent인데 클래스 이름만 봐도 어떤 용도인지 알 수 있다. WMI의 관점에서 보면 인스턴스 이벤트의 발생은 이들 인스턴스 이벤트 클래스의 인스턴스가 생성되는 것이다. 따라서 이벤트 발생을 감지하고자 한다면 이들 클래스의 인스턴스가 발생했나를 살펴보는 행위가 되며, 구체적으로 이벤트 발생을 감지하고자 하는 행동을 이벤트 가입(event subscription)이라 한다. 이들 인스턴스 이벤트 클래스는 TargetInstance 속성을 갖고 있어서 이벤트를 발생한 대상이 어떤 인스턴스(예를 들어 Win32_Process의 인스턴스)인가를 알아낼 수 있다. 특히 __InstanceModificationEvent 클래스는 PreviousInstance 속성도 가지고 있어서 인스턴스가 어떻게 변경되었는가도 알아낼 수 있다.
<리스트>는 프로세스 종료를 감시하는 간단한 WMI 이벤트 가입 예제이다. 이 코드는 Win32_Process 클래스의 인스턴스가 삭제될 때(프로세스가 종료될 때이다) __InstanceDeletionEvent 클래스의 인스턴스가 생성되므로 이를 매 1초마다 감시하는 것이다. 다시 말해 Win32_Process의 인스턴스가 삭제되는 것을 감시하는 코드이다. <리스트>를 수행시키고 임의의 프로세스가 종료하면 1초 이내로 종료된 프로세스가 어떤 것인가를 콘솔에 출력할 것이다. <리스트>에 등장하는 여러 닷넷 클래스에 대해서는 걱정하지 말자. 후에 WMI에 관련된 닷넷 클래스들을 다룰 것이다. 다만 <리스트>에서 인스턴스 이벤트를 어떻게 활용하는가에 주안점을 두고 살펴보자.
| <리스트> 프로세스 종료 감시 WMI 이벤트 예제리스트> |
| |
| using System; using System.Management;
public class MonitorProcessTerminate { public static void Main() { WqlEventQuery query = new WqlEventQuery("__InstanceDeletionEvent", new TimeSpan(0, 0, 1), // every second "TargetInstance ISA 'Win32_Process'") ManagementEventWatcher watcher = new ManagementEventWatcher(query); ManagementBaseObject objEvent = watcher.WaitForNextEvent(); // getting Win32_Process instance ManagementBaseObject objProcess = (ManagementBaseObject)objEvent["TargetInstance"]; Console.WriteLine("{0} Process terminate...", objProcess["Caption"]); watcher.Stop(); } } |
| |
인스턴스 이벤트는 간단하지만 강력하다. 하지만 인스턴스 이벤트로는 해결하기 어려운 이벤트들이 존재한다. 레지스트리를 예를 들어 본다면, 특정 레지스트리의 키가 변경되었음을 이벤트로 알리고자 한다고 생각해 보자. 단순히 생각하면 레지스트리 키에 대한 인스턴스가 존재하고 그 인스턴스의 변경 이벤트를 받으면 될 것이다. 하지만 레지스트리의 키에 대한 WMI 클래스는 존재하지 않으며 따라서 키에 대한 인스턴스도 존재하지 않는다. 이처럼 WMI 인스턴스와 무관한 이벤트를 발생하고자 한다면 커스텀 이벤트를 사용해야 한다.
WMI 커스텀 이벤트는 __ExtrinsicEvent 시스템 클래스에서 파생된 클래스를 정의하면 된다. 레지스트리의 경우 __ExtrinsicEvent 클래스에서 파생된 RegistryEvent 클래스를 제공하며 다시 RegistryEvent에서 파생된 RegistryValueChangeEvent, RegistryKeyChangeEvent, RegistryTreeChangeEvent 클래스를 제공한다. 이들은 레지스트리의 값, 키, 트리 및 하위 트리의 변경을 알리는 이벤트를 발생한다. 인스턴스 이벤트와 달리 커스텀 이벤트는 클래스의 프로퍼티를 통해 받고자 하는 이벤트를 결정하는 데 사용되곤 한다. 레지스트리 이벤트의 경우, Hive 프로퍼티에 레지스트리 하이브를 설정하고 RootPath, KeyPath, ValueName 등의 프로퍼티에 조건을 설정하는 형태로 특정 레지스트리 트리, 키, 값이 변경되는 이벤트를 수신할 수 있다. 예제 코드는 지면관계상 ‘이달의 디스켓’의 Monitor Registry.cs 파일을 참고하기 바란다.
WMI 기타 사항 WMI를 사용함에 있어서 추가적으로 알아둬야 할 사항들에 대해 몇 가지 기술한다. 비록 제목은 기타 사항이지만 WMI를 이해하는 데 있어서 반드시 필요한 부분이므로 간과하지 않도록 한다.
WMI 경로 이벤트 클래스를 포함한 WMI 클래스와 인스턴스는 경로를 갖는다. 즉, 특정 클래스나 특정 인스턴스를 찾기 위해 경로를 명시할 수 있다는 것이다. WMI의 경로는 ADSI(Active Directory Service Interface)에서 사용되는 경로와 매우 흡사하다. WMI에서 사용하는 경로는 다음과 같은 형태를 갖는다.
[\\ComputerName][\Namespace][:ClassName][.KeyProperty='value']
UNC 경로와 흡사한 WMI 경로는 먼저 컴퓨터 이름과 네임스페이스가 명시되고 : (콜론) 이후에 클래스 명이 명시된다. 클래스에 대한 경로라면 여기서 끝이 나겠지만 인스턴스에 대한 경로라면 인스턴스를 구별할 키 속성이 경로에 명시하게 된다. 만약 컴퓨터 명 대신 ‘.’이 사용되면 로컬 컴퓨터를 의미한다. 다음은 WMI 경로에 대한 몇몇 예이다.
[1] \\.\root\__NAMESPACE [2] \\Lancer\root\cimv2\Win32_Service [3] \\Svr1\root\cimv2\Win32_Service.Name='Alerter'
첫 번째 예는 로컬 컴퓨터의 root 네임스페이스에 __NAMESPACE 클래스를 나타내는 WMI 경로이며 두 번째 예는 Lancer 컴퓨터의 root\cimv2 네임스페이스의 Win32_Service 클래스를 나타낸다. 세 번째는 Svr1 컴퓨터의 root\cimv2 네임스페이스의 Win32_Service 클래스 인스턴스 중 Name이 Alerter인 인스턴스를 나타낸다. 모든 WMI 클래스 및 인스턴스는 __PATH라는 시스템 속성을 가지고 있어서 이 속성 값을 통해 해당 클래스 혹은 인스턴스의 WMI 경로를 알아낼 수 있다. 또한 __RELPATH 속성을 통해 컴퓨터 이름과 네임스페이스를 제외한 상대 경로 역시 알아낼 수 있음을 알아두자.
MOF MOF(Management Object Format)는 WMI 클래스 및 인스턴스를 기술하기 위해 WBEM에서 제정한 디스크립션 언어이다. 즉 CIM 클래스(WMI 클래스) 및 인스턴스를 더 쉽게 기술하기 위한 메타언어로서 제정된 것이다. 다음은 CIM_Process 클래스에 대한 MOF의 일부이다. 마치 C# 코드를 보는 것과 같이 클래스의 이름, 슈퍼 클래스 그리고 클래스의 한정자를 표시하고 또한 프로퍼티의 타입, 이름, 프로퍼티의 한정자 역시 모두 기술되고 있음을 알 수 있다.
[Abstract,UUID("{8502C566-5FBB-11D2-AAC1-006008C78BC7}") : ToInstance] class CIM_Process : CIM_LogicalElement { [Key : ToInstance ToSubclass DisableOverride,Read : ToSubclass,MaxLen(256) : ToSubclass] string Handle; [Units("Milliseconds") : ToSubclass,read : ToSubclass] uint64 KernelModeTime; // 생략.. }
MOF의 용도는 더 쉽게, 그리고 텍스트 파일로서 CIM 스키마를 명시할 수 있도록 하기 위함이다. ‘더 쉽게’라는 말이 의미하듯이 MOF를 사용하지 않고도 클래스나 인스턴스를 기술할 수도 있다. WMI가 제공하는 COM API 및 스크립트 API는 코드를 통해 클래스를 생성하고 클래스의 한정자를 기술하며, 클래스가 갖는 프로퍼티, 메쏘드 등을 명세할 수 있다. 하지만 이렇게 코드를 통해 클래스를 명세해야 한다면 속된 말로 정말 ‘빡센’ 일이 아닐 수 없다. 이 때문에 MOF가 등장했고 MOF 파일을 통해 클래스와 인스턴스를 기술하고 MOF 컴파일러(mofcomp.exe)를 통해 MOF를 컴파일하기만 하면 MOF 파일에 기술된 클래스와 인스턴스가 CIM Repository에 기록된다는 말이다. 실제로 CIM 표준 클래스와 WMI가 구현한 Win32 클래스들에 대한 MOF 파일이 궁금하다면 %SYSTEM_ROOT%\system32\webm\cimwin32.mof 파일을 한번 열어보는 것도 나쁘지 않다. MOF 스펙에 대한 상세한 설명은 WMI SDK 문서나 DMTF 홈페이지의 MOF 스펙을 참고하기 바란다.
WQL 쿼리 WMI는 데이터베이스와 유사한 점이 많다. WQL(WMI Query Language)는 SQL과 비슷하게 WMI 내의 클래스 및 인스턴스, 이벤트를 조회하기 위한 언어이다. WQL은 SQL과 매우 흡사하기 때문에 별도로 이것을 배우기 위해 노력하지 않아도 된다. 다만 WQL에서만 사용되는 몇 가지 연산자와 문장 예제만 참고하면 된다. 여기서는 WQL에 대해 상세히 설명하지 않고 몇 가지 WQL 예제만을 설명하도록 하겠다. WQL은 어렵지 않다.
[1] SELECT * FROM Win32_Service [2] SELECT Handle, Caption FROM Win32_Process WHERE Caption = 'w3wp.exe' [3] SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE TargetInstance ISA Win32_Process
첫 번째 WQL은 Win32_Service 클래스의 모든 인스턴스를 반환하는 WQL 예제이다. SQL과 비교하여 전혀 다를 게 없다. 두 번째 WQL 예제는 Win32_Process 인스턴스 중 Caption 속성이 w3wp.exe인 인스턴스를 반환하되 속성은 Handle과 Caption 속성만을 사용하겠다는 의미이다. 역시 SQL과 크게 다를 바가 없다. 세 번째 예제는 이벤트를 조회하는 데 사용하는 쿼리로서 Win32_Process 클래스의 인스턴스 생성 이벤트가 발생하는 것을 매 5초 단위로 감시하라는 것이다. 이러한 WQL 쿼리를 통해 보다 효율적으로 원하는 정보만을 WMI로부터 조회할 수 있다. 예를 들어 현재 상태가 ‘running’인 NT 서비스를 알아내거나 시작 모드가 ‘자동’인 NT 서비스들이 어떤 것들이 있는가를 알아내는 것은 매우 손쉬운 작업이 된다.
동적, 정적 인스턴스 WMI에서 제공되는 클래스의 인스턴스는 대상에 대한 정보를 포함하고 있다. 이들 정보는 수시로 변하는 동적(dynamic) 정보들과 변하지 않는 정적(static) 정보들이 있을 수 있다. 예를 들어 Win32_Process 클래스의 인스턴스는 동적인 정보들이다. 프로세스들은 수시로 시작되거나 종료되며 Win32_Process 클래스는 프로세스가 현재 얼마 동안 수행중인가를 나타내는 속성들도 존재하기 때문이다. 반면 __Namespace 클래스의 인스턴스는 주어진 네임스페이스의 하위 네임스페이스를 나타낸다. 네임스페이스는 일단 생성되면 변경되는 부분이 없기 때문에 그 정보는 정적이라 할 수 있다.
윈도우 2003에는 6000여 개의 WMI 클래스들이 존재하고, 정적인 정보를 나타내는 몇몇 시스템 클래스(__Namespace 같은)를 제외하곤 대다수는 동적인 정보를 나타낸다. 동적인 정보를 나타내는 클래스는 반드시 클래스에 dynamic 한정자가 정의되고 그 값이 true여야 한다. 그렇지 않은 클래스는 정적 클래스가 된다. 동적 클래스는 인스턴스에 대한 조회가 요청될 때, 즉 인스턴스가 생성될 때 인스턴스의 정보가 WMI 프로바이더에 의해 채워지게 된다. 따라서 동적 클래스는 반드시 WMI 프로바이더가 명시돼야 한다.
동적 클래스의 인스턴스인 동적 인스턴스는 해당 WMI 프로바이더가 수행 중일 때만 그 정보가 표시된다. 앞서 동적 클래스의 인스턴스는 생성시에 WMI 프로바이더가 그 값을 채워준다고 했었다. 따라서 WMI 프로바이더가 수행중이 아니라면 동적 클래스의 인스턴스는 전혀 생성되지 않는다.
예를 들어, root\MSAPPS11:Win32_Word11Font 클래스는 MS 워드 2003에서 현재 사용 중인 폰트들의 정보를 나열한다. 물론 이 클래스도 dynamic 한정자와 provider 한정자가 명시되어 있다. 만약 워드가 수행 중이 아니라면 이 클래스의 인스턴스는 존재하지 않는 것처럼 나타난다. 워드를 수행한 후에 이 클래스의 인스턴스를 조회하면 폰트 정보들이 표시될 것이다. 동적 인스턴스는 CIM Repository에 저장되지 않는다. 클라이언트의 요구에 의해 on-demand로 생성되며 인스턴스가 수시로 변동하기 때문이다. 반면에 정적 인스턴스는 동적으로 변경이 이루어지지 않기 때문에 CIM Repository에 인스턴스 자체가 저장된다. 동적 인스턴스를 이해하는 것은 나중에 설명할 애플리케이션 Instrumentation에서 중요한 사항이므로 잘 이해하기 바란다.
WMI 관련 유틸리티 및 도구들 지금까지 거의 말로만 CIM 클래스니 프로퍼티니 한정자를 떠들어 왔다. 하지만 WMI 내에 구체적으로 어떤 클래스들이 있으며 그들의 인스턴스는 어떤 것인지 살펴볼 도구나 유틸리티들은 없는가? 반드시 프로그램을 작성해야 하는가? 여기서는 WMI에 관련된 몇 가지 유틸리티를 간단히 살펴보도록 하겠다.
WMI 테스터와 WMI 콘솔 WMI 테스터(wbemtest.exe)는 윈도우 XP 이상에 기본적으로 포함된 WMI 테스트 도구이다. UI는 그다지 간편하지 못하지만 WMI 네임스페이스, 클래스, 프로퍼티, 인스턴스를 모두 조회하거나 생성할 수 있는 기능을 가지고 있다. 클래스의 속성을 검사하거나 WQL 조회를 수행하거나, 이벤트 subscription을 수행할 수 있다(<화면>). WMI 콘솔은 커맨드 라인에서 WMI의 다양한 클래스 정보를 확인하고 인스턴스를 열거하는 데 사용할 수 있다. 인터페이스나 커맨드가 다소 생소하지만 커맨드 라인에서 작동하기 때문에 텔넷이나 기타 방법을 이용하여 원격 제어가 가능하다는 장점이 있다.
화면> | <화면1> WMI 테스터(wbemtest.exe)화면1> |
WMI 도구들 WMI 도구는 WMI SDK에 포함됐던 WMI 관련 유틸리티를 별도로 다운로드할 수 있도록 패키징한 유틸리티 모음이다. WMI 도구에는 CIM Studio, CIM Object Browser 등의 유틸리티가 포함되어 있다. 이중 CIM Studio는 CIM repository에 존재하는 클래스들을 브라우징하거나 클래스의 인스턴스를 조회하거나 추가/삭제하는 것이 가능하며 CIM 클래스의 정의를 변경할 수도 있다. 또한 클래스, 속성, 메쏘드의 한정자를 보거나 편집하는 것이 가능하며 특정 클래스와 그와 연관된 관계 클래스들이 어떤 것이 있는가를 그래픽으로 보여주기 때문에 매우 편리한 도구이다. 필자 역시 WMI에 관련된 작업을 할 때는 항상 CIM Studio로 작업하고 있다. CIM Object Browser는 CIM Studio와 비슷한 기능을 가지고 있다. CIM Studio가 클래스 관점에서 작동하는 유틸리티라면 CIM Object Browser는 인스턴스 관점에서 작동하는 유틸리티라고 보면 된다.
WMI SDK 레퍼런스 WMI를 잘 활용하기 위해서는 CIM 클래스(WMI 클래스)가 어떤 것들이 있으며 그것이 어떤 정보를 제공하는가를 잘 아는 것이 중요하다. 6000여 개의 클래스가 제공되지만 필요한 것을 찾지 못한다면 말짱 헛것이기 때문이다. 따라서 CIM Studio나 CIM Object Browser와 같은 도구를 통해 유용한 정보를 찾아보도록 하는 것이 중요하다. 이렇게 원하는 정보를 담는 WMI 클래스를 직접 찾아보는 것보다 일목요연하게 클래스들이 나열돼 있는 레퍼런스 역시 중요한 WMI의 도구라고 할 수 있다. 이런 의미에서 WIM 프로바이더별로 클래스가 구분되어 설명되어 있는 WMI SDK의 레퍼런스는 중요한 참고자료가 될 것이다.
| <화면> CIM Studio화면> |
닷넷 프레임워크에서 WMI 지금까지 WMI에 대해 살펴보았다. 실제로 WMI에 대한 기술은 내용이 상당히 방대한 편이므로 한정된 지면에서 이들을 모두 설명하기는 어렵다. 하지만 지금까지 설명한 내용을 이해한다면 이제부터 닷넷 프레임워크에서 WMI를 액세스하기 위한 코드 작성을 독자가 직접 해볼 수 있을 것이다. <리스트 2,="" >의 닷넷 코드에서 등장한 닷넷의 클래스들을 설명하도록 하겠다.
System.Management 네임스페이스 닷넷 환경에서 WMI에 접근하기 위해서는 System.Management 네임스페이스 하의 여러 클래스들을 사용하면 된다. 이들 클래스를 사용하기 위해서는 System.Management.dll 어셈블리를 참조해야만 한다. 앞서 WMI 인프라스트럭처에서 간단히 설명했듯이 WMI에 액세스하는 것은 CIMOM 서비스에 접근하는 것이며 이는 WMI에서 제공하는 COM API 혹은 Scripting API를 통해서이다.
System.Management 네임스페이스에서 제공하는 여러 클래스들은 Scripting API를 닷넷 클래스로서 보다 사용하기 쉽게 감싸놓은(wrapping) 클래스 집합을 제공한다. 여기에서 System.Management 네임스페이스의 클래스들을 모두 설명하기엔 무리가 있으므로 핵심적인 주요 클래스들만을 예제 위주로 간략히 살펴보도록 하겠다. 상세한 각 클래스의 속성, 메쏘드들에 대해서는 MSDN 라이브러리를 참고하기 바란다.
ManagementClass 클래스 ManagementClass 클래스는 WMI 클래스를 추상화하는 클래스이다. ManagementClass를 통해 WMI 클래스의 여러 정보를 검사하거나 WMI 클래스의 인스턴스를 생성할 수 있다. ManagementClass는 생성자를 통해 어떤 WMI 클래스를 추상화할 것인가를 결정할 수 있다. 다음 코드에서 ManagementClass는 Win32_Service WMI 클래스를 나타낸다. ManagementClass의 GetInstances 메쏘드는 해당 클래스의 모든 인스턴스를 반환한다. 이 예제는 이미 앞서 코드에서 예를 보였다. 리스트> ManagementClass cls = new ManagementClass("Win32_Service");
ManagementClass의 Properties, Methods, Qualifiers 속성은 WMI 클래스의 속성, 메쏘드, 한정자를 나타내는 데 사용된다. 따라서 이 속성들을 이용하여 클래스가 어떤 속성, 메쏘드, 한정자를 갖는지 알아낼 수 있다. Properties 속성은 PropertyDataCollection 컬렉션을 통해 PropertyData 목록을 반환하는데 PropertyData 클래스를 통해 WMI 프로퍼티의 이름, 데이터 타입, 프로퍼티의 한정자 역시 알아낼 수 있다. <리스트>는 Win32_Service WMI 클래스의 모든 프로퍼티에 대해 데이터 타입, 그리고 프로퍼티의 한정자를 표시하는 코드 조각이다.
리스트> | <리스트> WMI 클래스 프로퍼티 정보 나열리스트> |
| |
| string clsName = "Win32_Service"; ManagementClass cls = new ManagementClass(clsName);
Console.WriteLine("{0} class property information ---------------", clsName); foreach(PropertyData prop in cls.Properties) { // show property info. Console.WriteLine("{0} : CIM type = {1}, defined in {2}", prop.Name, prop.Type, prop.Origin); // show property qualifier foreach(QualifierData qualifier in prop.Qualifiers) Console.WriteLine("\t{0} : {1}", qualifier.Name, qualifier.Value); } |
| |
ManagementClass는 정적 WMI 클래스를 정의하는 데도 사용될 수 있다. 물론 앞서 언급한 MOF를 이용하는 것이 바람직하겠지만 코드에 의해 생성되는 클래스 정의에도 사용된다. <리스트>은 MOF와 MOF 컴파일러를 사용하지 않고 코드를 통해 WMI 클래스를 정의하는 예제를 보여주고 있다. MasoTestClass 클래스를 생성하기 위해 ManagementClass의 인스턴스를 생성하고 __CLASS 시스템 속성에 클래스 명을 설정한 후, 클래스 고유의 속성들을 추가한다. 그리고 이것을 CIM Repository에 저장하면 된다. <화면>는 CIM Studio를 통해 추가된 MasoTestClass 클래스를 확인하는 것을 보여주고 있다.
| <화면> CIM Studio를 통해 추가된 MasoTestClass 클래스화면> |
<리스트>에서 생성한 MasoTestClass는 정적 클래스이다. 즉 이 클래스는 dynamic 한정자와 provider 한정자를 명시하지 않았기 때문인데 동적 인스턴스를 생성하는 동적 클래스는 WMI 프로바이더를 구현해야만 작성이 가능하다. 하지만 닷넷 프레임워크는 이러한 한정자 추가와 WMI 프로바이더 작성 작업을 모두 프레임워크 내에서 작성해 준다. 어찌됐건 이렇게 생성된 클래스의 인스턴스를 만드는 것은 WMI 테스터나 CIM Studio에서 인스턴스 추가 기능을 통해 인스턴스를 추가할 수 있다. 추가되는 인스턴스는 정적 인스턴스이기 때문에 CIM Repository에 저장된다. 잠시 후에 닷넷 코드를 통해 정적 인스턴스를 생성하는 예제도 살펴보도록 하겠다.
리스트> | <리스트> 정적(static) WMI 클래스 정의리스트> |
| |
| // defining WMI class. string clsName = "MasoTestClass"; ManagementClass cls = new ManagementClass(); cls.Properties["__CLASS"].Value = clsName; // adding property cls.Properties.Add("MyProperty1", CimType.String, false); cls.Properties.Add("MyProeprty2", CimType.SInt32, true); // set key property (required for creating instance) cls.Properties["MyProperty1"].Qualifiers.Add("key", true); // save class definition cls.Put(); |
| |
ManagementBaseObject, ManagementObject 클래스 ManagementBaseObject 클래스와 ManagementObject 클래스는 System.Management 네임스페이스에서 가장 핵심적인 역할을 수행하는 클래스이다. ManagementBaseObject는 WMI의 클래스 인스턴스에 대응되는 닷넷 클래스이다. WMI 인스턴스를 5개 획득했다면 ManagementBaseObject 클래스의 인스턴스 5개에 대한 참조를 가지고 있다는 말과 같다. ManagementObject 클래스는 ManagementBaseObject에서 파생된 클래스이다. ManagementBaseObject 클래스는 new 연산자나 Activator.CreateInstance() 메쏘드를 통해 인스턴스를 생성할 수 없는 반면 ManagementObject 클래스는 new를 통해 인스턴스를 생성할 수 있다. 따라서 WMI 클래스를 통하지 않고 직접 하나의 WMI 인스턴스를 얻어내는 것도 가능하다. 다음 코드는 Win32_LogicalDisk WMI 클래스의 C: 드라이브 인스턴스 하나를 직접 얻어내는 코드이다.
// ManagementObject 클래스를 통해 WMI 클래스 인스턴스를 구한다. ManagementObject disk = new ManagementObject("Win32_LogicalDisk.deviceid='c:'"); disk.Get(); ulong total = ((ulong)(disk["size"])) / (1024*1024); ulong free = ((ulong)(disk["freespace"])) / (1024*1024); Console.WriteLine("{0} Total = {1:###,##0} MB Available = {2:###,##0} MB", disk["Caption"], total, free);
또 한 가지 두 클래스가 다른 점은 ManagementBaseObject는 수정된 사항을 다시 CIM Repository에 저장할 수 없는 반면 ManagementObject는 저장이 가능하다. <리스트>의 코드 뒤에 다음 코드를 수행하면 새로운 MasoTestClass의 정적 인스턴스가 생성된다. CIM Studio나 WMI 테스터를 통해 생성된 인스턴스를 확인해 보기 바란다. 리스트> // Now.. add instance ManagementObject obj = cls.CreateInstance(); // adding proeprty value obj.Properties["MyProperty1"].Value = "Test Value !"; // save instance obj.Put();
앞의 코드처럼 새로운 인스턴스를 생성하는 것 외에도 기존에 존재하는 인스턴스를 수정하는 것도 가능하다. 물론 이렇게 인스턴스를 수정하는 것이 가능하기 위해서는 WMI 클래스가 수정 기능을 지원(클래스의 SupportUpdate 한정자의 값이 true)해야만 하고 속성이 읽기 전용이 아니어야 할 것이다.
ManagementScope, ManagementPath 클래스 <리스트>의 소스코드는 MasoTestClass를 root/cimv2 네임스페이스에 생성해버린다. ManagementClass와ManagementObject의 생성자(constructor)는 명확하게 WMI 네임스페이스나 WMI 경로에 명시되지 않으면 로컬 컴퓨터의 root/cimv2 네임스페이스를 사용하기 때문이다. 원격 컴퓨터나 다른 네임스페이스를 사용하고자 한다면 추가적인 작업이 필요하다.
ManagementScope 클래스는 WMI 네임스페이스에 대응되는 닷넷 클래스이다. WMI의 보안 모델은 네임스페이스 별로 이루어지므로 ManagementScope 클래스는 네임스페이스에 연결하기 위한 접속에 관련된 프로퍼티와 메쏘드들을 제공한다. 예를 들어 원격 컴퓨터의 WMI에 연결하기 위해서는 원격 컴퓨터의 이름과 사용자 ID, 암호를 제공할 필요가 있다. 이를 위해 ManagementScope 클래스는 ConnectionOption 클래스와 더불어 이와 같이 접속에 필요한 컴퓨터, 네임스페이스, 보안 정보를 기술할 수 있도록 되어 있다.
ManagementPath 클래스는 WMI PATH에 대한 유틸리티성 클래스로서 주어진 문자열 WMI PATH에 대해 네임스페이스만을 분리해낸다든가, 주어진 PATH가 네임스페이스를 나타내는지, 클래스를 나타내는지 혹은 인스턴스를 나타내는지 알려주는 프로퍼티를 가지고 있다.
ManagementObject 클래스와 ManagementClass 클래스의 생성자는 모두 ManagementScope 객체와 ManagementPath 객체를 매개변수로 취하는 생성자를 가지고 있다. 따라서 지금까지 보아온 예제들처럼 단순히 네임스페이스, 경로를 문자열로 줄 수도 있지만 보안 정보가 필요한 경우에는 ManagementScope 객체와 ManagementPath 객체를 모두 사용해야 한다. <리스트>은 원격 컴퓨터에 설치된 핫픽스를 열거하는 예제 코드로서 ManagementScope 객체를 사용하는 예를 보여주고 있다.
| <리스트> 원격 컴퓨터의 핫픽스를 열거하는 코드리스트> |
| |
| ConnectionOptions option = new ConnectionOptions(); option.Username = "yourid"; option.Password = "yourpassword";
ManagementScope scope = new ManagementScope(@"\\svr1\root\cimv2", option); scope.Connect();
ManagementPath path = new ManagementPath("Win32_QuickFixEngineering"); ManagementClass cls = new ManagementClass(scope, path, null); ManagementObjectCollection col = cls.GetInstances();
foreach(ManagementObject obj in col) { string desc = (string)obj["Description"]; if (desc != "") Console.WriteLine(desc); } |
| |
<리스트>의 코드가 root/cimv2 네임스페이스에 MasoTestClass를 생성하므로 다른 네임스페이스에 클래스를 생성하고자 한다면 ManagementClass의 인스턴스를 생성하는 코드를 다음과 같이 수정하면 된다. 리스트> ManagementClass cls = new ManagementClass(new ManagementScope("\\.\root\otherNamesapce"), null);
WqlObjectQuery, ManagementObjectSearcher 클래스 System.Managment 네임스페이스는 CIM Repository에 대한 검색 기능을 지원한다. 물론 이 검색이란 것이 WQL에 의한 것임은 말할 필요도 없다. WQL 쿼리를 기술하기 위해서는 WqlObjectQuery 클래스를 사용하면 된다. WqlObjectQuery 클래스는 WQL을 문자열로 기술하는 매우 간단한 클래스이지만 이 클래스에서 파생된 몇몇 클래스는 WQL 구문 자체를 모르더라도 손쉽게 WQL을 생성할 수 있도록 해준다. 이들 클래스에 대한 상세한 내용은 MSDN 라이브러리를 참고하기 바란다.
WqlObjectQuery 클래스를 통해 WQL을 기술했다면 이 WQL을 이용해 실제 검색을 수행하는 클래스는 ManagementObjectSearcher 클래스이다. 이 클래스는 ManagementScope 객체와 WqlObjectQuery 객체를 매개변수로 취하는 생성자를 통해 조건 검색을 수행할 수 있다. ManagementObjectSearcher 클래스의 인스턴스를 생성한 후에는 Get 메쏘드를 호출하여 조회 결과로서 ManagementObject 혹은 ManagementClass 컬렉션을 얻을 수 있다. 다음 코드는 MSDN 라이브러의 예제 코드로서 시스템에 공유된 폴더에 대한 정보를 나열하는 Win32_Share WMI 클래스의 모든 인스턴스를 반환한다.
WqlObjectQuery objectQuery = new WqlObjectQuery("select * from Win32_Share"); ManagementObjectSearcher searcher = new ManagementObjectSearcher(objectQuery);
foreach (ManagementObject share in searcher.Get()) { Console.WriteLine("Share = " + share["Name"]); }
MangementEventWatcher 클래스 ManagementEventWatcher 클래스는 WMI의 이벤트를 위한 클래스이다. 이 클래스는 이벤트를 조회하는 WqlObjectQuery 객체를 매개변수로 인스턴스가 생성된다. 이 클래스는 이벤트의 수신을 시작/중단하는 Start()/Stop() 메쏘드를 가지고 있으며 다음 이벤트 발생까지 기다리는 WaitForNextEvent() 메쏘드도 가지고 있다. 더욱 편리한 것은 WMI 이벤트가 발생되면 이벤트를 처리하는 핸들러를 추가할 수 있다. ManagementEventWatcher 클래스의 EventArrived 이벤트 속성에 delegate를 추가함으로써 이벤트가 발생할 때마다 특정한 작업을 수행할 수도 있다. 이 클래스에 대한 예제는 MSDN 라이브러리에서 충분히 등장하므로 여기에선 생략하겠다.
WMI를 이용한 닷넷 Instrumentation WMI는 관리(Managment)와 계측(Instrumentation) 서비스를 제공하는 윈도우 인프라 시스템 중 하나이다. 지금까지 필자가 살펴본 내용을 음미해 보면 대부분 하드웨어, 운영체제, 소프트웨어 ‘관리’ 측면에서 WMI의 사용법이 위주였다고 할 수 있다. 닷넷의 System.Management 네임스페이스와 관련 클래스들도 모두 이러한 ‘관리’를 위한 클래스들을 위주로 설명했다. 하지만 필자가 정말 이 글을 통해 하고 싶은 말은 그러한 관리의 대상이 되는 정보들을 WMI를 통해 어떻게 제공하는가이다. 이 이야기가 하고 싶어서 지금까지 WMI 내부 구조와 System.Management 네임스페이스를 설명한 것이다. 그렇다면 WMI를 어떻게 계측에 사용할 수 있는가를 살펴보도록 하자.
Instrumentation ‘Instrumentation’이란 용어를 영한사전에서 찾아보면 악기의 ‘연주’ 정도로 해석된다. ‘연주’라는 뜻으로는 도저히 ‘instrumentation’이라는 용어가 갖는 뉘앙스와 연결되지 않는다. 다시 IT 용어 사전을 찾아보았다. 대개 ‘instrumentation’ 앞에 명사가 붙어서 ‘~ 계측’ 정도로 해석이 되어 있다. 흡족하지는 않지만 ‘연주’ 보다는 ‘계측’이라는 단어가 좀 더 명확한 듯하다. 앞으로 설명할 ‘계측’이란 소프트웨어(시스템, 애플리케이션 등)의 작동 상황을 관찰하고 측정하는 행위를 말하는 것이다. 예를 들어, 웹 서버가 현재 다운되지 않고 작동 중이며 IIS가 1분에 몇 개의HTTP 요청을 처리하고 있는지 관찰하고 측정한다면 이것이 웹 서버에 대한 계측(instrumentation)이 될 것이다.
일반적으로 Instrumentation을 수행하는 방법과 도구는 다양하다. 이벤트 로그를 통해 계측 대상이 생성하는 로그 메시지를 관찰하거나, Win32 퍼포먼스 카운터를 사용해 시스템/애플리케이션의 성능을 모니터링해 볼 수도 있다. 이렇게 일반적인 도구와 방법을 사용하거나, 애플리케이션에서 printf 문이나 OutputDebugMenssage API, 혹은 닷넷의 Trace.WriteLine 메쏘드를 통해 로그를 파일에 기록하는 프로그래밍적인 방법도 생각해 볼 수 있다. WMI는 표준화되고 단일화된 인터페이스를 통해 Instrumentation을 보다 쉽게 해주는 계측의 강력한 도구로서 사용될 수 있다.
계측의 핵심은 운영 중인 혹은 작동 중인 시스템, 애플리케이션에 대한 정보를 제공하는 것에 있다. 개발시나 테스트시에는 디버깅이나 기타 테스트 도구를 이용해 이러한 계측을 수행할 수 있다. Instrumentation은 애플리케이션/시스템이 설계되고 구현될 때부터 고려해야 하는 사항이다. 시스템의 현재 상태와 작동 현황에 대한 정보를 제공하도록 설계/구현된 시스템은 추후 유지보수가 편리하며 시스템에서 발생하는 문제를 즉각 파악할 수 있도록 해준다.
System.Management.dll 어셈블리의 System.Management.Intrumentation 네임스페이스는 WMI를 통해 계측을 수행할 수 있도록 다양한 기능을 제공한다. 닷넷 클래스를 WMI 클래스로 변환해 주어 닷넷 클래스의 인스턴스가 WMI 인스턴스로 맵핑될 수 있도록 WMI 프로바이더를 제공해주며 닷넷 애플리케이션에서 WMI 이벤트를 발생할 수 있도록 해준다. 이와 같은 기능에 복잡한 코드는 필요하지 않으며 단순히 특성(attribute)를 클래스에 추가함으로 손쉽게 Instrumentation이 가능하게 해준다는 데 더 큰 매력이 있다. 심지어는 WMI에 대해 전혀 모르더라도 말이다.
관리 데이터 제공 System.Management.Instrumentation 네임스페이스의 InstrumentationClass 특성은 닷넷 클래스를 WMI 클래스로 맵핑해 준다. <리스트>과 같이 단순한 정적 클래스가 아닌, 동적으로 인스턴스를 생성/조회하는 동적 클래스로 말이다. 더욱 훌륭한 것은 닷넷 클래스의 클래스 상속 구조를 모두 WMI 상속 구조로 변환해 준다는 것이다. 단지 아쉬운 점은 닷넷의 특성(attribute)를 WMI의 한정자(qualifier)로 변환시켜 주지는 못한다. 하지만 WMI 클래스, 한정자, 동적 클래스 등 사항을 잘 모르더라도 WMI를 통해 instrumentation 정보를 관리 프로그램에게 제공할 수 있다는 것은 큰 매력이다.
닷넷 클래스를 WMI 클래스로 정의하고자 한다면 클래스에 InstrumentationClass 특성을 추가한다. 이때 InstrumentationType을 인스턴스로 설정하면 WMI 인스턴스로서 정보가 제공된다. InstrumentationClass 특성이 추가되면 클래스의 모든 public 속성과 필드가 WMI 속성으로 맵핑되며 닷넷 클래스의 public 메쏘드는 WMI 메쏘드로 맵핑된다. 이 맵핑에서 일부 지원하지 않는 데이터 타입과 객체에 대한 참조들이 존재하는데 대부분의 경우 문자열이나 숫자 혹은 문자열의 배열, 숫자의 배열 정도의 타입을 사용하므로 큰 문제는 없다. 리스트> [InstrumentationClass(InstrumentationType.Instance)] public class MyClass { public string MyData1; public int MyData2; }
이렇게 클래스를 정의한 후 일반 클래스의 인스턴스를 만들듯 MyClass의 인스턴스를 만들면 된다. 그리고 이 MyClass의 인스턴스가 WMI를 통해 액세스되도록 한다면 Instrumentation 클래스의 정적 메쏘드인 Publish를 호출하면 된다.
MyClass obj = new MyClass(); obj.MyData1 = "My Data Test"; obj.MyData2 = 99; Instrumentation.Publish(obj);
Publish 메쏘드를 통해 퍼블리시된 객체들은 MyClass WMI 클래스의 인스턴스 조회에서 모두 나타나게 된다. 한 가지 주의할 사항은 MyClass 클래스와 그 인스턴스가 dynamic 인스턴스이기 때문에 이 코드를 담는 프로세스가 시작되지 않았다면 인스턴스는 조회되지 않을 것이다. 동적 인스턴스이기 때문에 얻을 수 있는 장점은 인스턴스의 데이터가 변경되면 그 변화사항은 즉시 반영된다. 여기서 말하는 ‘즉시’라는 말은 WMI 클라이언트가 인스턴스를 조회할 때마다 인스턴스의 값을 읽어간다는 것이다. 다시 말해 WMI 인스턴스는 하나 존재하지만 해당 인스턴스가 제공하는 데이터(속성)는 매번 변할 수 있다는 것이다.
이러한 점을 통해 애플리케이션의 현재 작동 상태를 측정할 수 있다. 예를 들어, 소켓을 이용한 네트워크 서버에 현재 연결되어 있는 클라이언트가 몇 개나 되며 그들의 IP 정보를 WMI를 통해 제공할 수 있다. 간단한 모니터링 프로그램을 작성하거나 WMI 유틸리티 혹은 고급 모니터링 도구(Microsoft Operation Monitor)를 통해 이러한 정보를 계측할 수 있다. 다음 코드 조각은 구체적인 예를 보여주고 있다. 소켓 클라이언트가 접속하면 클라이언트의 정보(IP, 사용자 ID 등)를 WMI 인스턴스로 등록하고 클라이언트의 접속이 종료되면 등록된 WMI 인스턴스를 제거(Instrumentation.Revoke 메쏘드)한다. 따라서 WMI ClientInfo 클래스의 인스턴스를 조회하는 WQL을 작성하면 현재 접속되어 있는 클라이언트의 다양한 정보 등을 얻을 수 있으며 WMI의 인스턴스 생성/삭제 이벤트에 가입(subscription)하면 클라이언트의 접속 이벤트를 얻을 수도 있다.
// WMI 클래스 정의 [InstrumentationClass(InstrumentationType.Instance)] public class ClientInfo { public string IP; public string UserID; public int WorkerThreadID; }
// 클라이언트마다 Worker 클래스가 하나씩 만들어져 처리한다고 가정하자. public class Worker { private ClientInfo info;
// 클라이언트 접속 시 코드--------------------------------------- public void OnAccept(Socket clientSocket) { ClientInfo info = new ClientInfo(); info.IP = clientSocket.RemoteEndPoint.ToString(); info.WorkerThreadID = AppDomain.GetCurrentThreadId(); // 소켓에서 사용자 정보를 읽는다고 가정.. info.UserID = GetUserInfoFromFirstPacket(); // WMI에 인스턴스 퍼블리시 Instrumentation.Publish(info); // 기타 다른 작업 수행… (생략) }
// 중략……
// 클라이언트 접속 종료 시 WMI에서 인스턴스 제거 --------------------- public void OnClose() { Instrumentation.Revoke(info); } }
만약 닷넷을 사용하지 않고 WMI를 통해 동적 데이터를 제공하고자 한다면 많은 작업이 필요하다. MOF를 이용하여 클래스를 정의하고, WMI 프로바이더를 ATL과 C++를 이용해 작성해야 한다. 하지만 닷넷의 System.Management.Instrumentation 네임스페이스의 클래스들은 이러한 작업을 매우 간단한 선언적(declarative) 방법으로서 가능하게 해준다.
관리 이벤트 제공 WMI 이벤트를 발생하는 것 역시 매우 쉽다. 닷넷 클래스를 WMI 커스텀 이벤트 클래스로 맵핑하는 작업도 마찬가지로 닷넷 클래스에 InstrumentationClass 특성을 추가하는 것이다. 이번에는 InstrumentationType을 이벤트로 설정하기만 하면 된다. 이벤트의 발생 또한 매우 쉽다. 닷넷 클래스의 인스턴스를 생성하고 퍼블리시와 비슷하게 Instrumentation의 Fire 메쏘드를 호출하면 WMI 이벤트가 발생한다.
[InstrumentationClass(InstrumentationType.Event)] public class MyEvent { public string EventInfo; }
MyEvent evt = new MyEvent(); evt.EventInfo = "Your event information"; Instrumentation.Fire(evt);
WMI 설치 예상할 수 있듯이 닷넷 클래스가 WMI 클래스로서 사용되기 위해서는 설치 과정이 필요하다. 프로그램적인 코딩이 매우 간단하듯이 설치 역시 간단히 해결할 수 있다. 닷넷 Instrumentation의 설치는 항상 어셈블리 단위로 이뤄진다. 따라서 해당 어셈블리가 Instrumentation에 사용되는지 여부를 표시해야 한다. 이를 위해 Instrumented 특성이 제공되며 이 특성에는 어셈블리 내에 정의된 WMI 클래스가 어떤 WMI 네임스페이스에 저장될 것인지도 명시할 수 있다. 다음 계측된 특성은 어셈블리가 WMI 클래스(이벤트 클래스 포함) 정의를 포함하고 있으며 이 클래스는 root/MyTest 네임스페이스에 저장될 것을 명시하고 있다. 닷넷 프레임워크는 root\MyTest 네임스페이스의 존재 유무를 검사하여 존재하지 않는다면 새로운 네임스페이스를 만든다.
[assembly:Instrumented("root/MyTest")]
닷넷 Instrumentation을 사용하기 위한 최소의 조건은 이 정도이다. 어셈블리에 Instrumented 특성이 명시되어 있고, InstrumentationClass 특성이 명시된 클래스가 존재한다면, Instrumentation.Publish, Instrumentation.Fire 메쏘드는 런타임에 MOF 파일을 생성하고 컴파일해 WMI 클래스 정의를 CIM Repository에 만든다. 한 가지 주의할 사항은 닷넷 프레임워크가 제공하는 WMI 자동 설치에 의존해서는 안 된다는 것이다. 자동 설치 기능은 개발 시 편의를 제공하기 위함일 뿐이다. 만약 실제 배포를 자동 설치에 의존한다면 최초에 애플리케이션이 수행되고 Instrumentation 클래스의 Publish/Revoke/Fire 메쏘드를 호출할 때까지는 WMI 스키마가 전혀 생성되지 않기 때문이다.
따라서 프로그램 수행시가 아닌 프로그램 설치시에 WMI 스키마가 생성되도록 하는 것이 좋다. 이러한 설치를 지원하기 위해 System.Management.Instrumentation 네임스페이스에는 ManagementInstaller 클래스가 제공되며 이 클래스는 어셈블리가 정의하는 WMI 스키마를 생성해 준다. 이 클래스를 여러분의 Install 코드에 사용할 수도 있으며 다른 설치 작업이 없다면 DefaultManagementProjectInstaller 클래스를 상속받아 다음과 같이 두 줄의 코드를 추가함으로써 해결할 수 있다. 이렇게 생성된 어셈블리(DLL 혹은 EXE)를 InstallUtil.exe를 통해 설치하면 WMI 스키마가 생성된다.
[System.ComponentModel.RunInstaller(true)] public class MyInstaller : DefaultManagementProjectInstaller {}
실제로 작동하는 코드 예제는 지면 관계상 ‘이달의 디스켓’을 참고하거나 MSDN 온라인에서 예제를 쉽게 찾을 수 있으므로 이를 참고하기 바란다.
Instrumentation 응용 Instrumentation은 애플리케이션의 작동 여부를 감시하는 중요한 수단이다. 물론 Instrumentation을 사용하지 않고 성능 카운터(performance counter)나 추적 로그(trace log)를 통해 애플리케이션의 정상 작동 여부를 판단하는 방법도 있다. 하지만 성능 카운터는 특정 숫자 값에 의존하는 Instrumentation의 한 방법일 뿐이며, 추적 로그는 발생되는 로그를 다시 재가공해야 하는 경우가 많다. 반면 WMI를 이용한 Instrumentation은 다양한 정보를 표준화된 인터페이스를 통해 애플리케이션의 현재 상태를 알아보는 방법을 제공한다.
[1] 현재 수행중인 서버에 접속한 사용자는 누구인가? [2] 현재 서버에서 작동중인 비즈니스 로직 컴포넌트는 모두 몇 개인가? [3] 각 컴포넌트의 평균 수명 주기는 어떠한가? [4] 특정 사용자에 대해 로그 온/오프 이벤트를 받을 수 있는가?
이러한 질문에 대한 대답은 WMI와 System.Management.dll가 가지고 있다. 마지막으로 Instrumentation은 반드시 애플리케이션 설계와 개발 단계에서 고려돼야 하며 개발시에 반드시 애플리케이션의 작동 상황을 모니터링할 수 있는 방법(WMI와 같은)이 제공돼야 함을 강조하는 바이다. |