Windows Administration

Windows PowerShell로 그룹 정책 관리 단순화

Thorbjörn Sjövold

 

한 눈에 보기:

  • Windows PowerShell이란?
  • 기존의 GPO 관리 방법
  • Windows PowerShell로 스크립트 마이그레이션

Microsoft 그룹 정책 기술은 처음에는 쉽게 받아들여지지 않았습니다. 이해하기가 다소 어려웠고 이를 사용하려면 한때 표준이었던 계정/리소스 도메인과 비슷한 점이 전혀 없어 보였던 생소하고 새로운 서비스인 Active Directory에 적응해야 했습니다. 하지만 이런 그룹 정책이 지금은

Windows® 인프라를 사용하는 거의 모든 조직의 핵심 관리 방법이 되었습니다. Microsoft의 최신 관리 기술인 Windows PowerShell™의 경우도 동일한 과정을 거칠 것으로 생각합니다. 사실 그룹 정책 관리자의 경우 Windows PowerShell을 이용하면 자신이 맡은 업무를 훨씬 쉽게 수행할 수 있습니다.

이 기사에서는 일반적인 COM 기반 스크립팅 언어인 VBScript와 같은 Windows Scripting Host 언어용으로 작성된 Microsoft® GPMC(그룹 정책 관리 콘솔) API를 Windows PowerShell에서 직접 사용하여 자신의 환경에서 그룹 정책을 관리하는 방식을 단순화하는 방법을 소개합니다.

그룹 정책 작업 스크립팅

몇 년 전 Microsoft에서 GPMC를 발표했을 때 그룹 정책 관리자는 갑자기 편리한 기능 여러 가지를 얻게 되었습니다. 그룹 정책 중심 MMC 스냅인은 특히 Active Directory® 사용자 및 컴퓨터와 비교했을 때 그룹 정책 관리를 위한 큰 발전으로 인식되었습니다. 게다가 관리자가 VBScript와 같은 COM 기반 언어를 사용하여 GPO(그룹 정책 개체)의 백업과 복원, 도메인 간의 GPO 마이그레이션, GPO와 링크에 대한 보안 설정 구성 및 보고 등과 같은 그룹 정책 관리 작업을 수행할 수 있도록 해주는 새로운 API가 선보였습니다.

하지만 GPMC는 그룹 정책 개체 안에서 실제로 구성된 설정을 편집하는 기능은 제공하지 않았습니다. 즉, GPO 컨테이너에 대해 GPO 버전 읽기, 수정 날짜 읽기, 새 GPO 만들기, 다른 도메인으로부터 GPO 복원/가져오기 등의 작업을 수행하는 것은 가능했지만, 새로 리디렉션된 폴더 추가나 새 소프트웨어 설치 등의 GPO 내용을 프로그래밍 방식으로 추가하거나 변경하는 작업은 수행할 수 없었습니다. 대신 그룹 정책 개체 편집기를 사용하여 GPO를 만들고 모든 설정을 수동으로 구성한 다음 이를 백업하고 테스트 환경으로 가져와야 했습니다. 모든 사항을 확인하고 이러한 사항이 제대로 작동하면 라이브 환경으로 가져왔습니다. 이렇게 기능은 부족했지만 GPMC API로 직접 작업하는 있는 대신 스크립트를 사용할 수 있기 때문에 일상적인 그룹 정책 관리 작업에 소요되는 시간, 노력 및 이와 관련하여 발생하는 오류는 크게 감소했습니다.

더 나은 수준

그렇다면 Windows PowerShell은 VBScript 같은 스크립팅 언어와 어떻게 다를까요? 우선 Windows PowerShell은 셸입니다. 최소한 이 글의 목적에서 보자면 셸은 명령줄 인터프리터로 생각할 수 있습니다. VBScript는 명령줄에서 실행할 수 있지만 VBScript 파일을 줄 단위로 실행할 수는 없습니다. 이와 반대로 Windows PowerShell 스크립트는 일련의 개별 명령으로 만들 수 있습니다. 또한 Windows PowerShell에는 VBScript의 서브루틴과 매우 비슷한 역할을 하며 Windows PowerShell 명령 프롬프트에서 실시간으로 만들 수 있는 함수들이 있습니다.

