Hey, Scripting Guy!お決まりのごまかし

The Microsoft Scripting Guys

この記事で使用しているコードのダウンロード: HeyScriptingGuy2008_03.exe (150KB)

古いことわざ によると、どんなに小さなスズメでも、地面に落下すれば必ずそのことが天に知らされるそうです。皆さんがそこら中のスズメをたたき落としていると言っているわけではありませんよ (ただし、日曜の早朝から屋根の上に止まってけたたましく鳴く間抜けなカラスをなんとかしたい場合は、また別の話です [編集者注 : 編集者はカラスが好きなので、このコラムを執筆している Scripting Guy の言う "なんとかする" というのは "カラスの健康を維持する" という意味であると信じています])。いずれにせよ、私たちが転んだときにも必ずだれかが気にしてくれていて、どんなに小さくてちっぽけなことでも、どこかでだれかが見守ってくれているというのは元気づけられますね [編集者注 : それがただの編集者であっても]。

偶然ですが、このスズメの話はシステム管理スクリプトを作成する場合にも当てはまります。スクリプトの世界では、人々の注目はスクリプト界のお偉方に集中すると言ってもよいでしょう。Windows PowerShellTM、VBScript、WMI、ADSI を始め、FileSystemObject にもスポットが当たります。こういったテクノロジが非常に注目され、広く知られるのも無理はありません。これらを使用することで、役に立つ優れた操作を数多く実行できるのですから。しかし、だからと言ってスクリプトの作成者がこういったテクノロジしか使用できないというわけではありません。他にも多くのテクノロジがあります。

たとえば、Shell オブジェクトについて考えてみましょう。Shell オブジェクトは Windows PowerShell ほど有名ではなく、情報もこのテクノロジほど多くは提供されていませんが、重要性の高い便利なオブジェクトです。

遅かれ早かれ、おそらく皆さんは Shell オブジェクトを使用したシステム管理スクリプトの作成を依頼されるでしょう。そのとき、当然ある疑問が浮かびます。それは、Shell オブジェクトを使用して一体どのような操作を行うことができるかということです。実は、Shell オブジェクトを使用すると、多くのすばらしい操作を行うことができます。『Microsoft® Windows® 2000 スクリプト ガイド』(microsoft.com/technet/scriptcenter/guide) には、ディスク クォータの管理、ファイルをコピーまたは削除するときの進行状況バーの表示など、いくつかの操作についての説明が記載されています。今月のコラムでは、この他にも Shell オブジェクトを使用して行うことができるいくつかのすばらしい操作について説明します。おそらくこれらの操作は、Shell オブジェクトを使用するなどまったく想像もしていなかったような操作だと思います。

ファイルの最終更新日時を変更する

多くの皆さんは、上の見出しを見てこう思ったでしょう。「ちょっと待って。スクリプトを使ってファイルの最終更新日時を変更することなんてできない。少なくとも VBScript では不可能だ」と。1 つよろしいでしょうか。一体だれがそんなことを言ったんですか。

ああ、そうでした。私たち Scripting Guys でしたね。後からわかったのですが、これは間違いでした。ショックです。VBScript を使用してファイルの最終更新日時を変更することは可能です。しかも Shell オブジェクトを使用するだけで変更できます。

注 : なぜ Scripting Guys が間違いを犯したかは聞かないでください。そんなことは今さら言うまでもないでしょう。興味を持っていただきたいのは、一体どうやってこの解決手段にたどり着いたのかということです。

ファイルの最終更新日時を変更するには、次のようなスクリプトを使用します。

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 スクリプト ホストの Shell オブジェクト、つまり Wscript.Shell を混同しないようにしてください。今回使用する Shell オブジェクトは Windows の Shell オブジェクトです)。Shell オブジェクトのインスタンスを作成したら、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 時に設定されます (必要であれば、エクスプローラでフォルダ C:\Scripts を開いて確認してみてください)。

