Windows PowerShell필터링의 유용성

Don Jones

지난 달 칼럼에서는 cmdlet 간에 데이터 집합(엄밀히 말하면 개체의 스트림)을 전달하는 방식으로 필요에 따라 집합을 더욱 정밀하게 다듬을 수 있는 Windows PowerShell 파이프라인의 강력한 기능과 유연성에 대해 알아보았습니다. 그때 cmdlet뿐만 아니라 개발자가 직접 작성한 스크립트에도

파이프라인의 이점을 활용할 수 있다는 사실을 언급한 바 있습니다. 이번 달 칼럼에서는 이 주제에 대해 자세히 다루겠습니다.

필자는 일반적으로 WMI(Windows® Management Instrumentation)를 통해 여러 원격 컴퓨터에 대해 작동하는 스크립트를 작성하는 데 Windows PowerShell™을 가장 많이 사용합니다. 원격 컴퓨터를 다루는 모든 작업이 그렇듯이, 스크립트를 실행할 때 이러한 컴퓨터가 정상적으로 작동하지 않을 가능성은 늘 존재합니다. 따라서 이러한 문제를 처리할 수 있는 스크립트를 작성해야 합니다.

물론 다양한 방법으로 스크립트가 WMI 연결 시간 제한을 처리하도록 할 수 있지만, 제한 시간(기본 30초) 자체가 너무 길기 때문에 개인적으로 이러한 접근 방식이 마음에 들지 않았습니다. 수많은 시간 제한이 발생하길 기다려야 한다면 스크립트 실행 속도가 너무 느려지니까요. 그래서 필자는 컴퓨터에 연결하기 전에 해당 컴퓨터가 실제로 온라인 상태인지를 신속히 확인하도록 스크립트를 작성합니다.

Windows PowerShell 패러다임

VBScript와 같은 다른 스크립팅 언어에서는 대개 한 번에 하나의 컴퓨터에 대해서만 작업을 수행하게 됩니다. 즉, 텍스트 파일에 저장된 이름 목록 등에서 컴퓨터 이름을 검색하고 ping 테스트를 통해 해당 시스템을 사용할 수 있는지 확인하게 됩니다. 그리고 시스템을 사용할 수 있으면 WMI를 연결하고 필요한 작업을 수행합니다. 이는 매우 일반적인 스크립팅 스타일의 방법입니다. 사실 필자라면 이러한 방법 대신 모든 코드를 루프로 작성한 후 연결할 컴퓨터마다 한 번씩 루프를 반복할 겁니다.

반면 Windows PowerShell은 개체를 기반으로 하고 개체의 그룹 또는 컬렉션을 대상으로 직접 작업을 수행할 수 있다는 점에서 일괄 작업에 훨씬 적합합니다. Windows PowerShell의 패러다임은 단일 개체나 데이터 조각이 아니라, 전체 그룹을 가지고 작업하면서 처음 설정했던 목표를 달성할 때까지 그룹을 조금씩 조정해 가는 것입니다. 예를 들어 목록에서 컴퓨터 이름을 한 번에 하나만 검색하는 게 아니라 전체 이름 컬렉션을 한꺼번에 읽는 것입니다. 또한 루프에 포함된 각 컴퓨터에 대해 ping 테스트를 수행하는 대신 이름 컬렉션을 받아 ping 테스트를 수행한 후 연결 가능한 컴퓨터 이름만 출력하는 단일 루틴을 작성합니다. 그 다음 단계는 이렇게 얻은 이름, 다시 말해 ping 테스트로 연결할 수 있는 이름에 대해 WMI 연결을 만드는 것입니다.

Windows PowerShell에서도 바로 이 접근 방식을 사용하여 수많은 작업을 수행합니다. 예를 들어 실행 중인 서비스 목록을 얻으려면 다음과 같은 코드를 사용하면 됩니다.

Get-Service | Where-Object { $_.Status –eq "Running" }

그림 1은 필자의 컴퓨터에 출력된 결과입니다. 서비스를 한 번에 하나씩 확인하지 않고, Get-Service를 사용하여 모든 서비스를 검색한 다음 이를 Where-Object에 파이프로 연결하여 실행 중이 아닌 서비스를 필터링으로 걸러냈습니다. 필자가 스크립트에서 구현하려는 것도 이와 유사합니다. 말하자면, 컴퓨터 이름 목록을 가져와 ping 테스트를 통과하지 못한 컴퓨터를 필터링한 다음 ping 테스트를 통과한 컴퓨터를 다음 단계로 전달하는 것입니다.