게다가 VBScript는 기존의 COM 기술에 의존하는 반면 Windows PowerShell은 Microsoft .NET Framework를 기반으로 합니다. 즉, 현재 생성되는 방대한 양의 .NET 코드를 Windows PowerShell 내에서 직접 사용할 수 있습니다.

따라서 Windows PowerShell을 사용하면 완전한 스크립팅이 가능할 뿐만 아니라 대화형 모드도 활용할 수 있습니다. 여기서 소개하는 예는 모두 명령줄 입력이기 때문에 직접 입력해도 되지만 Windows PowerShell 스크립트 파일에 넣어서 실행해도 동일하게 정상 작동합니다.

기존 스크립트를 Windows PowerShell로 다시 만들기

새 기술을 사용하려고 할 때 기존의 모든 작업을 버려야 하는 상황을 원하지는 않을 것입니다. GPMC API에서 COM 개체에 액세스하거나 기본적으로 이전 VBScript를 다시 사용하는 방법에는 세 가지가 있습니다. 다음 세 가지 옵션 중 하나를 선택할 수 있습니다.

  • C# 또는 Managed C++ 등의 프로그래밍 언어를 사용하여 Windows PowerShell cmdlet을 만듭니다.
  • Windows PowerShell을 사용하여 MSScript.ocx의 ScriptControl에 액세스하여 이전 스크립트를 래핑합니다.
  • 재사용 가능한 Windows PowerShell 함수에서 COM 호출을 래핑하거나 COM 개체를 직접 호출합니다.

여기서는 주로 세 번째 옵션에 대해 설명하겠지만 우선 세 옵션 모두를 간단하게 살펴보겠습니다.

Windows PowerShell Cmdlet 만들기

Microsoft는 Windows PowerShell에 파일 복사, 출력 서식 지정, 날짜 및 시간 검색 등의 작업을 할 수 있도록 여러 cmdlet을 추가했습니다. 하지만 필요한 경우 cmdlet을 직접 만들 수도 있습니다. cmdlet를 만드는 과정은 msdn2.microsoft.com/ms714598.aspx에서 자세히 설명합니다. 이 단계를 간단히 설명하자면 다음과 같습니다.

  • C#과 같은 .NET 프로그래밍 언어에서 클래스 라이브러리 DLL을 만듭니다.
  • 새 클래스를 만들고 기본 클래스 cmdlet에서 상속합니다.
  • 이름, 용도, 입력 매개 변수 등을 결정하는 특성을 설정하고 코드를 추가합니다.

Windows PowerShell은 .NET Framework를 기반으로 하기 때문에 매개 변수로 반환되거나 전달되는 문자열, 개체 등과 같은 모든 형식은 코드와 Windows PowerShell에서 정확하게 동일합니다. 따라서 별도의 형식 변환이 필요 없습니다.

이 방법의 진정한 장점은 마음대로 활용 가능한 완전한 프로그래밍 언어가 있다는 점입니다.

MSScript.ocx의 ScriptControl 개체를 사용하여 이전 스크립트 래핑

VBScript 파일을 실행하려면 당연히 VBScript 엔진이 필요합니다. 하지만 이 엔진은 COM 개체이며 Windows PowerShell에서 COM 개체를 사용할 수 있기 때문에 VBScript 엔진을 호출하기만 하면 됩니다. 다음과 같은 코드로 이러한 작업을 수행할 수 있습니다.

$scriptControl = New-Object -ComObject ScriptControl
$scriptControl.Language = ‘VBScript’
$scriptControl.AddCode(
    ‘Function ShowMessage(messageToDisplay)
    MsgBox messageToDisplay
    End Function’)
$scriptControl.ExecuteStatement(‘ShowMessage
    "Hello World"’)

이 코드를 Windows PowerShell CLI(명령줄 인터페이스)에 입력하면 VBScript 함수 ShowMessage가 호출되어 매개 변수와 함께 실행됩니다. 그 결과 Hello World라는 텍스트가 있는 메시지 상자가 표시됩니다.

