Hey, Scripting Guy!Scripting Guy가 즐기는 셸 게임

Microsoft Scripting Guys

이 기사의 코드 다운로드: HeyScriptingGuy2008_03.exe (150KB)

옛 속담에 아무리 작은 참새라도 하느님 모르게 땅으로 떨어질 수는 없다는 말이 있습니다. 참새를 사냥하는 사람들이 들으라고 하는 이야기는 아닙니다. 뭐, 토요일 아침 7:00시부터 지붕에 앉아서 깍깍 울어대는 까마귀라면 좀 손봐줄 필요는 있겠죠. [Scripting Editor는 까마귀를 좋아합니다. 그리고 이 칼럼을 쓴 Scripting Guy의 "손봐준다"는 표현은 "건강에 이상이 없는지 확인한다"는 의미라고 확신합니다.-편집자] 물론, 나락으로 떨어질 때, 아무리 체구가 작고 외모가 초라하더라도 그런 우리를 지켜보고 돌보는 누군가가 있다는 사실은 큰 위안이 됩니다. [그게 그저 이 편집자라 할지라도 말입니다.-편집자]

이는 참새뿐만 아니라 시스템 관리 스크립팅에 있어서도 마찬가지입니다. 스크립팅의 세계는 Windows PowerShellTM, VBScript, WMI, ADSI는 물론 FileSystemObject까지, 몇 가지 주요 스크립팅 기술을 중심으로 돌아간다고 해도 과언이 아닙니다. 멋진 작업과 유용한 기능을 제공하는 이러한 기술에 열광하고 너도나도 사용하는 현상이 이해되지 않는 바는 아니지만, 스크립트 작성자가 꼭 이러한 기술만 사용해야 하는 것은 아닙니다. 선택의 폭이 이보다는 훨씬 넓다는 말이죠.

셸 개체를 예로 들어봅시다. 셸 개체가 Windows PowerShell만큼 잘 알려지거나 널리 사용되고 있는 것은 아니지만, 그래도 중요하고 유용한 기술임에는 틀림없습니다.

조만간에 사람들이 셸 개체를 사용하는 시스템 관리 스크립트를 작성해달라고 부탁해올 날이 분명 올 겁니다. 그렇다면 대체 셸 개체로 무엇을 할 수 있는 것일까요? 사실 디스크 할당량을 관리하고 파일을 복사 또는 이동할 때 진행률 표시줄을 표시하는 등 셸 개체로 구현할 수 있는 멋진 기능은 무궁무진합니다. Microsoft® Windows® 2000 스크립팅 가이드(microsoft.com/technet/scriptcenter/guide)에도 그 중 몇 가지가 소개되어 있습니다. 이번 달 칼럼에서는 셸 개체로 구현할 수 있는 다른 멋진 기능을 살펴보도록 하겠습니다. 아마 셸 개체만으로 이런 기능을 구현할 수 있다는 사실에 놀라실 겁니다.

마지막으로 파일을 수정한 날짜 변경

이 소제목을 보고 아마 "잠깐! 파일의 마지막으로 수정한 날짜는 변경할 수 없잖아? 적어도 VBScript로는 불가능한데"라고 생각하는 사람이 많을 겁니다. 그런 분들에게 우리가 해줄 수 있는 말은 "대체 무슨 근거로 그렇게 생각하게 되었냐"입니다.

아, 사실 Scripting Guy도 예전에는 같은 생각을 가지고 있었습니다. 그런데 우리 생각이 틀린 것 같습니다. 놀라지 마십시오. VBScript로도 파일의 마지막 수정 날짜를 변경할 수 있습니다. 그리고 그렇게 하려면 셸 개체가 필요합니다.

한 가지 말씀 드리자면, Scripting Guy가 어떻게 이러한 사실을 모르고 있었냐는 말은 하지 마십시오. 지금까지는 너무 명백한 사실이었으니까요. 그보다 앞으로 이런 실수를 하지 않으려면 어떻게 해야 할지를 고민하는 것이 낫습니다.

