Hey, Scripting Guy!WinRM의 귀환

The Microsoft Scripting Guys

Scripting Guy가 WinRM(Windows® Remote Management)에 대한 2부작 연재물을 구상하고 있을 때 한 가지 중요한 문제가 머리를 스치고 지나갔습니다. 바로 보안이었지요. Scripting Guy는 해리포터 시리즈의 완결편이 출시되는 과정에서, 예약 주문된 책이 실수로 공식 출간 날짜보다 먼저 배송돼 버린 문제를 익히 잘 알고 있습니다. 이게 문제가 되었을까요? 물론 아닙니다. 출판사에서는 사람들에게 출시 날짜 전까지는 책을 읽지 말아 달라고 요청했을 뿐입니다.

이건 시작에 불과했습니다. 책이 출간되기도 전에 책 내용은 물론 완결편의 결말이 누출되어 버린 겁니다. (책을 아직 읽지 않으셨다면 제가 내용을 알려 드리죠. 사실 해리포터의 정체는 무슨 마법사인지 뭔지 랍니다!) 이와 마찬가지로 책의 모든 내용을 스캔한 복사본이 곧바로 인터넷에 공개되기도 했고, 어떤 경우에는 저자인 J. K. Rowling이 책을 써내기도 전에 그 내용이 유포되곤 했습니다. 이런 모든 현상은 결국 보안의 누수로 인한 것이며 Scripting Guy는 이런 일이 일어나지 않도록 해야겠다고 결심했습니다 우리 Scripting Guy가 쓴 WinRM 2부작의 결말을 알아내기란 해리포터 시리즈의 결말을 유포하기로 작정하고 그 내용을 알아내는 것만큼이나 어려운 일이랍니다.

다행히 Scripting Guy는 비밀을 지킬 수 있었습니다. 사실 그 이유는 기사 마감 시한이 훨씬 지난 뒤에도 이 시리즈의 두 번째 원고를 작성하지 않았기 때문이었습니다. 하지만 이 칼럼을 쓰는 Scripting Guy는 8월에 휴가를 보내고 있었고 여러 Microsoft 직원들과 달리 그는 휴가 동안 컴퓨터를 쳐다보지도 않았습니다.

사실 휴가가 아닐 때도 컴퓨터를 잘 들여다보지 않지만 그건 별개의 문제에요.

하여간 저희는 독자 여러분이 지난 4주 동안 대하 소설 WinRM이 어떻게 끝날지 걱정하고 또 궁금해 하면서 밤잠을 설치셨다는 걸 잘 압니다. 다행히 이런 지루한 기다림에 종지부를 찍을 수 있게 되었습니다. 이제 Scripting Guy가 기획한 WinRM 2부작의 흥미로운 결말을 최초로 공개합니다. 사실 그 내용은 그림 1에 나와 있습니다.

Figure 1 결과

strComputer = "atl-fs-01.fabrikam.com"

Set objWRM = CreateObject("WSMan.Automation") Set objSession = objWRM.CreateSession("http://" & strComputer)

strDialect = "https://schemas.microsoft.com/wbem/wsman/1/WQL" strResource = "https://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/*" strFilter = "Select Name, DisplayName From Win32_Service Where State = 'Running'"

Set objResponse = objSession.Enumerate(strResource, strFilter, strDialect)

Do Until objResponse.AtEndOfStream strXML = objResponse.ReadItem

    Set objXMLDoc = CreateObject("Microsoft.XMLDom") objXMLDoc.async=False objXMLDoc.loadXML(strXML)

    Set objElement = objXMLDoc.documentElement Wscript.Echo "Name: " & objElement.ChildNodes(0).ChildNodes(0).NodeValue Wscript.Echo "Display Name: " &  objElement.ChildNodes(1).ChildNodes(0).NodeValue Wscript.Echo Loop

예, 압니다. 얽히고 설킨 줄거리가 등골을 오싹하게 하죠. objElement.ChildNodes(0).ChildNodes(0).NodeValue! 이런 걸 본 사람이 있기나 하겠습니까? 물론, 철저한 보안 유지를 위해 Scripting Guy조차도 이 시리즈의 결말을 몰랐습니다. 하지만 이제 그 비밀을 공개합니다.