그림 1 ping 테스트를 통과한 컴퓨터 목록 얻기

그림 1** ping 테스트를 통과한 컴퓨터 목록 얻기 **(더 크게 보려면 이미지를 클릭하십시오.)

필터링 함수

이러한 기능을 수행하는 cmdlet를 직접 작성할 수도 있지만 그 방법은 별로 좋아하지 않습니다. cmdlet를 작성하려면 Visual Basic®이나 C#뿐만 아니라 Microsoft® .NET Framework 개발에 대한 전문 지식이 많이 필요합니다. 그리고 무엇보다도 작업에 더 많은 시간을 할애해야 한다는 점이 문제입니다. 다행히 Windows PowerShell을 사용하면 파이프라인 내에서 완벽하게 작동하는 필터라는 특수 함수를 작성할 수 있습니다. 필터링 함수의 기본 형식은 다음과 같습니다.

function <name> {
  BEGIN {
    #<code>
  }
  PROCESS {
    #<code>
  }
  END {
    #<code>
  }
}

위에서 볼 수 있듯이 이 함수에는 BEGIN, PROCESS, END라는 세 가지 독립적인 스크립트 블록이 있습니다. 필터링 함수(파이프라인 내에서 개체를 필터링하도록 작성된 함수)에는 원하는 작업에 따라 이 세 가지 스크립트 블록을 자유롭게 조합해서 사용할 수 있습니다. 각 블록은 다음과 같이 작동합니다.

  • BEGIN 블록은 함수를 처음 호출할 때 한 번 실행되며, 필요한 경우 설정 작업을 수행하는 데 사용할 수 있습니다.
  • PROCESS 블록은 함수로 전달되는 각 파이프라인 개체마다 한 번씩 실행됩니다. $_ 변수는 현재 파이프라인 입력 개체를 나타냅니다. 필터링 함수에서 PROCESS는 필수 블록입니다.
  • END 블록은 모든 파이프라인 개체가 처리된 후에 한 번 실행되며, 필요한 경우 모든 종류의 종료 작업에 사용할 수 있습니다.

필자의 예제 스크립트에서는 이름 컬렉션을 입력 개체로 받아 각 이름에 대해 ping 테스트를 실행하는 필터링 함수를 만들려고 합니다. 테스트를 통과하는 모든 이름이 파이프라인에 결과로 출력되고, 통과하지 못하는 시스템은 버려집니다. ping 기능은 특별한 설정이나 종결 과정이 필요 없으므로 PROCESS 스크립트 블록만 사용하면 됩니다. 그림 2의 코드에는 전체 스크립트가 나와 있습니다.

Figure 2 Ping-Address 및 Restart-Computer