이것만 보고 "좋았어. Windows PowerShell에서 COM을 사용하는 방법을 알았으니까 이제 이 기사는 그만 읽고 사용하고 있는 이전의 GPMC 스크립트 컬렉션을 ScriptControl 개체에 채워야겠다."고 생각하는 분들도 있을 것입니다. 하지만 그렇게 간단하지만은 않습니다. 이 기술은 금방 매우 복잡해지고 스크립트가 커질수록 디버그도 매우 까다로워집니다.

COM 개체 래핑

따라서 최선의 옵션은 세 번째인 재사용 가능한 Windows PowerShell 함수에서 COM 호출을 래핑하여 GPMC API에서 COM 개체를 사용할 수 있도록 만드는 방법입니다. 아래의 코드 줄을 보면 Windows PowerShell에서 직접 .NET 개체를 만드는 방법을 알 수 있습니다. 여기서는 파일 크기를 가져오는 데 사용할 수 있는 FileInfo 개체를 예로 들었습니다.

$netObject = New-Object System.IO.FileInfo(
"C:\boot.ini") # Create an instance of FileInfo 
               # representing c:\boot.ini

# 기호는 Windows PowerShell에서 인라인 주석으로 사용됩니다. 새롭게 인스턴스화된 이 FileInfo 개체를 사용하면 다음 코드를 간단히 입력하여 boot.ini의 크기를 쉽게 가져올 수 있습니다.

$netObject.Length # Display the size in bytes of the
                  # file in the command line interface

여기서 잠깐 COM 개체와 VBScript 변환에 대해 설명하도록 하겠습니다. 다음 명령을 살펴보십시오.

$comFileSystemObject = New-Object –ComObject Scripting.FileSystemObject

앞에서 .NET Framework에서 네이티브 개체를 만드는 데 사용한 구문과 이 구문은 기본적으로 동일하나 다음 두 가지 차이가 있습니다. 우선 –ComObject 스위치를 추가하여 Windows PowerShell이 .NET이 아닌 COM을 지향하도록 했습니다. 두 번째로 .NET 생성자 대신 COM ProgID(이 경우 Scripting.FileSystemObject)를 사용합니다. ProgID는 지금까지 사용하던 것과 같은 이름입니다. VBScript에서는 다음에 해당합니다.

Set comFileSystemObject = CreateObject(
    "Scripting.FileSystemObject")

VBScript를 사용하여 파일 크기를 가져오려면 위의 코드 줄과 다음 코드를 파일에 추가합니다.

Set comFileObject = comFileSystemObject.GetFile(
    "C:\Boot.ini")
WScript.Echo comFileObject.Size

그런 다음 Cscript.exe 등을 사용하여 실행합니다. Windows PowerShell에서는 다음과 같이 합니다. 원하는 경우 Windows PowerShell 명령줄에서 직접 수행합니다.

$comFileObject = $comFileSystemObject.GetFile(
    "C:\boot.ini")
$comFileObject.Size

물론 파일 크기를 읽는 VBScript를 변환하려면 드라이브의 개체를 관리하는 Windows PowerShell cmdlet을 사용해도 되지만 Windows PowerShell에서 COM에 액세스하는 것이 얼마나 쉬운지 보여 주기 위해 이 방식을 사용했습니다. Windows PowerShell에게 COM 개체를 생성하라고 지시하지만 여기서 실제로 생성되는 개체인 $comFileSystemObject는 COM 개체를 래핑하고 인터페이스를 노출하는 .NET 개체입니다. 하지만 이 기사의 관점에서 보면 이는 문제가 되지 않습니다.

Windows PowerShell 실제 적용

지금까지 Windows PowerShell에서 COM에 액세스하는 방법을 살펴보았으므로 이제 그룹 정책에 대해 설명하겠습니다. 이 예제는 Windows PowerShell에서 GPMC API를 사용하는 방법의 개념을 보여 주기 위한 작은 코드 조각입니다. 이 기사와 관련된 코드 다운로드(technetmagazine.com/code07.aspx)에서 그룹 정책을 관리하는 전체 Window PowerShell 함수 집합을 다운로드할 수 있습니다. 그림 1은 이 다운로드에 포함된 함수 목록입니다.

Figure 1 다운로드에 포함된 사용자 지정 함수