더 진행하기에 앞서 1부(technetmagazine.com/issues/2007/11/HeyScriptingGuy)를 읽지 못한 독자를 위해 시리즈 전체 내용을 간략하게 짚어 보겠습니다. 1부에서는 Windows Server® 2003 R2, Windows Vista® 및 Windows Server 2008에서 새롭게 선보인 기술인 WinRM을 소개했습니다. 이 기능은 방화벽까지 통과할 수 있으며 인터넷상에서 컴퓨터를 쉽게 관리할 수 있습니다. 사실 WMI(Windows Management Instrumentation)에도 컴퓨터를 원격으로 관리하는 기능이 있었습니다. 하지만 WMI는 DCOM(분산 COM)을 원격 관리 기술로 이용합니다. 대부분의 방화벽에서 DCOM 트래픽을 차단한다는 점만 제외한다면 이 기술도 훌륭합니다. 물론 적절한 포트를 열어서 DCOM 트래픽을 허용할 수도 있지만 대부분의 네트워크 관리자는 이런 방법을 꺼리는 경우가 많습니다. DCOM을 위해 문을 열었다가 모든 종류의 악의적인 공격이 들어올 수 있는 문도 함께 열게 될까 우려하기 때문입니다.

따라서 WinRM은 "서로 다른 업체에서 제공한 운영 체제와 하드웨어의 상호 운용을 가능케 하는 방화벽 친화적인 표준 SOAP 기반 프로토콜인 WS 관리 프로토콜을 Microsoft식으로 구현한 것"입니다. 쉽게 말해 이제 HTTP 및 HTTPS와 같은 표준 인터넷 프로토콜을 사용하여 원격 관리를 수행할 수 있다는 의미입니다.

지난달에 언급했듯이, WinRM을 사용하면 원격 컴퓨터에서 손쉽게 WMI 정보에 연결하고 이를 검색할 수 있습니다. 그렇다면 이 기술이 절대적으로 완벽한 기술이라는 의미일까요? 꼭 그렇지만은 않습니다. 이미 지적했듯이, WinRM에서 데이터를 호출 스크립트로 다시 전송하면 데이터는 다시 XML 형식으로 반환됩니다. 말할 필요도 없이 XML은 시스템 관리자가 이에 대한 충분한 지식이 없다면 구문 분석하고 이용하기가 조금 까다롭습니다. 이런 점 때문에 WinRM에는 반환된 데이터를 더 읽기 쉬운 형식으로 변환하는 XSL 변환이 함께 제공됩니다.

이 기능은 출력 내용이 항상 다음과 같은 형식으로 나온다는 점을 제외하면 매우 훌륭합니다.

Win32_Service AcceptPause = false AcceptStop = true Caption = User Profile Service CheckPoint = 0 CreationClassName = Win32_Service

이것도 나쁘지는 않지만 결과가 항상 명령 창에 출력된다는 것이 문제입니다. 기본적으로 데이터를 텍스트 파일로 저장하거나 데이터베이스 또는 Microsoft® Excel® 스프레드시트에 기록할 수 없으며, 정보를 화면에 표시하는 것 이외의 다른 방식으로는 이용할 수 없습니다. 이건 그다지 좋은 점이라 할 수 없겠죠.

무엇보다 WMI 클래스의 속성을 선택한 수만큼만 반환하려는 경우 문제가 더 악화됩니다. 이것은 보통 네트워크 트래픽을 줄이기 위한 것입니다. 클래스의 모든 속성이 아닌 일부 속성만으로 작업할 경우 다음과 유사한 출력 내용이 표시됩니다.

XmlFragment DisplayName = Windows Event Log Name = EventLog

XmlFragment DisplayName = COM+ Event System Name = EventSystem

흥미롭지만 지금까지 보아온 것과는 달리 정보가 표시되는 모양이 미관상 그리 훌륭하진 않네요. 보고서 전체에 나타나는 머리글 XmlFragment가 특히 그렇습니다.

그러면 이를 어떻게 해결해야 할까요? XML 데이터를 구문 분석하고 서식을 지정하는 사용자 지정 코드를 작성하면 될까요? 그건 시스템 관리 스크립트 작성자가 그다지 반가워하지 않을 것 같습니다. 아니, 반가워 할까요?

어떤 일을 제대로 하려면 그 일을 직접 할 것

