Hey, Scripting Guy!帰ってきた WinRM

Microsoft Scripting Guys

Scripting Guys が Windows® Remote Management (WinRM) についての 2 部構成のコラムの執筆に取り掛かったとき、セキュリティという 1 つの大きな問題に直面しました。私たち Scripting Guys もハリー ポッター シリーズの最終章の販売に伴う問題については知っています。先行予約された本が、うっかり早く発送されて、正式な発売日の前に配達されてしまったという問題です (結局それは問題になったのでしょうか。もちろんそんなことはなく、出版社は「発売日まで本を読まないでください」とお願いしただけでした)。

ただし、話はここからです。この最終章の発売前に、この本とシリーズの結末が漏れていました (この本をまだ読んでいないのなら、ここで結末をお教えましょう。なんとハリー ポッターは魔法使いのようなものだったんです)。さらに、本の内容をスキャンしたものが、すぐにインターネットで入手可能になり、ときには著者 J・K・ローリングもまだ書いていないページさえありました。結局のところ、これはちょっとしたセキュリティの大失敗で、Scripting Guys は自分たちの身に同じようなことが起らないようにしようと心に決めました。とにかく、もしハリー ポッター シリーズの最終章の結末をばらしてやろうと固く決意したとした人が、Scripting Guys がお届けする 2 部構成の最終回の結末を入手するのに採る手段を考えてみてください。

さいわいにも、Scripting Guys は取り締りをしっかりして、秘密を守ることができました。確かに、このコラムの締め切りをとっくに過ぎるまで、実はこのシリーズの第 2 部をまだ書いていなかったというのがそもそもの原因です。けれども、このコラムを執筆している Scripting Guy は 8 月は休暇を取っていました。彼はマイクロソフトの社員の大多数と違って、休暇中には、コンピュータを使うことはおろか、決して見向きもしないのです。

そうですね。彼は休暇中でなくても、コンピュータを使うことはおろか、めったに見向きもしません。でも、それはまた別の話です。

とにかく、この 4 週間、多くの人が WinRM の冒険談がどんな結末を迎えるのか、心配でひっきりなしに寝返りを打って眠れなかったことでしょう。しかし、そんな眠れなかった長いつらい日々とも今日でお別れです。史上初、これが Scripting Guys がお届けする WinRM に関する 2 部構成コラムのエキサイティングな結末です。ええと、結末は、こちらの図 1 を参照してください。

Figure 1 結末

strComputer = "atl-fs-01.fabrikam.com"

Set objWRM = CreateObject("WSMan.Automation") Set objSession = objWRM.CreateSession("http://" & strComputer)

strDialect = "https://schemas.microsoft.com/wbem/wsman/1/WQL" strResource = "https://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/*" strFilter = "Select Name, DisplayName From Win32_Service Where State = 'Running'"

Set objResponse = objSession.Enumerate(strResource, strFilter, strDialect)

Do Until objResponse.AtEndOfStream strXML = objResponse.ReadItem

    Set objXMLDoc = CreateObject("Microsoft.XMLDom") objXMLDoc.async=False objXMLDoc.loadXML(strXML)

    Set objElement = objXMLDoc.documentElement Wscript.Echo "Name: " & objElement.ChildNodes(0).ChildNodes(0).NodeValue Wscript.Echo "Display Name: " &  objElement.ChildNodes(1).ChildNodes(0).NodeValue Wscript.Echo Loop

ええ、わかっています。この見事なプロットの展開には、背筋がゾクゾクしますね。objElement.ChildNodes(0).ChildNodes(0).NodeValue ですよ。だれが、このような結末を予想したでしょうか。もちろん、これはセキュリティ規制の一環ですが、Scripting Guys 本人たちでさえ、このシリーズの結末をまったく知らなかったわけですから。しかし、今からその秘密が解き明かされます。

話を進める前に、第 1 部を読むことができなかった方のために、このシリーズ全体の内容を簡単に振り返っておきましょう (第 1 部は technetmagazine.com/issues/2007/11/HeyScriptingGuy で参照できます)。第 1 部では、ファイアウォールが有効な場合でも、インターネット経由でのコンピュータの管理を簡略化する新しいテクノロジである WinRM をご紹介しました。このテクノロジは、Windows Server® 2003 R2、Windows Vista®、および Windows Server 2008 で導入されています。確かに Windows Management Instrumentation (WMI) を使用してコンピュータをリモートで管理することは可能ですが、WMI はリモート管理技術として分散 COM (DCOM) に依存しています。そのこと自体には問題はありませんが、多くのファイアウォールの既定の設定では、DCOM トラフィックがブロックされます。もちろん、適切なポートを開いて、DCOM トラフィックを許可することは可能です。ただし、DCOM トラフィックを許可すると、あらゆる悪質なトラフィックに対してもポートを開くことになってしまうことが懸念されるので、ほとんどのネットワーク管理者は、そのような設定にすることを好みません。