함수 이름 ¼³¸í
BackupAllGpos 도메인의 모든 GPO를 백업합니다.
BackupGpo 단일 GPO를 백업합니다.
RestoreAllGpos 백업에 있는 모든 GPO를 도메인으로 복원합니다.
RestoreGpo 백업에서 단일 GPO를 복원합니다.
GetAllBackedUpGpos 지정된 경로에서 최신 버전의 GPO 백업을 가져옵니다.
CopyGpo 한 GPO의 설정을 다른 GPO로 복사합니다.
CreateGpo 빈 GPO를 새로 만듭니다.
DeleteGpo GPO를 삭제합니다.
FindDisabledGpos 사용자 및 컴퓨터 부분 모두가 비활성화된 모든 GPO를 반환합니다.
FindUnlinkedGpos 링크가 없는 모든 GPO를 반환합니다.
CreateReportForGpo 도메인에 있는 단일 GPO에 대한 XML 보고서를 생성합니다.
CreateReportForAllGpos 도메인에 있는 각 GPO별로 별도의 XML 보고서를 생성합니다.
GetGpoByNameOrID 표시 이름이나 GPO ID로 GPO를 찾습니다.
GetBackupByNameOrId 표시 이름이나 GPO ID로 GPO 백업을 찾습니다.
GetAllGposInDomain 도메인에 있는 모든 GPO를 반환합니다.

이 부분을 읽으면서 원하는 경우 Windows PowerShell 명령줄을 시작하고 명령을 입력해 보십시오. 다만 일부 명령은 이전 명령에 종속된다는 점을 유의하십시오. 즉, 처음에 만든 개체 일부를 나중에 사용하므로 동일한 Windows PowerShell 세션을 유지해야 합니다. 세션을 닫으면 모든 명령을 처음부터 다시 입력해야 합니다.

이제 Windows PowerShell을 사용하여 새 GPO를 만들어 보겠습니다. Microsoft의 그룹 정책 팀은 GPMC에서 완전하게 작동하는 여러 VBScript 샘플을 제공하므로 이를 이용하여 작업을 간단하게 수행할 수 있습니다. 이 샘플은 %ProgramFiles%\GPMC\Scripts 디렉터리에 있으며 여기에는 GPMC API 문서가 포함된 gpmc.chm이라는 파일도 있습니다. 이제 CreateGPO.wsf 스크립트를 분석해 보겠습니다.

윗부분에는 다음 줄이 있습니다.

Dim GPM
Set GPM = CreateObject("GPMgmt.GPM")

이 줄은 대부분의 GPMC 기능에 액세스할 수 있도록 해주는 GPMgmt.GPM 클래스를 인스턴스화하기 때문에 그룹 정책 관리 세션이나 스크립트의 시작점입니다. 이 작업을 Windows PowerShell에서 수행하는 경우 코드는 다음과 같습니다.

$gpm = New-Object -ComObject GPMgmt.GPM

그룹 정책 관리의 시작점을 만들었으므로 다음 단계에서는 이것으로 무엇을 할 수 있는지 알아 봅니다. 대개는 이런 정보를 얻기 위해 문서를 찾아보겠지만 Windows PowerShell에는 매우 멋진 기능이 있습니다. 다음 줄을 입력하면 그림 2와 같은 출력이 나타납니다.

그림 2 Get-Member 출력

그림 2** Get-Member 출력 **(더 크게 보려면 이미지를 클릭하십시오.)

$gpm | gm

여기서 볼 수 있듯이 매우 편리한 기능입니다. 즉, Get-Member(또는 gm) cmdlet을 이용하면 개체가 지원하는 속성과 메서드를 명령줄에서 직접 확인할 수 있습니다. 물론 문서를 읽는 것과 같지는 않지만 정확한 매개 변수 수, 정확한 이름 등을 기억하지 못하는 경우 이미 잘 알고 있는 개체를 쉽게 사용할 수 있습니다. 한 가지 중요한 사항은 GPMC 문서 노드 목록을 보는 경우 이러한 개체는 GPM 개체와 비슷하며 다른 모든 클래스 앞에는 문자 I가 접두사로 붙어 있다는 점입니다. 이는 COM의 내부 작동 방식 때문이며 여기서는 별로 문제가 되지 않습니다. 이는 네이티브 COM 코드를 작성하는 C++ 프로그래머를 위한 것이며 인터페이스와 해당 인터페이스를 구현하는 클래스 간의 차이를 표시합니다. 또한 GPMC API를 사용할 때는 GPMgmt.GPM이라는 개체만 이 방법으로 만들면 됩니다. 다른 개체는 모두 이 GPM 개체로 시작되는 메서드를 사용하여 생성됩니다.