자, 그럼 파일의 마지막 수정 날짜를 변경하려면 어떻게 해야 할까요? 다음 스크립트만 사용하면 됩니다.

Set objShell = _
  CreateObject("Shell.Application")

Set objFolder = _
  objShell.NameSpace("C:\Scripts")
Set objFolderItem = _
  objFolder.ParseName("Dr_Scripto.jpg")

objFolderItem.ModifyDate = _
  "01/01/2008 8:00:00 AM"

보다시피 특별히 복잡한 것은 없습니다. 먼저 Shell.Application 개체의 인스턴스를 만듭니다. 한 가지 주의할 것은 Shell.Application과 Windows 스크립트 호스트 셸 개체인 Wscript.Shell을 혼동해서는 안 됩니다. 여기서 사용하는 셸 개체는 Windows 셸 개체입니다. 셸 개체의 인스턴스가 실행되면 Namespace 메서드를 사용하여 C:\Scripts 폴더에 바인딩한 다음 ParseName이라는 이상한 이름의 메서드로 해당 폴더의 지정된 파일에 바인딩합니다. 이 경우에는 파일 이름이 Dr_Scripto.jpg인 JPEG 이미지에 바인딩합니다.

Set objFolderItem = _
  objFolder.ParseName("Dr_Scripto.jpg")

파일 자체에 바인딩한 후에는 어떻게 해야 할까요? 간단합니다. ModifyDate 속성에 새 날짜와 시간을 할당하면 됩니다.

objFolderItem.ModifyDate = _
  "01/01/2008 8:00:00 AM"

이제 다 끝났습니다.

그렇습니다. 너무 멋지죠. 이 스크립트를 실행하면 파일의 마지막 수정 날짜가 2008년 1월 1일 오전 8:00로 설정됩니다. 원한다면 Windows 탐색기에서 C:\Scripts 폴더를 열어 직접 확인해 보십시오.

물론 여러분 중에 "파일의 마지막 수정 날짜를 변경할 수 있다니 멋지긴 한데, 파일의 마지막 수정 날짜를 굳이 변경해야 할 이유가 있을까?"라고 생각하는 분도 있을 겁니다. 마지막 수정 날짜를 일종의 버전 제어 시스템으로 사용하는 경우가 많습니다. 일례로, 스크립트의 여러 복사본을 이곳 저곳에 사용할 경우 마지막 수정 날짜를 확인하는 것도 공식 버전을 추적하는 한 방법이 될 수 있습니다. 마지막 수정 날짜를 통해 해당 스크립트의 특정 복사본이 원본 스크립트에서 수정되었는지 여부를 확인할 수 있습니다.

맞습니다. 누군가 앞서 살펴본 스크립트를 실행하여 마지막 수정 날짜를 변경할 수도 있습니다. 하지만 코드 서명이 조금만 부족해도 이렇게 시스템을 조작하는 문제는 언제든지 발생할 수 있습니다. 스크립트를 공유하는 동료들이 모두 선하다는 전제하에 이 방법은 충분히 유용하다고 할 수 있습니다.

모든 스크립트의 마지막 수정 날짜를 업데이트하고 표준화해야 하는 경우도 있습니다. 이렇게 하려면 어떻게 해야 할까요? C:\Scripts 폴더에 모든 스크립트가 있다면 다음 코드만 실행하면 됩니다.

Set objShell = _
  CreateObject("Shell.Application")

Set objFolder = _
  objShell.NameSpace("C:\Scripts")
Set colItems = objFolder.Items

For Each objItem In colItems
    objItem.ModifyDate  = _
      "01/01/2008 8:00:00 AM"
Next

보다시피 이 스크립트는 앞서 소개한 첫 번째 스크립트와 시작 부분이 매우 비슷합니다. 하지만 여기서는 C:\Scripts 폴더에 바인딩한 후에 ParseName을 사용하여 해당 폴더의 개별 파일에 바인딩하는 대신 다음 코드 줄로 해당 폴더에 있는 모든 파일의 컬렉션을 반환합니다.

Set colItems = objFolder.Items

