Windows PowerShell한 번에 한 줄씩 스크립팅

Don Jones

필자는 지난 칼럼들을 통해 Windows PowerShell이 셸이라는 점을 강조해 왔습니다. Windows PowerShell은 이미 잘 알고 있는 cmd.exe(또는 명령 프롬프트) 셸과는 달리 대화식으로 사용되며, 여전히 cmd.exe의 배치 언어보다 훨씬 강력한 스크립트 언어를 지원합니다. 심지어

VBScript 같은 언어에 견줄 수 있을 만큼 강력합니다. 그러나 Windows PowerShell™은 대화식 셸이므로 스크립트를 훨씬 쉽게 배울 수 있습니다. 실제로 셸 내에서 스크립트를 대화식으로 개발할 수 있으므로 한 번에 한 줄씩 스크립트를 작성하여 그 결과를 즉시 확인할 수 있습니다.

이와 같은 반복적인 스크립팅 기술에 의해 디버깅도 보다 쉬워집니다. 스크립트 결과를 즉시 확인할 수 있으므로 예상과 다른 결과가 나타날 경우 스크립트를 신속하게 수정할 수 있습니다.

이 칼럼에서는 Windows PowerShell의 대화식 스크립팅 예를 살펴보고, 텍스트 파일에서 서비스 이름을 읽고 각 서비스의 시작 모드를 사용 안 함으로 설정하는 스크립트를 만들어 보겠습니다.

이를 통해 Windows PowerShell의 전체 스크립트를 하나의 커다란 조각으로 다루기 보다는 작은 조각들로 구성하는 개념에 익숙해질 수 있습니다. 수행해야 하는 관리 작업을 하나 선택하여 이를 구성 요소 조각으로 세분화한 다음 각 조각이 독립적으로 작동하도록 구성하는 방법을 확인할 수 있습니다. 앞으로 설명하겠지만 Windows PowerShell은 이러한 조각들을 결합하는 뛰어난 방법을 제공합니다. 작은 조각들을 한 번에 하나씩 구성하면 최종 스크립트를 보다 쉽게 개발할 수 있다는 사실을 발견하게 될 것입니다.

파일에서 이름 읽기

Windows PowerShell에서 텍스트 파일을 읽는 방법을 이해하는 것은 쉽지 않은 일입니다. Help *file*을 실행하면 파일에서 텍스트를 읽는 대신 텍스트를 파일로 보내는 Out-File cmdlet에 대한 도움말이 표시됩니다. 이는 전혀 도움이 되지 않습니다! 그러나 Windows PowerShell cmdlet 이름은 일종의 논리를 따르므로 이를 활용할 수 있습니다. Windows PowerShell이 어떤 항목을 검색할 때 cmdlet 이름은 보통 Get으로 시작됩니다. 따라서 Help Get*을 실행하여 해당 cmdlet을 표시한 다음 목록을 스크롤하여 Get-Content를 확인해 보니, 괜찮아 보입니다! 그래서 Help Get-Content를 실행하여 수행 작업에 대한 자세한 내용(그림 1 참조)을 확인해 보니 원하던 결과와 거의 비슷합니다.

그림 1 Help Get-Content를 실행하여 자세한 정보 확인

그림 1** Help Get-Content를 실행하여 자세한 정보 확인 **(더 크게 보려면 이미지를 클릭하십시오.)

Windows PowerShell은 거의 모든 항목을 개체로 취급하는데 텍스트 파일도 예외는 아닙니다. 텍스트 파일은 원칙적으로 볼 때 줄의 집합이며 파일의 각 줄은 일종의 독립적인 개체 역할을 합니다. 따라서 C:\services.txt라는 텍스트 파일을 만들어 서비스 이름으로 채우면(각 이름은 파일 내의 고유한 줄에 위치함) Windows PowerShell에서 Get-Content cmdlet을 사용하여 이름을 개별적으로 읽을 수 있습니다. 이 연습은 스크립트를 대화식으로 개발하는 방법을 보여 주기 위한 것이므로 Get-Content를 실행하고 텍스트 파일에 이름을 지정한 다음 그 결과를 살펴보는 것부터 시작하겠습니다.

PS C:\> get-content c:\services.txt
messenger
alerter
PS C:\>

예상대로 Windows PowerShell은 파일을 읽고 이름을 표시합니다. 물론 단순히 이름을 표시하는 것이 목적은 아니지만 Get-Content가 예상대로 작동한다는 것을 알 수 있습니다.

서비스 변경