계속해서 GPO를 만들어 보겠습니다.

그림 3을 보면 GPO를 만드는 것이 얼마나 간단한지 알 수 있습니다. 여기서는 GPO를 만들 수 없는 경우에 수행해야 할 작업 등과 같은 오류 처리를 포함하여 몇 가지 코드를 생략했으며 도메인 이름을 하드코드했지만 개념을 파악하는 데는 아무런 문제가 없습니다.

Figure 3 GPO 만들기

$gpmConstants = $gpm.GetConstants() 
# This is the GPMC way to retrieve all 
# constants
$gpmDomain =$gpm.GetDomain("Mydomain.local", "", $gpmConstants.UseAnyDC)
# Connects to the domain where the GPO should 
# be created, replace Mydomain.local with the 
# name of the domain to connect to.
$gpmNewGpo = $gpmDomain.CreateGPO() 
# Create the GPO
$gpmNewGpo.DisplayName = "My New Windows PowerShell GPO" 
# Set the name of the GPO

이제 GPO를 만드는 방법을 알았으므로 기존 GPO를 열겠습니다. 아직 도메인 $gpmDomain에 대한 참조가 있으므로 다음을 입력합니다.

$gpmExistingGpo = $gpmDomain.GetGPO(
  "{31B2F340-016D-11D2-945F-00C04FB984F9}") 