WinRM에서 반환된 원시 XML 데이터를 다루는 것은 보기보다 까다롭습니다. 이번 달에는 XML 데이터를 구문 분석하고 서식을 지정하는 간단한 방법 한 가지를 소개하려고 합니다. 여기서 사용하는 방법이 XML을 다루는 유일한 방법은 결코 아니지만 뭐, 상관 없습니다. 우리의 주된 목표는 XSLT 변환에 의존할 필요가 없다는 점을 보여 드리기 위한 것이니까요. 기본 개념만 파악하면 WinRM 데이터로 무궁무진한 작업을 할 수 있게 될 것입니다.

한 가지 유의할 점이 있습니다. 이 칼럼에서는 한정된 지면 때문에 그림 1에 소개된 스크립트의 대부분은 설명하지 않고 넘어갑니다. 하지만 스크립트 앞부분의 2/3 정도는 지난달 칼럼에서 자세히 설명했으므로 크게 문제되지 않을 것입니다. 여기서는 반환된 데이터로 실제 작업을 하는 부분인 스크립트의 마지막 1/3 부분을 집중적으로 설명하겠습니다.

잘 아시겠지만 여기서부터 설명하는 내용은 WSMan.Automation 개체의 인스턴스를 만들고, 원격 컴퓨터(이 경우 atl-fs-01.fabrikam.com)를 쿼리하고, 해당 컴퓨터에서 실행되고 있는 모든 서비스에 대한 정보를 수신한 후의 단계입니다. 반환된 XML 데이터를 읽고 처리하기 위해 Do Until 루프를 설정한 스크립트 줄부터 시작하겠습니다. 이 루프는 모든 데이터를 읽고 처리할 때까지 -또는 주제와 관련하여 얘기하자면, XML 파일의 AtEndOfStream 속성이 True일 때까지- 계속됩니다.

다음 코드 줄이 바로 그 부분이자 이번 달 칼럼에서 이야기할 부분입니다.

Do Until objResponse.AtEndOfStream

루프 안에서 가장 먼저 해야 할 일은 반환된 XML 데이터의 첫 번째 섹션을 ReadItem 메서드를 사용하여 읽는 것입니다. 지금은 WinRM으로 작업하면서 서비스 정보를 검색하고 있으므로 이 첫 번째 섹션은 컬렉션의 첫 번째 서비스에 대해 반환된 모든 데이터로 구성됩니다. XML 형식의 데이터를 strXML이라는 변수에 저장한 다음 Microsoft.XMLDom 개체의 인스턴스를 만듭니다. 그러면 실제로 작업을 할 수 있는 빈 XML 문서가 생성됩니다.

Set objXMLDoc = _ CreateObject("Microsoft.XMLDom")

빈 문서가 준비되면 Async 속성의 값을 False로 설정한 다음 loadXML 메서드를 호출합니다.

그러면 이런 의문이 들 수 있습니다. 왜 Async 속성의 값을 False로 설정할까요? 그리고 왜 loadXML 메서드를 호출할까요? 좋은 질문이지만 직접 생각해 보는 것이 좋습니다.

초보자를 위해 설명하자면 Async 속성은 스크립트가 XML 정보의 비동기 다운로드를 허용할 것인지 여부를 나타냅니다. 이 속성이 True이면 다운로드가 시작되고 다운로드가 완료되지 않았더라도 제어가 즉시 스크립트로 반환됩니다. 꽤 괜찮은 처리 방법인 것처럼 보일 수 있습니다. 하지만 이 경우 해당 스크립트는 필요한 모든 정보를 얻은 것처럼 다음 작업을 진행하게 됩니다. 이때 필요한 모든 정보를 얻지 못한 경우에는 문제가 발생합니다. 따라서 Async 속성을 False로 설정하는 것입니다.

참고로 말씀 드리자면, 물론 다운로드의 상태를 주기적으로 모니터하는 코드를 작성하여 스크립트가 너무 속성으로 진행되지 않도록 할 수도 있습니다. 이런 방법도 있지만 Async 속성을 False로 설정하는 편이 훨씬 더 쉽습니다. 이렇게 하면 스크립트는 다운로드가 완료될 때까지 중지됩니다. 즉, 다운로드가 시작되면 스크립트는 다운로드가 끝날 때까지 기다린 다음 다른 작업을 하게 됩니다.