이제부터는 서비스의 시작 모드를 변경해 보겠습니다. 올바른 cmdlet을 찾아 보는 것부터 시작하겠습니다. Help *Service*를 실행합니다. 그러면 간단한 목록이 표시되는데 이 중에서 Set-Service cmdlet이 현재 필요에 적합한 것 같습니다. 이 cmdlet을 스크립트에 통합하기 전에 작동 방식을 이해하고 있는지 확인하기 위해 이 cmdlet을 테스트하려고 합니다. Help Set-Service를 실행하면 cmdlet의 작동 방식이 표시되며 간단한 테스트를 통해 이를 확인할 수 있습니다.

PS C:\> set-service messenger -startuptype
    disabled
PS C:\>

여러 부분의 결합

이제 Set-Service cmdlet을 사용하여 파일에서 서비스 이름을 읽는 기능을 결합해야 합니다. 그러면 Windows PowerShell의 강력한 파이프라인 기능이 작동하기 시작합니다. 파이프라인을 사용하면 첫 번째 cmdlet의 출력이 두 번째 cmdlet의 입력으로 전달됩니다. 파이프라인은 전체 개체를 전달합니다. 개체 컬렉션이 파이프라인에 삽입될 경우 각 개체는 파이프라인을 통해 개별적으로 전달됩니다. 즉, 개체 컬렉션인 Get-Content의 출력이 파이프라인을 통해 Set-Service에 전달될 수 있습니다. Get-Content는 컬렉션을 전달하므로 컬렉션의 각 개체 또는 텍스트 줄은 파이프라인을 통해 개별적으로 Set-Service에 전달됩니다. 그 결과 Set-Service가 텍스트 파일의 각 줄마다 한 번씩 실행됩니다. 명령은 다음과 같습니다.

PS C:\> get-content c:\services.txt | 
 set-service -startuptype disabled
PS C:\>

결과:

  1. Get-Content cmdlet이 전체 파일을 읽으면서 실행됩니다. 파일의 각 줄은 고유한 개체로 취급되며 이들이 모여 개체 컬렉션이 됩니다.
  2. 개체 컬렉션이 파이프라인을 통해 Set-Service로 전달됩니다.
  3. 파이프라인이 각 입력 개체마다 한 번씩 Set-Service cmdlet을 실행합니다. 한 번 실행될 때마다 입력 개체가 cmdlet의 첫 번째 매개 변수(서비스 이름)로 Set-Service에 전달됩니다.
  4. 첫 번째 매개 변수에 대한 입력 개체를 사용하여 Set-Service가 실행되며 다른 매개 변수(이 경우 -startuptype 매개 변수)가 지정됩니다.

흥미로운 점은 이 시점에서 작업을 실제로 완료했지만 아직 스크립트를 하나도 작성하지 않았다는 사실입니다. Cmd.exe 셸에서는 이와 똑같은 작업을 수행하기가 쉽지 않으며 VBScript에서는 수십 줄의 코드가 필요합니다. 그러나 Windows PowerShell에서는 단 한 줄만으로 이 모든 작업을 처리할 수 있습니다. 아직 끝나지 않았습니다. 보다시피 필자가 작성한 명령은 많은 양의 상태 출력이나 피드백을 제공하지 않습니다. 오류가 없는 것은 차치하더라도 실제로 발생한 작업을 확인하기가 쉽지 않습니다. 지금까지 작업을 완료하는 데 필요한 기능을 익혔으므로 이제 실제 스크립팅을 통해 모양을 다듬을 차례입니다.

Windows PowerShell Prompt Here(Michael Murgolo)

가장 인기 있으면서 필자가 가장 선호하는 Microsoft® PowerToys for Windows® 중 하나는 Open Command Window Here(여기서 명령 창 열기) 도구입니다. Windows Server® 2003 Resource Kit 도구의 Microsoft PowerToys for Windows XP 중 하나로 제공되는 Open Command Window Here(여기서 명령 창 열기)를 사용하면 Windows 탐색기에서 폴더나 드라이브를 마우스 오른쪽 단추로 클릭하고 선택한 폴더를 가리키는 명령 창을 열 수 있습니다.

Windows PowerShell에 대해 배울 때 Windows PowerShell에도 같은 기능이 있으면 좋겠다고 생각했었습니다. 그래서 Open Command Window Here(여기서 명령 창 열기) 설치 프로그램의 .inf 파일인 cmdhere.inf를 Windows Server 2003 Resource Kit 도구에서 선택한 후 이를 수정하여 Windows PowerShell Prompt Here 컨텍스트 메뉴를 만들었습니다. 이 .inf 파일은 이 추가 기사의 바탕이 되는 블로그 원본(leeholmes.com/blog/PowerShellPromptHerePowerToy.aspx)에 포함되어 있습니다. 이 도구를 설치하려면 .inf 파일을 마우스 오른쪽 단추로 클릭하고 설치를 선택하십시오.