# Open an existing GPO based on its GUID, 
# in this case the Default Domain Policy.
$gpmExistingGpo.DisplayName 
# Show the display name of the GPO, it 
# should say Default Domain Policy
$gpmExistingGpo.GenerateReportToFile($gpmConstants.ReportHTML, ".\DefaultDomainPolicyReport.html"

이렇게 하면 기본 도메인 정책의 설정에 대한 전체 HTML 보고서를 얻을 수 있지만 GPO의 설정이 변경된 시점을 알 수 있도록 GPO의 마지막 수정 시점을 알려 주는 ModificationTime 등 원하는 속성과 메서드 또한 모두 사용할 수 있습니다.

이 기능은 매우 유용합니다. 컴퓨터가 이상하다고 불평하는 사용자들의 전화가 빗발치는 상황을 경험해봤을 것입니다. 이 상황의 원인이 GPO 설정의 변경, 추가 또는 삭제 때문이라는 의심은 가지만 어떤 GPO를 조사해야 할지 알아낼 단서가 없는 경우가 있습니다. 바로 이럴 때 Windows PowerShell을 사용하면 큰 도움이 됩니다. 그림 4에 나와 있는 스크립트를 Windows PowerShell 명령줄에 입력하면 지난 24시간 내에 변경된 모든 GPO를 알아낼 수 있습니다.

Figure 4 수정된 GPO 검색

$gpmSearchCriteria = $gpm.CreateSearchCriteria() 
# We want all GPOs so no search criteria will be specified
$gpmAllGpos = $gpmDomain.SearchGPOs($gpmSearchCriteria) 
# Find all GPOs in the domain
foreach ($gpmGpo in $gpmAllGpos)
{
if ($gpmGpo.ModificationTime -ge (get-date).AddDays(-1)) {$gpmGpo.DisplayName}
# Check if the GPO has been modified less than 24 hours from now 
}

여기서 크거나 같음을 의미하는 –ge 연산자에 주의하십시오. 다른 스크립팅 또는 프로그래밍 언어에서 < 및 > 연산자를 사용해 온 사용자에게는 생소해 보일 것입니다. 하지만 이러한 연산자는 출력을 파일로 리디렉션하는 경우와 같은 리디렉션에 사용되므로 Windows PowerShell에서 비교 연산자로 사용할 수 없습니다.

¿ä¾à

그림 5에 나와 있는 코드는 한 GPO의 설정을 다른 GPO로 복사하는 전체 스크립트입니다. 이제 이 새로운 기술을 그룹 정책에 활용하고 COM 개체를 사용하는 모든 COM 개체나 VBScript 코드를 다시 사용하는 방법을 이해했을 것입니다.

Figure 5 한 GPO의 설정을 다른 GPO로 복사

###########################################################################
# Function  : CopyGpo
# Description: Copies the settings in a GPO to another GPO
# Parameters : $sourceGpo     - The GPO name or GPO ID of the GPO to copy
#           : $sourceDomain   - The dns name, such as microsoft.com, of the domain where the original GPO is located
#           : $targetGpo      - The GPO name of the GPO to add
#           : $targetDomain   - The dns name, such as microsoft.com, of the domain where the copy should be put
#           : $migrationTable - The path to an optional Migration table to use when copying the GPO
# Returns   : N/A
# Dependencies: Uses GetGpoByNameOrID, found in article download
###########################################################################
function CopyGpo(
 [string] $sourceGpo=$(throw ‘$sourceGpo is required’),
 [string] $sourceDomain=$(throw ‘$sourceDomain is required’),
 [string] $targetGpo=$(throw ‘$targetGpo is required’),
 [string] $targetDomain=$(throw ‘$targetDomain is required’),
 [string] $migrationTable=$(""),
 [switch] $copyAcl)
{
 
 $gpm = New-Object -ComObject GPMgmt.GPM # Create the GPMC Main object
 $gpmConstants = $gpm.GetConstants() # Load the GPMC constants
 $gpmSourceDomain = $gpm.GetDomain($sourceDomain, "", $gpmConstants.UseAnyDC) # Connect to the domain passed 
                                                                              # using any DC
 $gpmSourceGpo = GetGpoByNameOrID $sourceGpo $gpmSourceDomain
 # Handle situations where no or multiple GPOs was found
 switch ($gpmSourceGpo.Count)
 {
   {$_ -eq 0} {throw ‘No GPO named $gpoName found’; return}
   {$_ -gt 1} {throw ‘More than one GPO named $gpoName found’; return} 
 }
 if ($migrationTable)
 {
   $gpmMigrationTable = $gpm.GetMigrationTable($migrationTable)
 }

 $gpmTargetDomain = $gpm.GetDomain($targetDomain, "", $gpmConstants.UseAnyDC) # Connect to the domain passed 
                                                                              # using any DC

 $copyFlags = 0
 if ($copyAcl)
 {
   $copyFlags = Constants.ProcessSecurity
 }
 $gpmResult = $gpmSourceGpo.CopyTo($copyFlags, $gpmTargetDomain, $targetGpo)
 [void] $gpmResult.OverallStatus
 
}

기존에 그룹 정책이 그랬던 것처럼 Windows PowerShell은 Windows 관리 환경에서 자연스러운 한 부분으로 자리잡을 것입니다. 하지만 수백만 줄의 VBScript를 마이그레이션하거나 유지해야 할 수 있으며 이 경우 다행히 이 자습서가 도움이 될 것입니다.

다운로드에 포함된 Windows PowerShell 함수를 포함하여 이전에는 VBScript를 사용했던 여러 분야와 그룹 정책 관리를 개선하는 데 사용할 수 있는 다양한 소스가 있습니다. 또한 TechNet 웹 사이트의 VBScript - Windows PowerShell 변환 가이드에서 VBScript에서 해당 기능을 알고 있는 경우 많이 수행하는 작업을 Windows PowerShell로 수행하는 방법에 대한 힌트를 볼 수 있습니다. 이 가이드는 microsoft.com/technet/scriptcenter/topics/winpsh/convert에서 확인할 수 있습니다.

또한 GPMC API는 완전히 문서화되어 있으며 microsoft.com/grouppolicy의 그룹 정책 사이트에서 이러한 정보를 다운로드할 수 있습니다.

마지막으로 아직도 Windows PowerShell을 설치하지 않았다면 지금 바로 설치하십시오. Windows PowerShell은 microsoft.com/powershell에서 다운로드할 수 있습니다. Windows PowerShell의 재미에 빠져 보십시오.

Thorbjörn Sjövold는 그룹 정책 기반 시스템 관리 및 보안 확장 제품 공급 업체인 Special Operations Software(www.specopssoft.com)의 설립자 겸 CTO입니다. 문의 사항이 있으면 thorbjorn.sjovold@specopssoft.com으로 연락하십시오.

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