WinRM は、WS-Management プロトコルのマイクロソフト実装です。WS-Management プロトコルは、さまざまなベンダが提供するハードウェアやオペレーティング システムの相互運用を可能にする標準的なファイアウォールに対応した SOAP ベースのプロトコルです。というのは大袈裟な言い方ですが、WinRM により、HTTP や HTTPS などの標準的なインターネット プロトコルを使用してコンピュータをリモート管理できるようになります。

先月のコラムでもお知らせしたとおり、WinRM を使用すると、リモート コンピュータに接続したり、リモート コンピュータから WMI 情報を取得したりする作業が簡単に行えるようになります。では、WinRM は、完璧なテクノロジということでしょうか。いえ、そうとは言い切れません。既に説明したように、WinRM が呼び出し元のスクリプトにデータを返すときのデータ形式は XML です。言うまでもなく、XML は、特に XML に関して経験が浅いシステム管理者にとっては、解析して活用するのが少々困難な場合があります。そのため、WinRM には、返されたデータを読みやすい形式に変換する XSL 変換が同梱されています。

それはいいのですが、XSL 変換を使用すると、出力が常に次のような形式になってしまいます。

Win32_Service AcceptPause = false AcceptStop = true Caption = User Profile Service CheckPoint = 0 CreationClassName = Win32_Service

出力が常にコマンド ウィンドウに表示されるという点を除けば、これは必ずしも悪いことではありません。ただ、既定では、データを簡単にテキスト ファイルに保存したり、データベースや Microsoft® Excel® ワークシートに保存したりすることができず、単に画面に情報を表示することしかできないということです。これでは不十分です。

それに加えて、(ネットワーク トラフィックを減らす目的などで) WMI クラスの特定のプロパティだけを返すようにしたい場合、この問題は深刻なものになります。クラスのすべてのプロパティではなく、いくつかのプロパティだけを操作する場合は、次のような情報が出力されます。

XmlFragment DisplayName = Windows Event Log Name = EventLog

XmlFragment DisplayName = COM+ Event System Name = EventSystem

面白いとは思いますが、これまで見たことがある出力の中で最も美的センスに優れているとは言えません (特に出力結果の至るところに現れる XMLFragment という見出しは美しいものではありませんね)。

では、どうしたらよいのでしょう。返された XML データを解析して書式を設定するカスタム コードを記述したらよいのでしょうか。でも、それは、システム管理のスクリプト作成者がしそうなことではなさそうです。違いますか。

重要なことは他人任せにしない

WinRM から返される XML データをそのまま使うのは、想像以上に難しい作業であることがわかりました。今月は XML データを解析して書式を設定する簡単な方法を 1 つご紹介しましょう。この方法は決して XML を処理する唯一の方法ではありません。ここでは、XSLT 変換に頼る必要がないことを説明することを目的としているので、1 つの方法を紹介すればよいのです。基本的な考え方が理解できたら、WinRM データを使ってできることに際限はありません。

ただ、問題が 1 つあります。このコラムを与えられたスペースに収めるには、図 1 に示したスクリプトの大半の説明を省略しなくてはならないのです。しかし、スクリプトの最初の 3 分の 2 については先月のコラムで詳しく説明したので、たいした問題にはならないはずです。今月は、返された値を実際に処理しているスクリプトの最後の 3 分の 1 に重点を置いて説明します。

ご覧のとおり、今月の WinRM の冒険談のエピソードは WSMan.Automation オブジェクトのインスタンスを作成し、リモート コンピュータ (ここでは atl-fs-01.fabrikam.com) をクエリし、そのコンピュータで実行中のすべてのサービスについての情報を受け取った後についてです。つまり、返された XML データを読み込んで処理する Do Until ループがあるスクリプト行からということです。このループ処理は、読み込んで処理するものがなくなるまで繰り返されます (つまり、いかにもわかったようなことを言うと、このループ処理は XML ファイルの AtEndOfStream プロパティが True になるまで続くということです)。

ここで説明しているのは次のコード行で、今月のお話はここから始まります。

Do Until objResponse.AtEndOfStream

このループでは、まず、ReadItem メソッドを使用して、返された XML データの最初のセクションを読み取ります (WinRM を使ってサービス情報を取得しているので、この最初のセクションには、コレクション内の最初のサービスに関する全データが含まれています)。このデータ (繰り返しになりますが、XML 形式のデータです) を strXML という名前の変数に格納したら、Microsoft.XMLDom オブジェクトのインスタンスを作成します。要するに、次のコード行により空の XML ドキュメントが利用できるようになったわけです。