loadXML 메서드는 그 이름에서 추측할 수 있듯이 잘 구성된(Well-Formed) XML 문서(또는 문서 부분)를 앞에서 만든 빈 문서로 로드합니다. loadXML 메서드의 유일한 매개 변수에 strXML 변수를 전달하고 메서드를 호출하기만 하면 됩니다.

objXMLDoc.loadXML(strXML)

결과는 어떻게 될까요? 이제 WinRM 데이터를 가상 XML 문서로 변환했습니다. 이는 표준 XML 메서드를 사용하여 해당 가상 문서의 구문 분석을 할 수 있음을 의미합니다.

다음으로는 XML 파일의 루트 요소에 대한 개체 참조를 만들기 위해 다음 코드를 사용해야 합니다.

Set objElement = objXMLDoc.documentElement

이 지점에서 재미있는 작업을 할 수 있습니다. 물론 재미라는 개념에 XML 파일의 구문 분석이 포함되는 사람의 경우입니다. 당연히 Scripting Guy는 여기에 포함됩니다.

다시 기억을 더듬어 보면, WQL(WMI Query Language) 쿼리(또는 WinRM 용어로 '필터')는 다음과 같습니다.

strFilter = "Select Name, DisplayName " & _ "From Win32_Service Where State = 'Running'"

여기서 볼 수 있듯이 Win32_Service 클래스에서 Name 및 DisplayName 이 두 가지 속성을 요청했습니다. (반환되는 데이터를 실행 중인 서비스로 제한하는 Where 절도 넣었습니다. 하지만 여기서는 문제될 부분이 없습니다.) 두 개의 속성을 요청했다는 점이 중요할까요? 이 요청의 순서가 중요할까요? 그럴 수 있습니다. 이에 대해 알아두는 것이 좋습니다.

이 기사를 작성하는 우리는 이 질문에 대한 답을 알고 있어야 합니다. 우리는 이 두 질문에 대한 답이 '예'라는 것을 알고 있습니다. WQL 쿼리에서 두 개의 속성을 요청했다는 사실이 중요한 걸까요? 예, 중요합니다. 클래스의 모든 속성에 대한 값을 반환하는 Select * From 쿼리와 반대로, 이 두 가지 속성 값만 반환되기 때문이지요.

그러면 이 두 가지 속성의 순서도 중요할까요? 물론입니다. 속성 이름을 지정하는 순서는 속성 값이 반환되는 순서와 같습니다. 각 속성 값은 루트 요소의 자식 노드(또는 섹션)로 반환되므로 이는 중요한 사항입니다. 어떤 속성 값이 첫 번째 자식 노드로 반환될까요? 쉬운 질문입니다. 이 경우 첫 번째 자식 노드는 Name 속성입니다. WQL 쿼리에 이 속성을 처음 나열했기 때문입니다. 그럼 두 번째 자식 노드로 반환되는 속성은 무엇일까요? 그렇습니다. 쿼리에서 두 번째 항목으로 나열한 DisplayName 속성입니다.

잠깐만요. 이 문제를 직접 풀었습니까 아니면 누군가 이 칼럼의 사본을 미리 빼돌려서 여러분에게 주었습니까? 음, 의심스럽군요.

어쨌든 Name 속성의 값은 쉽게 출력할 수 있습니다. 다음과 같이 첫 번째 ChildNodes(항목 0) 컬렉션에서 첫 번째 항목(항목 0)의 NodeValue를 참조하기만 하면 됩니다.

Wscript.Echo "Name: " & _ objElement.ChildNodes(0).ChildNodes(0).NodeValue

그러면 DisplayName 속성의 값은 어떻게 참조할까요? 이 경우에는 두 번째 ChildNodes(항목 1) 컬렉션에서 첫 번째 항목의 NodeValue를 참조합니다.

Wscript.Echo "Display Name: " & _ objElement.ChildNodes(1).ChildNodes(0).NodeValue 

이 WQL 쿼리에 세 번째 속성(예를 들어 Status)이 있다면 어떻게 될까요? 이 경우에는 세 번째 ChildNodes(항목 2) 컬렉션에서 첫 번째 항목의 NodeValue를 참조하기만 하면 됩니다.