1  function Ping-Address {
2    PROCESS {
3      $ping = $false
4      $results = Get-WmiObject -query `
5      "SELECT * FROM Win32_PingStatus WHERE Address = '$_'"
6      foreach ($result in $results) {
7        if ($results.StatusCode -eq 0) {
8          $ping = $true
9        }
10      }
11      if ($ping -eq $true) {
12        Write-Output $_
13      }
14    }
15  }
16
17  function Restart-Computer {
18    PROCESS {
19      $computer = Get-WmiObject Win32_OperatingSystem -computer $_
20      $computer.Reboot()
21    }
22  }
23
24  Get-Content c:\computers.txt | Ping-Address | Restart-Computer

이 예에서 필자는 Ping-Address와 Restart-Computer라는 두 가지 함수를 정의했습니다. Windows PowerShell에서 함수를 호출하려면 그 전에 먼저 정의해야 합니다. 따라서 예제 스크립트에서 처음 실행되는 줄은 Get-Content cmdlet를 사용하여 파일에서 컴퓨터 이름 목록(한 줄에 하나의 컴퓨터 이름)을 가져오는 24번째 줄입니다. 이 목록, 정확히 말해 String 개체 컬렉션은 ping이 불가능한 컴퓨터를 모두 필터링하는 Ping-Address 함수에 파이프로 연결됩니다. 그 결과가 Restart-Computer 함수에 파이프로 연결되며 이 함수는 ping이 가능한 각 컴퓨터를 WMI를 사용하여 원격으로 다시 시작합니다.

Ping-Address 함수는 PROCESS 스크립트 블록을 구현합니다. 다시 말해 이 함수에는 입력 개체 컬렉션이 필요합니다. PROCESS 스크립트 블록은 이러한 입력을 자동으로 처리하므로 입력을 포함하기 위해 입력 인수를 정의할 필요가 없었습니다. 그리고 세 번째 줄에서 $ping 변수를 부울 값 False를 나타내는 기본 제공 Windows PowerShell 변수인 $false로 설정하여 프로세스를 시작했습니다.

다음으로, 로컬 Win32_PingStatus WMI 클래스를 사용하여 지정한 컴퓨터를 ping 테스트합니다. 다섯 번째 줄에서 현재 파이프라인 개체를 나타내는 $_ 변수가 WMI 쿼리 문자열에 포함되어 있는 것을 알 수 있습니다. 문자열이 큰따옴표에 포함되어 있으면 Windows PowerShell은 항상 $_과 같은 변수를 해당 내용으로 대체하므로 문자열 연결을 위한 작업을 직접 수행할 필요가 없습니다. 다섯 번째 줄에 이러한 기능이 사용되었습니다.

여섯 번째 줄은 ping 결과를 확인하는 루프입니다. 성공적으로 반환된 결과(StatusCode가 0인 결과)가 있으면 $ping을 $true로 설정하여 테스트에 성공했음을 나타냅니다. 11번째 줄에서 $ping이 $true로 설정되어 있는지 확인합니다. $true로 설정되어 있으면 원래 입력 개체를 기본 출력 스트림으로 출력합니다. Windows PowerShell은 기본 출력 스트림을 자동으로 관리합니다. 즉, 해당 함수가 파이프라인 끝에 있으면 출력 스트림이 텍스트 표현으로 바뀝니다. 또한 파이프라인에 다른 명령이 더 있으면 출력 스트림의 개체(예제의 경우 컴퓨터 이름이 들어 있는 문자열 개체)가 다음 파이프라인 명령으로 전달됩니다.

Restart-Computer 함수는 보다 간단하지만, 역시 PROCESS 블록을 사용하므로 파이프라인에 쉽게 참여할 수 있습니다. 19번째 줄에서 이 함수는 명명된 컴퓨터에 연결하고 해당 컴퓨터의 Win32_OperatingSystem WMI 클래스를 검색합니다. 20번째 줄에서는 클래스의 Reboot 메서드를 실행하여 원격 컴퓨터를 다시 시작합니다.

앞서 언급했지만 코드가 실제로 실행되는 곳은 24번째 줄입니다. 물론 이 스크립트를 실행할 때는 매우 신중해야 합니다. 왜냐하면 이 스크립트는 c:\computers.txt에 명명된 모든 컴퓨터를 다시 부팅하도록 설계되어 있어 이 텍스트 파일에 있는 이름을 잘 살피지 않으면 엄청난 결과를 초래할 수 있기 때문입니다.

다음 단계

이 스크립트가 완벽한 것은 아닙니다. 원격 컴퓨터에서 필요한 포트가 방화벽에 의해 차단되는 오류나 WMI 보안 오류와 같이 기본 연결과 관련 없는 WMI 오류의 가능성에 대해서도 인식해야 합니다. 이러한 문제는 트랩 처리기를 구현하면 처리할 수 있습니다. 여기에 대한 자세한 내용은 향후 칼럼에서 다루도록 하겠습니다.

또한 이 스크립트는 원격 컴퓨터를 다시 시작하는 상당히 위험한 작업을 수행합니다. 이처럼 위험성이 있는 작업을 시도하는 스크립트에서는 자주 사용되는 두 가지 Windows PowerShell 매개 변수(–Confirm 및 –WhatIf)를 구현해야 합니다. 자세한 구현 방법은 지면상 설명하기가 곤란하므로 역시 향후 칼럼에서 다루도록 하겠습니다. 지금은 우선 Windows PowerShell 팀 블로그를 확인해 보십시오. 설계자로 일하고 있는 Jeffrey Snover가 이 항목에 대해 다루고 있습니다.

이러한 기능을 모르더라도 필터링 함수의 기능에 대해 쉽게 알 수 있습니다. 앞으로 칼럼을 통해 계속 자세한 기법을 소개하면서, 함수를 사용하여 재사용이 가능한 코드를 모듈화하고 스크립트의 일반적인 기능을 향상시키는 방법을 설명할 예정입니다.

Don Jones는 SAPIEN Technologies의 수석 스크립팅 전문가이자 Windows PowerShell: TFM(SAPIEN Press, 2007)의 공동 저자입니다. 문의 사항이 있으면 www.ScriptingAnswers.com으로 연락하시면 됩니다.

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