이 도구의 Windows PowerShell 버전을 만들 때 원본에 버그가 있음을 발견했는데, 그 버그는 Open Command Window Here(여기서 명령 창 열기)를 제거해도 비활성화된 컨텍스트 메뉴 항목이 그대로 남아 있는 것입니다. 그래서 cmdhere.inf의 업데이트된 버전을 제공했습니다. 이 버전도 원본 블로그에서 제공하고 있습니다.

이러한 두 PowerToys는 이 컨텍스트 메뉴 항목이 디렉터리 및 드라이브 개체 유형과 연결된 키의 레지스트리에 구성되어 있다는 사실을 이용합니다. 이는 컨텍스트 메뉴 작업이 파일 형식과 연결되는 방식과 동일한 방식으로 이루어집니다. 예를 들어 Windows 탐색기에서 .txt 파일을 마우스 오른쪽 단추로 클릭하면 목록 맨 위에 몇 개의 작업(예: 열기, 인쇄, 편집)이 표시됩니다. HKEY_CLASSES_ROOT 레지스트리 하이브를 살펴보면 이러한 항목이 어떻게 구성되어 있는지 알 수 있습니다.

레지스트리 편집기를 열고 HKEY_CLASSES_ROOT 분기를 확장하면 .doc, .txt 등과 같은 파일 형식에 대해 이름이 지정된 키가 보입니다. .txt 키를 클릭하면 (Default) 값이 txtfile임을 확인할 수 있습니다(그림 A 참조). 이는 .txt 파일과 연결된 개체 유형입니다. 아래로 스크롤하여 txtfile 키를 확장한 다음 그 아래에 있는 셸 키를 확장하면 .txt 파일의 몇 가지 컨텍스트 메뉴 항목에 대해 이름이 지정된 키가 보입니다. 모든 컨텍스트 메뉴 항목이 보이지 않는 것은 컨텍스트 메뉴를 만드는 다른 방법이 있기 때문입니다. 이러한 각각의 키 아래에는 명령 키가 있습니다. 명령 키 아래에 있는 (Default) 값은 해당 컨텍스트 메뉴 항목을 선택하면 Windows에서 실행되는 명령줄입니다.

명령 프롬프트와 Windows PowerShell 프롬프트용 도구는 이 기술을 사용하여 Open Command Window Here(여기서 명령 창 열기) 및 Windows PowerShell Prompt Here 컨텍스트 메뉴 항목을 구성합니다. 드라이브 및 디렉터리와 연결된 파일 형식은 없지만 이 개체와 연결된 드라이브 및 디렉터리 키가 HKEY_CLASSES_ROOT 아래에 있습니다.

그림 A 레지스트리에 구성된 컨텍스트 메뉴 항목

그림 A** 레지스트리에 구성된 컨텍스트 메뉴 항목 **(더 크게 보려면 이미지를 클릭하십시오.)

Michael Murgolo는 Microsoft Consulting Services의 수석 인프라 컨설턴트로서, 운영 체제, 배포, 네트워크 서비스, Active Directory, 시스템 관리, 자동화 및 패치 관리 분야를 담당하고 있습니다.

대화식 스크립팅

여기서 설명할 한 가지 작업은 C:\services.txt에 나열된 각 서비스에 대해 여러 개의 cmdlet을 실행하는 것입니다. 그러면 서비스 이름과 원하는 정보를 출력할 수 있으므로 스크립트의 진행 상황을 추적할 수 있습니다.

앞에서는 파이프라인을 사용하여 개체를 한 cmdlet에서 다른 cmdlet으로 전달했습니다. 그러나 이번에는 스크립트와 보다 유사한 기법인 Foreach 구문(지난 달 칼럼에서 소개함)을 사용하려고 합니다. Foreach 구문에서는 개체 컬렉션을 사용할 수 있으며 컬렉션의 각 개체에 대해 여러 개의 cmdlet을 실행합니다. 여기서는 루프를 순환할 때마다 현재 개체를 나타내는 변수를 지정하겠습니다. 예를 들면 다음과 같은 구문으로 시작할 수 있습니다.

foreach ($service in get-content c:\services.txt)