もちろん、次のように思っている皆さんもいるでしょう。「すばらしい。ファイルの最終更新日時を変更できるなんて。でも、一体なぜファイルの最終更新日時を変更する必要があるんだろう」と。多くの場合、最終更新日時はある種のバージョン管理システムとして使用されます。たとえば、あるスクリプトのコピーがあちこちに存在する場合、正式なバージョンを特定するための 1 つの方法として最終更新日時を確認することがあります。最終更新日時を確認することで、そのスクリプトの特定のコピーが元のスクリプトから変更されているかどうかを見分けることができます。

注 : ええ、もちろんだれかが今説明したスクリプトを実行すれば、最終更新日時は変更できます。しかしどんな操作を行うためのコードでも署名を追加しておかなければ、だれかがなんらかの方法を見つけてシステムを破壊する可能性があります。このため、道徳的な同僚とスクリプトを共有していると仮定すれば、ここで紹介している方法を使用することには意味があります。

ある時点で、すべてのスクリプトの最終更新日時を更新および統一することが必要になる場合があります。その場合はどうすればよいでしょうか。すべてのスクリプトがフォルダ 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 時に変更します。

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

その後、ループの先頭に戻り、コレクション内の次のファイルに対して同じ処理を実行します。すべての作業が完了すると、フォルダ C:\Scripts の (隠しファイルを除く) すべてのファイルの最終更新日時がまったく同じものになります。簡単でしょう。VBScript を使用してファイルの最終更新日時を変更できないと言ったのはだれでしたっけ。

ああ、そうでした。私たちでしたね。

間違いと言えば

その昔 Scripting Guys が初めてシステム管理スクリプトを扱い始めたとき、NTFS ファイル システム内のファイルに関連付けられた詳細情報にアクセスする方法はありませんでした。たとえば、.wav ファイルを右クリックして [プロパティ] をクリックすると、図 1 のような情報が表示されます。

図 1 ファイルのプロパティの概要

図 1** ファイルのプロパティの概要 **

スクリプトを使用して、ビット レートやオーディオ サンプル サイズなどの値を取得するにはどうすればよいでしょうか。実はこの操作を行うことはできません。Shell オブジェクトを使用しなければ、という意味ですが (また、Windows デスクトップ サーチ 3.0 もダウンロードおよびインストールされている必要があります)。そのときから今までのどこかの時点で、ファイルのプロパティに関する詳細な情報を取得する機能が Shell オブジェクトに追加されましたが、Scripting Guys はなぜかそのことを見落としていました。その結果、実際には図 2 のようなコードを使用すれば可能であったにもかかわらず、皆さんには「スクリプトを使用して .wav ファイルのビット レートを確認することはできません」とお伝えしていました。

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 名前
1 サイズ
2 種類
3 更新日時
4 作成日時
5 アクセス日時
6 属性
7 状態
8 所有者
9 作成者
10 タイトル
11 表題
12 カテゴリ
13 ページ
14 コメント
15 著作権
16 アーティスト
17 アルバムのタイトル
18
19 トラック番号
20 ジャンル
21 長さ
22 ビット レート
23 保護
24 カメラのモデル
25 撮影日
26 大きさ
29 この回のタイトル
30 プログラムの説明
32 オーディオ サンプル サイズ
33 オーディオ サンプル レート

では、この For Next ループ内ではどのような処理を行うのでしょうか。まず次のコード行を実行します。

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

ここでは、GetDetailsOf メソッドを使用してプロパティ 0 の名前を取得します (ループが 0 から 33 まで実行されることを思い出してください)。その後、取得した名前を strHeader という名前の変数に格納し、次のコード行でアイテム 0 のプロパティ値を取得して、取得した情報を strValue という名前の変数に格納します。

strValue = _
  objFolder.GetDetailsOf(objFolderItem, i)