컬렉션이 반환되면 For Each 루프를 설정하여 컬렉션의 모든 항목에 대해 루프를 실행할 수 있습니다. For Each 루프에는 컬렉션에 있는 첫 번째 파일의 ModifyDate 속성 값을 2008년 1월 1일 오전 8:00로 변경하는 다음 코드 줄을 사용합니다.

objItem.ModifyDate  = _
  "01/01/2008 8:00:00 AM"

다음으로 컬렉션의 다음 파일에 대해 루프를 실행하여 프로세스를 반복합니다. 루프 실행이 완료되면 C:\Scripts 폴더에 있는 모든 파일(숨김 파일 제외)의 마지막 수정 날짜가 같아집니다. 자, 얼마나 쉽습니까? VBScript로 파일의 마지막 수정 날짜를 변경할 수 없다고 하셨나요?

아, 여기서 여담을 한 가지 하고 넘어가야겠군요.

틀린다는 것에 대해...

Scripting Guy가 시스템 관리 스크립팅 작업을 처음 시작했던 당시에는 NTFS 파일 시스템의 파일에 연결된 모든 확장 파일 정보에 액세스할 방법이 없었습니다. 예를 들어 .wav 파일을 마우스 오른쪽 단추로 클릭하고 속성을 클릭하면 그림 1과 유사한 정보가 표시됩니다.

그림 1 파일 속성 요약

그림 1** 파일 속성 요약 **

스크립트를 사용하여 비트 전송률이나 오디오 샘플 크기 같은 값을 검색하려면 어떻게 해야 할까요? 대답은 "불가능하다"입니다. 셸 개체를 사용하지 않는다면 말이죠. Windows 데스크톱 검색 3.0을 다운로드하여 설치한 경우 이 소프트웨어를 사용할 수도 있습니다. 언제인가 파일에 대한 모든 확장 속성 정보를 검색하는 기능이 셸 개체에 추가되었지만 웬일인지 Scripting Guy는 그 사실을 간과했습니다. 그 결과 사람들에게 "스크립트를 사용해서 .wav 파일의 비트 전송률을 확인할 수는 없다"고 말하고 다니게 된 거죠. 그림 2의 코드를 보면 알 수 있듯이, 사실 가능한데도 말이죠.

Figure 2 스크립트를 사용하여 .wav 파일의 비트 전송률 확인

Set objShell = CreateObject("Shell.Application")

Set objFolder = objShell.NameSpace("C:\Scripts")
Set objFolderItem = objFolder.ParseName("J0388563.wav")

For i = 0 to 33
    strHeader = objFolder.GetDetailsOf(objFolder.Items, i)
    strValue = objFolder.GetDetailsOf(objFolderItem, i)
    If strValue <> "" Then
        Wscript.Echo strHeader & vbTab & strValue
    End If
Next

이 스크립트는 어떻게 작동하는지 살펴봅시다. 여기서도 시작은 첫 번째 스크립트와 동일합니다. 즉, Shell.Application 개체의 인스턴스를 만들고 C:\Scripts 폴더에 바인딩한 다음 ParseName 메서드를 사용하여 원하는 파일(이 예에서는 J0388563.wav)에 바인딩합니다.

지금부터는 다소 복잡합니다. 먼저 0에서 33까지 실행되는 For Next 루프를 설정합니다. 이렇게 설정하는 이유는 파일의 경우 0에서 33 사이의 인덱스 번호가 지정된 확장 속성이 31개까지 지원되기 때문입니다. 단, 27과 28 같은 몇 가지 번호는 유효한 인덱스 번호이기는 하지만 속성 값을 포함하지 않습니다. 파일과 관련해서 지원되는 속성은 그림 3에 나와 있습니다.

Figure 3 파일 속성

인덱스 속성
0 Name
1 Size
2 Type
3 Date Modified
4 Date Created
5 Date Accessed
6 Attributes
7 Status
8 Owner
9 Author
10 Title
11 Subject
12 Category
13 Pages
14 Comments
15 Copyright
16 Artist
17 Album Title
18 Year
19 Track Number
20 Genre
21 Duration
22 Bit Rate
23 Protected
24 Camera Model
25 Date Picture Taken
26 Dimensions
29 Episode Name
30 Program Description
32 Audio Sample Size
33 Audio Sample Rate