Wscript.Echo "Status: " & _ objElement.ChildNodes(2).ChildNodes(0).NodeValue 

이렇게 마지막 속성까지 진행합니다.

이제 스크립트 출력은 다음과 같이 나타납니다.

Display Name: Windows Event Log Name: EventLog

Display Name: COM+ Event System Name: EventSystem

혼란스러운 XmlFragment 머리글을 제거하기는 했지만, 사실 기본 WinRM 출력과 크게 달라 보이지는 않습니다. 차이점이라면, 개별 속성 값으로 작업하기 때문에 기본 서식 지정으로 제한되지 않으며(DisplayName이 아니라 레이블 Display Name을 사용한다는 사실을 기억해 두십시오) 정보가 명령 창에서만 표시되는 제한도 없다는 점입니다.

이 데이터를 Excel 스프레드시트에 쓰고 싶으십니까? 이 역시 간단합니다. 우선 WinRM 스크립트에서 Enumerate 메서드 바로 뒤에 다음과 같은 코드 블록을 넣습니다. 이 코드 블록은 새 Excel 스프레드시트를 만들고 구성하는 코드입니다.

Set objExcel = _ CreateObject("Excel.Application") objExcel.Visible = True Set objWorkbook = objExcel.Workbooks.Add() Set objWorksheet = objWorkbook.Worksheets(1)

i = 2 objWorksheet.Cells(1,1) = "Name" objWorksheet.Cells(1,2) = "Display Name"

이제 원래의 Do Until 루프를 그림 2에 보이는 것으로 바꿉니다. 직접 결과를 확인하십시오.

Figure 2 새 Do Until 루프

Do Until objResponse.AtEndOfStream strXML = objResponse.ReadItem

  Set objXMLDoc = CreateObject("Microsoft.XMLDom") objXMLDoc.async=False objXMLDoc.loadXML(strXML)

  Set objElement = objXMLDoc.documentElement objExcel.Cells(i, 1) = objElement.ChildNodes(0).ChildNodes(0).NodeValue objExcel.Cells(i, 2) = objElement.ChildNodes(1).ChildNodes(0).NodeValue i = i + 1 Loop

WinRM, 3부?

이달 칼럼과 지난달 칼럼만 참조하셔도 WinRM 사용을 시작하기에는 충분합니다. 그게 저희의 바람이랍니다. WinRM은 안전과 보안은 유지하면서 컴퓨터를 훨씬 더 쉽게 원격 관리할 수 있도록 도와주는 새롭고 흥미로운 기술입니다. 이렇게 말하면 WinRM에 대한 기사가 3부까지 있다는 의미인지 궁금해 하는 독자가 있을 것입니다

죄송하지만 그 정보는 공개할 수 없습니다. 보안 문제 때문은 아니고요, 그저 지금으로서는 우리 Scripting Guy의 계획이나 의사 결정 절차 때문에 WinRM에 대한 다른 기사를 쓸 것인지 여부를 아직 알 수 없답니다. 흔히 하는 얘기지만, 채널을 고정하세요!

Dr. Scripto의 Scripting Perplexer

Doctor Scripto가 작은 사고를 치고 말았습니다. 스크립트를 방치하고 실수로 스크립팅 조각들을 마구 섞어버린 거지요. 그는 문제를 해결하기 위해 변수, 키워드, 기호 등을 모두 찾아서 알파벳 순서로 정렬했습니다. 하지만 이제 이를 다시 배열하여 스크립트 전체를 올바르게 구성해야 합니다. 시간이 어느 정도 걸리겠지만 다음 달 TechNet Magazine이 발간되기 전까지는 답을 찾을 예정입니다. 그동안 여러분도 이 뒤죽박죽 스크립팅 조각 모음을 하나의 스크립트로 맞추어 보시기 바랍니다. 행운을 기원합니다!

한 가지 작은 힌트를 드리자면, 최종 스크립트는 지정된 날짜 이전의 모든 파일을 로컬 컴퓨터에서 삭제합니다.

strDate = "20060102000000.000000+000"

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colFiles = objWMIService.ExecQuery("Select * From CIM_DataFile Where CreationDate < '" & strDate & "'")
For Each objFile in colFiles
    Wscript.Echo objFile.Name
Next

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

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