Set objXMLDoc = _ CreateObject("Microsoft.XMLDom")

空のドキュメントが用意できたら、すぐに Async プロパティの値を False に設定し、loadXML メソッドを呼び出します。

そうすると。え、何か言いましたか。なぜ Async プロパティの値を False に設定するのかですって。それになぜ loadXML メソッドを呼び出すのか、という質問ですか。自作自演とはいえ、よい質問ですよね。

まず、Async プロパティは、スクリプトが XML 情報を非同期でダウンロードするのを許可するかどうか指示するものです。このプロパティの値が True の場合、ダウンロードが始まると、ダウンロードが完了していなくても、すぐに制御がスクリプトに返されます。確かに、とてもよい処理のように思えます。しかし、残念ながら、スクリプトは必要な情報がすべて揃ったものとして、処理を続行します。必要な情報がすべて揃っていないのに処理が続行された場合は、困ったことになります。ですから Async プロパティの値は False に設定しておくのです。

注 : そうですね、スクリプトが早すぎる段階で終了しないように、定期的にダウンロードの状況を監視するコードを記述することは可能です。それで問題を解決することはできますが、単に Async プロパティの値を False にする方が断然簡単です。そうすると、スクリプトはダウンロードが完了するまでブロックされます。つまり、ダウンロードが始まると、スクリプトはダウンロードが終わるまで気長に他の処理を行うのを待つわけです。

loadXML メソッドの処理については、ほぼ名前のとおりです。このメソッドは、整形式の XML ドキュメント (またはドキュメントの一部) を空のドキュメントに読み込みます。後は、loadXML メソッドを呼び出し、strXML 変数をメソッドの唯一のパラメータとして渡すだけです。

objXMLDoc.loadXML(strXML)

最終結果を見てみましょう。WinRM データを仮想の XML ドキュメントに変換しました。ということは、標準的な XML メソッドを使用して、この仮想ドキュメントを解析できるということです。

それには、次のコード行を使用して、XML のルート要素のオブジェクト参照を作成する必要があります。

Set objElement = objXMLDoc.documentElement

これで、お楽しみの準備ができました (もちろん、皆さんが XML ファイルの解析が楽しいことだと思えばですが。私たち Scripting Guys にとって XML の解析は間違いなく楽しいことです)。

覚えているかもしれませんが、WMI Query Language (WQL) のクエリ (または WinRM 用語で言うと "フィルタ") は次のようになります。

strFilter = "Select Name, DisplayName " & _ "From Win32_Service Where State = 'Running'"

ご覧のとおり、Win32_Service クラスの Name と DisplayName という 2 つのプロパティを要求しています (また、戻り値を実行中のサービスに限定する Where 句を含めました。しかし、これについては特に気にする必要はありません)。要求したプロパティが 2 つだということは重要でしょうか。また、要求した順序は重要でしょうか。重要かもしれません。しかし、どうしたらそんなことがわかると言うのでしょうか。

おっしゃるとおりです。おそらく、このコラムの執筆者としては、本当はこのような質問には答えられるべきでしょう。それでかまいません。結局、どちらの質問に対する答えも「はい」だとわかりましたから。WQL クエリでプロパティを 2 つ要求したことは重要でしょうか。はい、何しろ、この 2 つのプロパティ値が返される値のすべてですから (クラスのすべてのプロパティ値を返す Select * From クエリを実行するのとは対照的です)。

それでは、その 2 つのプロパティの順序も重要でしょうか。当然です。プロパティ名は指定した順序で返されます。これは、各プロパティ値がルート要素の子ノード (またはセクション) として返されるので重要です。どのプロパティ値が最初の子ノードとして返されるのでしょうか。これは簡単な質問です。この場合、最初の子ノードは、WQL クエリで最初に指定されている Name プロパティになります。では、どのプロパティ値が 2 つ目の子ノードとして返されるのでしょうか。そうです。クエリで 2 番目に指定されている DisplayName プロパティです。

ちょっと待ってください。これは自力で解決できましたか。それともだれかが、このコラムの予定原稿の内容をばらしたのでしょうか。ふうむ...。

いずれにしても、これにより、Name プロパティの値をエコー バックしやすくなります。ここで必要な作業は、次のように、最初の ChildNodes (アイテム 0) コレクションの最初のアイテム (アイテム 0) の NodeValue を参照するだけです。

Wscript.Echo "Name: " & _ objElement.ChildNodes(0).ChildNodes(0).NodeValue

では、DisplayName プロパティの値はどうやって参照するのでしょうか。この場合は、次のように 2 番目の ChildNodes (アイテム 1) コレクションの最初のアイテムの NodeValue を参照します。