여전히 동일한 Get-Content cmdlet을 사용하여 텍스트 파일의 내용을 검색하지만 이번에는 Get-Content에 의해 반환된 개체 컬렉션 전체에 대해 Foreach 구문을 사용하여 루프를 실행할 것입니다. 루프는 각 개체에 대해 한 번씩 실행되는데 현재 개체는 $service 변수에 위치합니다. 이제 루프 내에서 실행할 코드를 지정하기만 하면 됩니다. 먼저 앞에서 만든 한 줄의 명령을 복제하겠습니다. 이렇게 하는 것이 가장 간단할 뿐 아니라 기능 손실도 전혀 발생하지 않습니다.

PS C:\> foreach ($service in get-content c:\services.txt) {
>> set-service $service -startuptype disabled
>> }
>>
PS C:\>

여기에는 몇 가지 흥미로운 점이 있습니다. 위의 Foreach 구문을 보면 첫 줄이 여는 중괄호로 끝났습니다. 여는 중괄호와 닫는 중괄호 사이에 있는 모든 구문은 Foreach 루프에 속한 것으로 간주되어 입력 컬렉션의 각 개체에 대해 한 번씩 실행됩니다. {를 입력하고 Enter 키를 누르면 Windows PowerShell의 프롬프트가 >>로 변경됩니다(그림 2 참조). 이 프롬프트는 시스템이 어떤 종류의 구문이 시작되었음을 인식하고 이 구문이 종료되기를 기다린다는 것을 나타냅니다. 여기에 Set-Service cmdlet을 입력했습니다. $service는 텍스트 파일에서 읽은 현재 서비스 이름을 나타내므로 이번에는 $service를 첫 번째 매개 변수로 사용했습니다. 다음 줄에서 닫는 중괄호를 사용하여 구문을 종결했습니다. Enter 키를 두 번 누르면 Windows PowerShell에서 코드를 즉시 실행합니다.

그림 2 구문이 시작되었음을 인식하는 Windows Powershell

그림 2** 구문이 시작되었음을 인식하는 Windows Powershell **(더 크게 보려면 이미지를 클릭하십시오.)

이처럼 Windows PowerShell에서는 입력 내용을 실제로 즉시 실행하므로 입력된 내용이 스크립트처럼 보이더라도 텍스트 파일로 저장되지 않습니다. 이제 현재 서비스 이름을 출력하는 한 줄의 코드를 추가하여 전체 내용을 다시 입력하겠습니다.

PS C:\> foreach ($service in get-content c:\services.txt) {
>> set-service $service -startuptype disabled
>> "Disabling $service"
>> }
>>
Disabling messenger
Disabling alerter
PS C:\>

위 코드를 보면 어떤 항목을 표시하도록 요청하고 이를 큰 따옴표로 묶은 것을 알 수 있습니다. Windows PowerShell에서는 따옴표 안의 내용을 명령이 아닌 텍스트 문자열로 인식합니다. 그러나 작은 따옴표와 달리 큰 따옴표를 사용하면 Windows PowerShell은 텍스트 문자열에서 변수를 검색합니다. 검색에 의해 변수를 찾으면 변수 이름을 변수의 실제 값으로 바꿉니다. 따라서 이 코드를 실행하면 현재 서비스 이름이 표시되는 것을 확인할 수 있습니다.

그러나 아직도 스크립트라고 할 수 없습니다.

지금까지, 작성한 코드의 결과를 즉시 확인할 수 있는 뛰어난 방법인 Windows PowerShell을 대화식으로 사용해 보았습니다. 그러나 때로는 이러한 코드 줄을 다시 입력하는 것이 번거로울 수 있습니다. 이러한 이유로 Windows PowerShell에서도 스크립트를 실행하는 것입니다. 실제로 Windows PowerShell 스크립트는 워드 스크립트의 정의를 가장 충실히 따릅니다 기본적으로 Windows PowerShell은 스크립트 텍스트 파일을 그대로 읽어서 사용자가 직접 행을 입력하는 것과 똑같이 각 행을 "입력"합니다. 즉, 지금까지 작성한 모든 코드를 스크립트 파일에 붙여 넣을 수 있습니다. 따라서 메모장에서 disableservices.ps1이라는 파일을 만들고 다음 내용을 붙여 넣겠습니다.

foreach ($service in get-content c:\services.txt) {
 set-service $service -startuptype disabled
 "Disabling $service"
 }

이 파일은 C:\test 폴더에 저장했습니다. 이제 PowerShell에서 이 파일을 실행해 보겠습니다.

PS C:\test> disableservices
'disableservices' is not recognized as a cmdlet, function, operable program, or
<script file.
At line:1 char:15
+ disableservices <<<<
PS C:\test>

이런, 무엇이 잘못된 것일까요? Windows PowerShell이 C:\test 폴더를 검색했지만 작성된 스크립트를 찾지 못합니다. 왜일까요? 보안 제약 조건에 의해 Windows PowerShell은 현재 폴더에서는 스크립트를 실행하지 못하도록 설계되어 있는데, 이는 스크립트가 운영 체제 명령을 가로채지 못하도록 하기 위해서입니다. 예를 들면 dir.ps1이라는 스크립트를 만들어 일반 dir 명령을 다시 정의하도록 할 수 없습니다. 따라서 현재 폴더에서 스크립트를 실행하려면 상대 경로를 지정해야 합니다.

PS C:\test> ./disableservices
The file C:\test\disableservices.ps1 cannot be loaded. 
The execution of scripts is disabled on this system. 
Please see "get-help about_signing" for more details.
At line:1 char:17
+ ./disableservices <<<<
PS C:\test>

하지만 이번에도 여전히 스크립트는 작동하지 않습니다. 경로를 올바르게 지정했지만 Windows PowerShell에서 스크립트를 실행할 수 없습니다. 왜냐하면 기본적으로 Windows PowerShell에서는 스크립트를 실행할 수 없기 때문입니다. 이는 VBScript에서 겪었던 문제를 방지하기 위해 설계된 보안 예방 조치입니다. Windows PowerShell에서는 기본적으로 스크립트를 실행할 수 없기 때문에 악의적인 스크립트가 실행될 수 없습니다. 스크립트를 실행하기 위해서는 실행 정책을 명시적으로 변경해야 합니다.

PS C:\test> set-executionpolicy remotesigned

RemoteSigned 실행 정책은 로컬 컴퓨터에서 서명되지 않은 스크립트의 실행은 허용하지만, 다운로드한 스크립트의 경우에는 여전히 서명이 있어야만 실행됩니다. 이보다 보안이 강화된 정책인 AllSigned 정책에서는 신뢰할 수 있는 게시자가 발급한 인증서를 사용하여 디지털 서명된 스크립트만 실행할 수 있습니다. 그러나 현재는 즉시 사용할 수 있는 인증서가 없어 스크립트에 서명할 수 없으므로 RemoteSigned를 사용하는 것이 적절해 보입니다. 이제 스크립트를 다시 실행해 보겠습니다.

PS C:\test> ./disableservices
Disabling messenger
Disabling alerter
PS C:\test>

사용된 RemoteSigned 실행 정책은 최상의 선택이 아니라 단지 이 상황에 적절한 선택이었다는 점을 지적하고 싶습니다. 훨씬 뛰어난 솔루션이 있습니다. 코드 서명 인증서를 사용하는 것이 훨씬 더 안전하지만, Windows PowerShell Set-AuthenticodeSignature cmdlet을 사용하여 현재 작성된 스크립트에 서명하고 실행 정책을 한층 더 안전한 AllSigned 정책으로 설정하십시오.

절대 사용해서는 안 되는 Unrestricted라는 또 다른 정책도 있습니다. 이 정책은 컴퓨터에서 모든 스크립트 즉, 원격 위치에 있는 악의적인 스크립트라도 아무 제한 없이 실행되도록 허용하므로 매우 위험할 수 있습니다. 따라서 이유 여하를 막론하고 Unrestricted 정책은 사용하지 않는 것이 좋습니다.

즉각적인 결과

Windows PowerShell의 대화식 스크립팅 기능을 사용하면 스크립트 또는 심지어 스크립트의 여러 작은 조각까지도 아주 빨리 프로토타입으로 만들 수 있습니다. 또한 즉각적인 결과를 얻을 수 있으므로, 스크립트를 간편하게 수정하여 원하는 정확한 결과를 얻을 수 있습니다. 작성이 끝난 스크립트는 ps1 파일로 옮겨 영구적으로 보관해 두었다가 나중에 손쉽게 사용할 수 있습니다. 단, 이러한 .ps1 파일에는 반드시 디지털 서명을 하여 Windows PowerShell의 스크립트 실행 정책 중 가장 안전한 정책인 AllSigned 실행 정책이 유지되도록 하는 것이 좋습니다.

Don Jones는 SAPIEN Technologies의 수석 스크립팅 전문가이자 Windows PowerShell: TFM(www.SAPIENPress.com 참조)의 공동 저자입니다. 문의 사항이 있을 경우 don@sapien.com으로 연락하시면 됩니다.

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