이 For Next 루프는 어떻게 동작할까요? 먼저 다음 코드 줄을 실행합니다.

strHeader = _
  objFolder.GetDetailsOf(objFolder.Items, i)

이 코드 줄은 GetDetailsOf 메서드를 사용하여 속성 0의 이름을 검색합니다. 0에서 33까지 루프가 실행된다는 점을 상기해 보십시오. 그런 다음 검색된 이름을 strHeader라는 변수에 저장하고 다음 코드 줄을 사용하여 항목 0의 속성 값을 검색한 후 반환된 정보를 strValue라는 변수에 저장합니다.

strValue = _
  objFolder.GetDetailsOf(objFolderItem, i)

어떻게 작동하는지 아시겠습니까? 여기서 항목 0은 Name 속성이므로 루프가 처음 실행되면 strHeader의 값이 Name이 됩니다. 반면 첫 번째 파일의 실제 이름, 즉 Name 속성의 값은 J0388563.wav입니다. 따라서 루프를 처음 실행할 때 strValue의 값이 J0388563.wav가 됩니다. 이렇게 설명하니까 별로 복잡하지 않죠?

알고 있겠지만 모든 파일 형식에서 모든 확장 속성이 지원되는 것은 아닙니다. 예를 들어 .jpg 파일에는 Album Title 또는 Audio Sample Rate 속성이 없습니다. 따라서 다음 코드 줄에서 반환된 값이 빈 문자열인지 여부를 확인합니다.

If strValue <> "" Then

값이 빈 문자열이면 루프의 맨 위로 건너뛰고 다음 확장 속성에 대해 프로세스를 반복합니다. 값이 빈 문자열이 아니면 다음과 같이 속성 이름과 값을 출력합니다.

Wscript.Echo strHeader & vbTab & strValue

결과는 어떻게 될까요? 다음과 유사한 결과가 출력됩니다.

Name    j0388563.wav
Size    169 KB
Type    Wave Sound
Date Modified   1/19/2004 8:56 AM
Date Created    3/6/2006 2:02 PM
Date Accessed   12/3/2007 10:41 AM
Attributes      A
Status  Online
Owner   FABRIKAM\kenmyer
Bit Rate        90kbps
Audio Sample Size       4 bit
Audio Sample Rate       11 kHz

훌륭하죠?

시간과 지면이 부족하기는 하지만 가장 관련성이 높은 속성 값을 반환하는 더 쉬운 방법을 찾는 독자를 위해 그림 4의 스크립트를 사용해 보도록 하겠습니다.

Figure 4 속성 값을 반환하는 손쉬운 방법

Const colInfoTip = -1

Set objShell = CreateObject("Shell.Application")

Set objFolder = objShell.NameSpace("C:\Scripts")
Set objFolderItem = _
  objFolder.ParseName("01. Out On The Weekend (Album Version).wma)")

Wscript.Echo objFolder.GetDetailsOf(objFolderItem, colInfoTip)

이 스크립트는 어떤 작업을 수행할까요? Windows 탐색기에서 파일 위로 마우스 포인터를 이동하면 그림 5의 정보와 같이 파일에 관한 유용한 정보를 제공하는 도구 설명이 나타납니다. 스크립트를 사용하여 도구 설명에 표시할 정보를 얻으려면 어떻게 해야 할까요? 그렇습니다. 방금 설명한 스크립트를 사용하면 됩니다.

그림 5 도구 설명에 표시되는 속성

그림 5** 도구 설명에 표시되는 속성 **

Artist: Neil Young
Album Title: Harvest
Year: 1972
Track Number: 1
Duration: 0:04:35
Type: Windows Media Audio file
Bit Rate: 256kbps
Protected: Yes
Size: 5.31 MB 