Wscript.Echo "Display Name: " & _ objElement.ChildNodes(1).ChildNodes(0).NodeValue 

また、WQL クエリで 3 番目のプロパティ (たとえば Status など) がある場合はどうしたらいいのでしょうか。その場合には、次のように、単に 3 番目の ChildNodes (アイテム 2) コレクションの最初のアイテムの NodeValue を参照するだけです。

Wscript.Echo "Status: " & _ objElement.ChildNodes(2).ChildNodes(0).NodeValue 

最後のプロパティに至るまで同様です。

スクリプトからの出力はどのようになるのでしょうか。これは、次のようになります。

Display Name: Windows Event Log Name: EventLog

Display Name: COM+ Event System Name: EventSystem

確かに WinRM の既定の出力とそれほど変わらないようにも見えます (あの見苦しい XmlFragment の見出しは取り除きましたが)。違うのは、個々のプロパティ値を取り扱うことで、既定の書式設定に縛られる必要がないこと (ここでは、DisplayName ではなく Display Name というラベルを使用していることに注意してください)、情報の出力先がコマンド ウィンドウに限定されないことです。

Excel ワークシートにデータを書き込む方がよいですか。それは簡単です。まず、次のコード ブロックを、WinRM スクリプトの Enumerate メソッドを呼び出す行の直後に挿入します (このコード ブロックでは新しい Excel ワークシートを作成して構成します)。

Set objExcel = _ CreateObject("Excel.Application") objExcel.Visible = True Set objWorkbook = objExcel.Workbooks.Add() Set objWorksheet = objWorkbook.Worksheets(1)

i = 2 objWorksheet.Cells(1,1) = "Name" objWorksheet.Cells(1,2) = "Display Name"

そして、もともとあった Do Until ループを図 2 の Do Until ループに置き替えます。試してみて結果をご覧ください。

Figure 2 新しい Do Until ループ

Do Until objResponse.AtEndOfStream strXML = objResponse.ReadItem

  Set objXMLDoc = CreateObject("Microsoft.XMLDom") objXMLDoc.async=False objXMLDoc.loadXML(strXML)

  Set objElement = objXMLDoc.documentElement objExcel.Cells(i, 1) = objElement.ChildNodes(0).ChildNodes(0).NodeValue objExcel.Cells(i, 2) = objElement.ChildNodes(1).ChildNodes(0).NodeValue i = i + 1 Loop

WinRM シリーズに第 3 部はあるのか

今月のコラムと先月のコラムの間に、WinRM に取り掛かる時間は十分あったことと思います。また、そうであったと願っています。WinRM は、コンピュータのリモート管理を大幅に簡略化する魅力的な新しいテクノロジです (また、作業を簡略化するだけでなく、安全性とセキュリティが確保されます)。この特徴により、皆さんの脳裏にはある疑問が浮かんでいることでしょう。WinRM の冒険談には第 3 部があるのか、という疑問です。

大変申し訳ありませんが、その情報はお教えできません。セキュリティ上の問題があるからではなく、単に Scripting Guys の相変わらずの企画立案と意思決定プロセスのせいで、WinRM の他の側面について何かを書くのかどうかについては、まったくわからないのです。でも、よく言いますよね、チャンネルはそのままでって。

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

Dr. Scripto はちょっとしたアクシデントに見舞われていました。彼が作成したスクリプトの 1 つを放置していたら (優秀なスクリプト作成者なら片付けているのでしょうが)、うっかりつまずいて、スクリプトの断片をあちこちにばらまいてしまったのです。変数、キーワード、記号などは、なんとかすべて見つかり、きちんとアルファベット順に並べるところまではできたのですが、スクリプトを 1 から組み立て直さなくてはなりません。この作業にはしばらく時間がかかると思いますが、彼は来月の TechNet Magazine の発行日までには、答えを用意しておくつもりのようです。**それまでに、よかったら、この一見ばらばらなスクリプト要素を 1 つの完全なスクリプトに並べ替えてみてください。健闘をお祈りします。

ヒント : 少しだけお教えしましょう。最終的なスクリプトは、指定の日付より古い日付のファイルをローカル コンピュータからすべて削除します。

ANSWER:

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

解答 : 「不器用な Doctor Scripto」、2007 年 12 月

Dr. Scripto はスクリプトを元どおりに組み立てることができました。次のスクリプトが、彼が再構築した、指定された日付より古い日付のファイルをローカル コンピュータからすべて削除するスクリプトです。

strDate = "20060102000000.000000+000"

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colFiles = objWMIService.ExecQuery("Select * From CIM_DataFile Where CreationDate < '" & strDate & "'")
For Each objFile in colFiles
    Wscript.Echo objFile.Name
Next

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

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