しくみがわかったでしょうか。アイテム 0 は偶然にも "名前" プロパティです。そのため、1 回目のループでは、"名前" が strHeader に格納されます。一方、1 つ目のファイルの実際の名前 (つまり、"名前" プロパティの値) は J0388563.wav です。そのため、1 回目のループでは、J0388563.wav が strValue に格納されます。こうして見てみると、そんなに複雑ではなさそうですね。

ご存知かもしれませんが、すべてのファイルの種類ですべての詳細プロパティがサポートされているわけではありません (たとえば、.jpg ファイルにアルバム タイトルやオーディオ サンプル レートに関する情報は含まれていません)。そのため、次のコード行で、返された値が空の文字列かどうかを確認します。

If strValue <> "" Then

値が空の文字列である場合は、ループの先頭に戻り、次の詳細プロパティに対して同じ処理を実行します。値が空の文字列ではない場合、次のようにプロパティの名前と値をエコー バックします。

Wscript.Echo strHeader & vbTab & strValue

最終的な結果はどうなったかと言うと、次のとおりです。

名前          j0388563.wav
サイズ        169 KB
種類          Wave サウンド
更新日時      1/19/2004 8:56 AM
作成日時      3/6/2006 2:02 PM
アクセス日時  12/3/2007 10:41 AM
属性          A
状態          オンライン
所有者        FABRIKAM\kenmyer
ビット レート          90kbps
オーディオ サンプル サイズ     4 ビット
オーディオ サンプル レート     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)

このスクリプトはどのような処理を行うのでしょうか。エクスプローラ内でファイルにマウス ポインタを合わせるとヒントが表示され、図 5 のような、ファイルに関する役立つ情報を確認できます。スクリプトを使用して、このヒントとして表示される情報を取得するにはどうすればよいでしょうか。そうです、たった今説明したスクリプトを使用します。

図 5 ヒントとして表示されるプロパティ

図 5** ヒントとして表示されるプロパティ **

アーティスト: Neil Young
アルバムのタイトル: Harvest
年: 1972
トラック番号: 1
長さ: 0:04:35
種類: Windows Media オーディオ ファイル
ビット レート: 256kbps
保護: はい
サイズ: 5.31 MB 

今月はこれで終わりです。では、最後にスズメについてもう 1 つお話ししましょう。小さなスズメを追いかける理由についてです。この世界では、スズメが家の中に入ることは縁起が悪いと考えられている地域があります。その理由は、その家に住んでいるだれかが命を落とすことを意味するためです (スズメがピアノに止まった場合、という地域もありますが、これは迷信ではなく単なる奇妙な話です)。しかし興味深いことに、スズメが家の中に入ると、その家に住んでいるだれかが結婚することを意味する地域もあります。どちらを信じればよいのか、困ったものですね。

Dr. Scripto のスクリプト パズル

パズルを解くスキルだけでなく、スクリプト作成スキルもテストする月に一度の課題です。

2008 年 3 月 : 暗号スクリプト

今月のパズルは暗号文です。つまり、それぞれの文字が異なる文字に置き換えられます (記号と数字はそのままです)。どの文字も必ず特定の文字に置き換えられます。たとえば、文字 a は必ず文字 b に置き換えられ、文字 b は必ず z に置き換えられます。次に、簡単な例を示します。

tdsjqu dfoufs

これを解読すると次のようになります。

script center

この場合は、それぞれの文字をアルファベットの 1 つ前の文字に置き換えることで解読できます。つまり、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 のスクリプト パズル

解答 : 暗号スクリプト (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

The Microsoft Scripting Guys は、マイクロソフトの仕事をしています、というよりもマイクロソフトにより雇われています。野球をプレイしたり監督したり観戦したり (または他のさまざまな活動を) しているのでない限り、彼らは TechNet スクリプト センターを運営しています。詳細については、www.scriptingguys.com を参照してください。

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; 許可なしに一部または全体を複製することは禁止されています.