이번 달에 준비한 내용은 여기까지입니다. 아, 그리고 참새에 대해 한 가지 덧붙이자면, 세계 어떤 곳에서는 집에 참새가 들어오면 불운이 닥친다고 믿습니다. 왜냐고요? 집안의 누군가가 죽는다는 신호이기 때문입니다. 참새가 피아노에 앉는다면 확실합니다. 뭐, 미신이라고 하기에도 너무 이상하지만요. 그런데 다른 곳에서는 집에 참새가 들어오면 집안 식구 중 누군가가 결혼하게 된다고 믿습니다. 흠, 이것 또한 어쩌면 불행이라고 할 수도 있을지 모르겠군요.

Dr. Scripto의 Scripting Perplexer

매달 퍼즐 풀이 실력뿐만 아니라 스크립팅 기술까지 시험해 볼 수 있는 문제입니다.

2008년 3월: 수수께끼의 스크립트

이번 달 퍼즐은 암호입니다. 즉, 각각의 문자가 다른 문자로 대체되어 있습니다. 모든 기호와 숫자는 그대로입니다. 하나의 문자는 항상 대체된 같은 문자를 나타냅니다. 예를 들어 문자 a는 모두 문자 b로 바뀌고 모든 문자 b는 문자 z로 바뀌는 식입니다. 간단한 예를 들어 보겠습니다.

tdsjqu dfoufs

위의 암호는 다음과 같이 해독됩니다.

script center

이 예의 경우 각 문자를 영문자 순서상 바로 앞에 오는 문자로 바꾸어 해독하면 됩니다. 즉, t는 s가 되고 d는 c가 되는 식입니다.

실제 퍼즐은 이보다 좀더 무작위로 암호화되어 있습니다. 이 스크립트를 해독하면 Microsoft Excel®의 인스턴스를 열고, 새 스프레드시트를 추가하고, 스프레드시트의 첫 번째 열에 값을 4개 할당하고, 그 열을 오름차순으로 정렬하는 스크립트를 얻게 됩니다.

자, 이제 퍼즐에 도전해 보십시오.

퍼즐

kqyjs edfjkzyrlyg = 1
kqyjs edyq = 2
kqyjs edjqtstqmj = 2

jzs quwzekzd = ktzfszquwzks("zekzd.faadlkfslqy")
quwzekzd.iljludz = stcz

jzs quwmqtxuqqx = quwzekzd.mqtxuqqxj.frr
jzs quwmqtxjozzs = quwmqtxuqqx.mqtxjozzsj(1)

quwmqtxjozzs.kzddj(1, 1) = "kfs"
quwmqtxjozzs.kzddj(1, 2) = "rqg"
quwmqtxjozzs.kzddj(1, 3) = "ufs"
quwmqtxjozzs.kzddj(1, 4) = "faz"

jzs quwtfygz = quwzekzd.fkslizkzdd.zysltztqm
quwtfygz.jqts quwtfygz, edfjkzyrlyg, , , , , , edyq, , , edjqtstqmj

ANSWER:

Dr. Scripto의 Scripting Perplexer

답: 수수께끼의 스크립트, 2008년 3월

스크립트를 해독하는 비결은 다음과 같습니다.

Actual Letter   a   b   c   d   e   f   g   h   i   j   k
Coded Letter    f   u   k   r   z   b   g   o   l   w   x

Actual Letter   l   m   n   o   p   q   r   s   t   u   v   w   x   y   z
Coded Letter    d   n   y   q   a   h   t   j   s   c   i   m   e   v   p

Const xlAscending = 1
Const xlNo = 2
Const xlSortRows = 2

Set objExcel = CreateObject("Excel.Application")
objExcel.Visible = True

Set objWorkbook = objExcel.Workbooks.Add
Set objWorksheet = objWorkbook.Worksheets(1)

objWorksheet.Cells(1, 1) = "Cat"
objWorksheet.Cells(1, 2) = "Dog"
objWorksheet.Cells(1, 3) = "Bat"
objWorksheet.Cells(1, 4) = "Ape"

Set objRange = objExcel.ActiveCell.EntireRow
objRange.Sort objRange, xlAscending, , , , , , xlNo, , , xlSortRows

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

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