Doctor Scripto のスクリプト ショップ

Windows XP Service Pack のインベントリ作成 - 第 2 部

公開日: 2005年7月14日
Microsoft Scripting Guys

作業中の Doctor Scripto

Doctor Scripto のスクリプト ショップでは、読者の皆さんから寄せられる実際のシステム管理スクリプトに関する問題を取り上げて、それらを解決するためのスクリプトを開発します。スクリプト倉庫にあるサンプルのほとんどは、簡単な作業を 1 つ実行するだけですが、このコラムではそれとは対照的に、それらのサンプルを組み合わせて、より複雑なスクリプトを作成します。これらのスクリプトは、包括的で強固なものではないかもしれませんが、これらのスクリプトを通じて、再利用可能なコード モジュールからスクリプトを作成する方法、エラーやリターン コードを処理する方法、さまざまなソースから入出力を行う方法、複数のコンピュータに対して実行する方法など、実際のスクリプトの中で実行してみたいテクニックを紹介します。

このコラムやここで紹介するスクリプトが皆さんのお役に立つことを期待します。ご意見、ご要望のほか、皆さんが問題解決のために編み出した方法や、今後取り上げてほしい話題などがありましたら、ぜひお知らせください (英語のみ)。

このコラムは、3 部構成の第 2 部です。最初のコラム「Windows XP Service Pack のインベントリ作成 - 第 1 部」を先に読むことをお勧めします。

トピック

Windows XP Service Pack のインベントリ作成 - 第 2 部
ADSI を使用して Active Directory コンテナ内のコンピュータを列挙する
Active Directory の検索に ADO を使用する
プロシージャと変数スコープ
完成版
詳細情報

Windows XP Service Pack のインベントリ作成 - 第 2 部

重要な締め切りが迫っているときは、作戦指令室 (豪華なペントハウス スイートに備え付けのシャンパン クーラーがあるオリンピックサイズのジャグジー) に行けば、カスケード山脈を眺めながら、そのすべての意味について考えを巡らせている Scripting Guys を見つけることができます。私たちは視野の広い独創的な頭脳集団なので、リッチで有名なわけです (そして、なぜか金曜の朝はいつも無料ベーグルの列の先頭にいます)。

私たちの切り札の 1 つは委託することなので、通常、私たちに急がされて働き過ぎのアドバイザである Dr. Scripto に細かい作業は任せています。ご想像のとおり、Windows XP Service Pack 2 のインストールを遮断する期限が 4 月 12 日までであることを聞いた彼は、熱狂状態に突入しました。彼は、その日を代表的なメロドラマにちなんで "SP2-Day" と呼んでいます。彼は、仕事中に、ウィンストン チャーチルのまねをして、「われわれは海岸で、街路で、あらゆる所でスクリプトを書き続けよう」などと言いながら、噛み煙草をむしゃむしゃ噛んでいます。

いつものことですが、彼はちょっとばかり度が過ぎるところがあります。まず、Service Pack 2 はたやすく侵入できるわけではありません。Windows ファイアウォールのように、ワームやウイルスの実際の侵入を防ぎ、更新プログラムを適用してコンピュータを最新の状態に維持する有効な新機能を数多く提供しています。次に、4 月 12 日の期限とは、自動更新機能や Windows Update サービスを、Windows XP クライアントを最新の状態に維持するために使用している場合のみ関係してくる制限です。4 月 12 日が過ぎるまで Service Pack 2 を展開したくない場合は、自動更新機能のメリットは失われますが、準備ができるまで、更新を停止しておくことができます。

しかし、ともかく、クライアントの実態を把握したいと思います。そこで、私たちの任務は、Service Pack 2 にアップグレードされていないクライアントを特定し、その情報に基づいて対処できるように、すべての Windows XP クライアントのインベントリを取得するスクリプトを作成することになりました。前回のコラムでは、Active Directory ドメインに参加していないコンピュータ上で WMI を使用してこの任務を実行する方法について説明しました。今週は、Active Directory 環境で使用する 2 種類の方法について紹介します。

ADSI を使用して Active Directory コンテナ内のコンピュータを列挙する

Active Directory サービス インターフェイス (ADSI) を使用したことがあれば、私たちのスクリプトの最初の動作が理解できるでしょう。ADSI を使用したことがない人のために、これからスクリプトの動作について説明します。まず、LDAP プロバイダを使用して、特定の Active Directory コンテナにバインドし、返されたコンピュータ オブジェクトのコレクションだけをフィルタリングします。次に、フィルタリングしたコレクション内のコンピュータごとに OperatingSystemVersion 属性をチェックして、Windows XP が動作しているかどうか確認します。オペレーティング システム名を含む ADSI 属性ではなく、Windows XP のバージョン番号とビルド (5.1 (2600)) を返す属性を使用します。Windows XP は動作しているが Service Pack 2 がインストールされていないコンピュータの場合、スクリプトは OperatingSystemServicePack 属性から取得したバージョンとサービス パックを表示します。

IADsComputer

派手な出力、2 連キャブレター、2 気筒、炎のステッカーなどでこのスクリプトを飾りたてるつもりはありません。このスクリプトは、別の手法による完成版用に保存することになります。私たちは、古い友人である ADSI のことを愛していますが、ADSI は、このジョブを実行するための最良のテクノロジでありません。その理由は、間もなく明らかになります。

On Error Resume Next
strContainer = "CN=computers,DC=fabikam,DC=com"
Set colComputers = GetObject("LDAP://" & strContainer)
colComputers.Filter = Array("Computer")
For Each objComputer in colComputers
  If objComputer.operatingSystemVersion = "5.1 (2600)" And _
   Not (objComputer.operatingSystemServicePack = "Service Pack 2") Then
    strComputer = objComputer.CN
    Wscript.Echo
    Wscript.Echo strComputer
    Wscript.Echo String(Len(strComputer), "-")
    Wscript.Echo "  " & objComputer.OperatingSystemVersion
    Wscript.Echo "  " & objComputer.OperatingSystemServicePack
  End If
Next

fabrikam.com のコンピュータ コンテナにサブコンテナがない場合は、このスクリプトはすべてのコンピュータをチェックします。しかし、そのコンテナが、組織単位 (OU) であり、内部に複数のサブ OU を含む場合はどうでしょうか。このスクリプトは、コンピュータのサブコンテナを検索できないため、完全なリストを取得できません。ディレクトリ構造を再帰的にたどる関数を作成することは可能ですが、コードが装飾的になり過ぎて、時間をかけたわりに得るものがありません。再帰処理は、まったく別の事柄です。いわゆる "fish of another color" です。いやいや "horse of another color" が正しいでしょうか (VBScript と数時間格闘した後の脳のように混乱しています)。これについては、また別の機会に触れることにします。

Active Directory の検索に ADO を使用する

当面の作業では、もっとよい解決策があるため、ADSI を使用する必要はありません。ADO (ActiveX Data Objects) は、データベースと連動するスクリプティング テクノロジであり、ADO にとって Active Directory はデータベースの 1 つに過ぎません。最初、ADO コードについて手ごわい印象を受けるかもしれませんが、わかってしまえば、COM オートメーション ライブラリの愛らしいテディ ベアのように感じられます。ADO を使用すると、ディレクトリ階層の移動が比較的簡単に実現できます。1 つのプロパティを設定するだけで、コンテナのサブツリーの階層を順に降りていくように ADO クエリに指示することができます。そして、最も重要なことは、ディレクトリの規模が大きい場合、ADSI と比較して ADO の方がはるかに検索速度が速いということです。ある非公式のテストで、ADO を使用した検索の方が、約 100 倍速かったという結果が出ています。

このコラムのメイン スクリプトでは、ADO を使用して Active Directory ドメインまたはその他のコンテナ内のすべてのコンピュータに関する情報を検索します。ADO は、データベースと連動するように設計されているため、(LDAP 形式のクエリと同様に) SQL クエリを受け付けます。ただし、これらのクエリの中では、前回のスクリプトで使用したものと同じ ADsPath とその他の ADSI 属性を使用します。これは、ADO が ADSI と連動して動作するためです。

完全なスクリプトを作成する前に、同様の ADO コードを使用する短いバージョンのスクリプトを作成します。このスクリプトでは、Excel スプレッドシートではなく画面に出力するだけなので、ADO をどのように動作させるかに集中することができます。

通常、ADO を使用するときは、ADODB.Connection と ADODB.Command の 2 つのオブジェクトを作成することから始めます。ADODB.Connection オブジェクトに、接続するデータベースの種類を設定します。このスクリプトでは、ADO に Active Directory プロバイダに接続するように指示します。ADODB.Connection オブジェクトに代替の資格情報を渡すこともできますが、わかりやすくすることが目的のため、このスクリプトでは行いません。

次に、ADODB.Command オブジェクトに、データベースに対して実行するクエリの種類を設定します。ADODB.Command オブジェクトの Searchscope プロパティに代入する ADS_SCOPE_SUBTREE 定数を使用して、最上位コンテナとその下の階層に含まれるすべてのコンテナを検索するように ADO に指示します。代わりに、他の定数を使用して、指定したコンテナだけを検索したり、そのコンテナとそのすぐ下の子コンテナを検索したりすることもできます。

ADODB.Command オブジェクトの Page Size プロパティは、クエリが単一の ADO 動作でディレクトリから読み出すレコード数を示します。ページ サイズは設定する必要があります。それは、設定しないと、ADO が 1,000 レコード (既定) を返した時点でクエリの処理を停止するためです。ページ サイズを設定すれば、すべての該当するレコードが読み出されるまで、そのページ サイズ分のレコードが複数回返されます。おおよその目安として、ディレクトリの規模に応じて、100 ~ 1,000 の値を Page Size に設定します。

この最初の ADO スクリプト内のクエリは、Windows XP は動作しているが Service Pack 2 がインストールされていないコンピュータだけを検索します。AND ステートメントと AND NOT ステートメントによる修正を加えた WHERE 句を使用して、コンテナ内のオブジェクトをフィルタリングします。

"SELECT CN, operatingSystemVersion, operatingSystemServicePack " _
   & "FROM 'LDAP://DC=fabrikam,DC=com' " _
   & "WHERE objectCategory='computer' " _
   & "AND operatingSystemVersion = '5.1 (2600)' " _
   & "AND NOT operatingSystemServicePack = 'Service Pack 2'"

SQL クエリは、データベースを使用した処理や、WMI と共に使用した WQL クエリ内で目にしたことがあるでしょう。SQL クエリについて詳しく説明しませんが、ADO を使用して Active Directory を照会するときに留意すべきことがいくつかあります。SELECT ステートメントの後に Active Directory から読み出す属性を列挙していることに気付いたかもしれません。通常、WQL クエリでは、"*" を使用してクラスからすべての属性を読み出します。しかし、ADO 内の SQL クエリでは、"*" は ADsPath 属性だけを返すため、他の属性を取得するには、その属性を指定する必要があります。

ADODB

このスクリプトの最後の部分では、このクエリを実行するコマンドを発行します。このコマンドは、クエリの基準を満たす、コンテナとその子コンテナ内のすべてのオブジェクトのレコードセットを返します。Sort メソッドを使用して、レコードセットの共通名 (CN) のアルファベット順にオブジェクトを並べ替えてから、レコードセット内の最初のオブジェクトに移動します。次に、Do Until ループを使用して、レコードセットをループ処理します。次の繰り返しに移行する前に、各レコードの最後で MoveNext メソッドを使用して、レコードセットの EOF (End Of File) 属性が True (真) かどうかをチェックします。ADO は自動的にはこれらの処理を実行しません。それどころか、ADO が提供している機能を使用して、レコードセット内のカーソルの位置や移動を指示する必要があります。

ADO のスクリプティングに関する詳細な説明は、Scripting Guys Web キャスト「Poking Your Nose Into Active Directory」(英語情報) を参照してください (リンクは、このコラムの最後に示します)。

'List all computers in an AD domain running XP but not SP2 using ADO.

On Error Resume Next

Const ADS_SCOPE_SUBTREE = 2
strContainer = "DC=fabrikam,DC=com"

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = ("ADsDSOObject")
objConnection.Open "Active Directory Provider"
objCommand.ActiveConnection = objConnection
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.Properties("Page Size") = 1000
objCommand.CommandText = _
  "SELECT CN, operatingSystemVersion, operatingSystemServicePack " _
   & "FROM 'LDAP://" & strContainer & "' " _
   & "WHERE objectCategory='computer' " _
   & "AND operatingSystemVersion = '5.1 (2600)' " _
   & "AND NOT operatingSystemServicePack = 'Service Pack 2'"
Set objRecordSet = objCommand.Execute
objRecordSet.Sort = "CN"
objRecordSet.MoveFirst 
Do Until objRecordSet.EOF
  Wscript.Echo
  Wscript.Echo objRecordSet.Fields("CN").Value
  Wscript.Echo String(Len(objRecordSet.Fields("CN").Value), "-")
  Wscript.Echo "  " & objRecordSet.Fields("operatingSystemVersion").Value
  Wscript.Echo "  " & objRecordSet.Fields("operatingSystemServicePack").Value
  objRecordSet.MoveNext 
Loop

プロシージャと変数スコープ

最終的なスクリプトでは、前回のスクリプトで使用したものと非常によく似たコードを使用しますが、結果を Excel スプレッドシートに出力します。前回のコラムでは、スクリプトの機能の一部を複数のプロシージャに分割しました。今回は、スプレッドシートへの書き込みとエラー処理にサブルーチンを使用します。

オペレーティング システムのバージョンとサービス パックに応じて、コンピュータ名を複数の変数に分類するロジックを 1 つの関数にすることもできますが、そうしないことにしました。それは... 役に立つよりも問題を引き起こす可能性の方が高いからです。ショックを与えるつもりはありませんが、スクリプトをコンポーネントにどのように分割するかのぎりぎりの決定は、少なくとも Scripting Guys が行う場合は、科学よりも芸術に近いところがあります。

前回と今回のコラムで使用されている VBScript (およびほとんどのプログラミング言語) の機能で、サブルーチンと関数に関連しているものがあります。これまでグローバル変数と変数スコープという概念に遭遇したことがなければ、簡単に説明した方がいいかもしれません。

スコープの概念は、スクリプトでプロシージャを使用するときに登場します。各変数は、スクリプト内部にスコープを持っています。スクリプトのメイン ルーチンで宣言または初期化された変数が、グローバルなスコープを持つグローバル変数です。これは、グローバル変数の値の読み取りと変更がサブルーチンと関数で可能なことを意味します。私たちのスクリプトでは、グローバル変数名は "g_" で始めるという任意の約束事を使用しています。

しかし、サブルーチンまたは関数内だけで変数を使用する場合は、その変数のスコープはローカル、つまり、そのプロシージャに限定されます。スクリプトの本体とその他のプロシージャは、その変数の存在を知らないため、その変数にはアクセスできません。

スクリプトのコンポーネント間で変数を共有する 1 つの方法は、スクリプトの本体からプロシージャに、または 1 つのプロシージャから別のプロシージャに、パラメータとして渡す方法です。しかし、グローバル変数を使用すれば、プロシージャを呼び出す前にスクリプト本体内で、グローバル変数の宣言、初期化、または使用を簡単に行うことができます。その後は、自動的にプロシージャからグローバル変数へのアクセスが可能になります。

VBScript は、変数を使用する前に宣言する必要がない (スクリプトの最初で Option Explicit を指定する場合は除きます) ため、混乱するかもしれません。Dim キーワードを使用して変数を宣言することができますが、必ずしもその必要はありません。最初に宣言または初期化 (値を代入) せずに変数を使用しようとすると、VBScript は、その値をコンテキストに応じて、0 または空の文字列 ("") と見なします。

以下に、グローバル変数とローカル変数の違いを理解しているかどうかがはっきりわかる簡単な例を 2 つ示します。

'main body of script

Sub1
Sub2
WScript.Echo x + y

'procedures

Sub Sub1
  x = 2
End Sub

Sub Sub2
  y = 5
End Subs

出力 :

0

意に反して (少なくとも、まだスコープ設定の巧妙さが見抜けない人にとって)、このスクリプトは、x と y の合計値として 0 を表示します。これは、サブルーチンを呼び出す前にスクリプト本体で変数に値を代入していないために、VBScript が 0 と見なした x と y の値を Echo 出力しているからです。各プロシージャ内部の変数のスコープがローカルであるため、スクリプト本体は、Sub1 と Sub2 が x と y に別の値を代入したこと (たとえそのローカル変数が本体内の変数と同じ名前であっても) がわかりません。

対照的に、次のスクリプトでは、サブルーチンで使用する前に、スクリプトの本体内で明示的に x と y に値を代入しています。これによって、その変数にグローバルなスコープが設定されます。

'main body of script

x = 0
y = 0

Sub1
Sub2
WScript.Echo x + y

'procedures

Sub Sub1
  x = 2
End Sub

Sub Sub2
  y = 5
End Sub

出力 :

7

これで、サブルーチンから、スクリプトの本体内のグローバル変数 x と y にアクセスして、その値を変更することができます。このときの x + y の値は 7 です。

完成版

最後に、輝かしいスクリプトの完成版を示します。この完成版では、オペレーティング システムとサービス パックに基づいて Active Directory ドメイン内のすべてのコンピュータの名前を検索し、Excel スプレッドシートに出力します。

ローカル ネットワーク上でこのスクリプトを実行するには、ネットワークで Active Directory を使用し、かつローカル コンピュータ上で Excel を実行している必要があります。また、変数 strContainer に代入された値を、インベントリを作成するネットワーク上のコンテナの名前に変更する必要があります。さらに、ローカル コンピュータ上に C:\Scripts という名前のディレクトリを作成するか、strOutputFile 内のパスをそのコンピュータ上に存在するパスに変更する必要があります (その気があれば、スプレッドシートのファイル名を変更することもできます)。このスクリプトが情報を収集するだけであるとしても、一般に、スクリプトは、テスト ネットワーク上でテストしてから、実稼動のネットワーク上で実行する方が賢明だということを認識しておいてください。

規模の大きなディレクトリ上では、このスクリプトは処理に時間がかかる場合があります。ネットワーク速度、ディレクトリ構成、スクリプトを実行するワークステーションの処理能力、その他の要因によって異なりますが、1,000 台あたり 30 秒前後かかると考えられます。

このスクリプトでは、前回のスクリプトのように Windows XP は動作しているが Service Pack 2 がインストールされていないコンピュータだけを検索するのではなく、コンテナとそのサブコンテナ内のすべてのコンピュータを検索します。

このスクリプトに続けて、ADO を使用して名前とサービス パックで並べ替えることもできますが、別のテクニックを試すために、インストールされているオペレーティング システムとサービス パックに基づいて、コンピュータ名を分類し、カテゴリ別に変数を割り当て、各カテゴリ内のコンピュータ数をカウントすることで、コンピュータ名を並べ替えることにしました。Windows XP が動作しているコンピュータの場合は、サービス パックごとに別々の変数を使用しますが、Windows XP が動作していないコンピュータは、このシナリオでは関係ないため、1 つの変数の中にダンプします。最終的に、カテゴリ別のコンピュータ名が、スプレッドシート内でアルファベット順に並べ替えられます。

このスクリプトをネットワーク上のサービス パックの更新に関する定期的な進捗レポートに改造するには、VBScript の Date 関数の出力を通知する次のようなコードを使用して、スプレッドシートのファイル名に日付を動的に追加する方法があります。

strDate = Replace(Date, "/", "-")
strOutputFile "c:\scripts\xpsp-" & strDate & ".xls"

At.exe または Task Scheduler を使用して、このスクリプトを毎日 (または一定間隔で) 実行するように設定することができます (WMI スクリプトで Win32_ScheduledJob クラスを使用して、AT コマンドを設定することもできます)。

ちなみに、このスクリプトは、規模の大きなネットワーク上では処理に時間がかかる場合があります。ネットワーク速度、ディレクトリ構成、スクリプトを実行するワークステーションの処理能力、その他の要因によって異なりますが、1,000 台あたり 30 秒以上かかると考えられます。もちろん、ネットワークの Active Directory にアクセスするためのすべての権限を有する管理者の資格情報でスクリプトを実行する必要があります。

スクリプトをメイン ルーチンと 2 つのサブルーチンに分割しました。

  • メイン ルーチンは、定数とグローバル変数を初期化してから、ADO に接続し、コンテナ内のすべてのコンピュータ オブジェクトを求めてディレクトリを検索します。また、オブジェクトをループ処理しながら、オペレーティング システムとサービス パックに基づいてオブジェクト名を分類し、カテゴリ別のコンマで区切られたグローバル変数 (g_strSP2、g_strSP1、g_strSP0、および g_strNotXP) を割り当てて、カテゴリごとにその数をカウントします。それから、サブルーチンを呼び出して、結果をスプレッドシートに書き込みます。

    また、メイン ルーチンでは、ADO を使用して Active Directory プロバイダへの接続をオープンした後と、クエリを実行した後に、エラーをチェックします。エラーが発生した場合は、別のサブルーチンに分岐するエラー処理コードを呼び出します。

  • WriteSpreadsheet サブルーチンは、作成するスレッドシートのパスと名前を含む文字列をパラメータとして受け取ります。また、VBScript の CreateObject メソッドを呼び出して、プログラム識別子 (ProgID) の Excel.Application を渡すことによって Excel オブジェクト モデルに接続します。この呼び出しによって、スクリプトから Excel のすべての機能へのアクセスを可能にする新しい Excel オブジェクトが作成されます。

    Excel を使用して、最初に、ワークブック オブジェクトを Excel オブジェクトに追加して、その中へのデータの書き込みと書式設定を行います。それから、パラメータとして渡されたファイル名で、そのワークブックを保存します。

    このサブルーチン内のコードの大部分では、特定のセルへの見出しの書き込みや、特定のセルの書式設定を行います。また、グローバルなカウンタ変数 (g_int*) を加算して、カテゴリごとのコンピュータ数を表示します。さらに、コンピュータのリストを表示するために、VBScript の Split 関数を使用して、グローバルな文字列変数 (g_str*) をコンマ区切りの配列要素に変換します。この配列をループ処理して、エントリごとに行番号を増分しながら、配列内のコンピュータ名を列に書き込みます。Excel の 2 つの Range オブジェクト (1 つ目はデータ範囲、2 つ目は並べ替えキーです) を作成し、列ごとにコンピュータ名を並べ替えます。最初の Range オブジェクト上で Excel の Sort メソッドを呼び出して、2 番目の Range オブジェクトの並べ替えキーをパラメータとして渡します。

    最後に、g_strContainer に代入されたファイル名でスプレッドシートを保存します。このとき、同じ名前の既存ファイルは強制的に上書きされます。

  • HandleError サブルーチンは、エラーに関する情報とコンテキストに応じたカスタム エラー メッセージを含む、VBScript の Err オブジェクトをパラメータとして受け取ります。そして、Err オブジェクトからのエラー番号、ソース、および説明と共にカスタム メッセージを表示します。

以下にコードを示します。

'List all computers in an AD domain running XP by SP using ADO.
'Output to Excel spreadsheet.

On Error Resume Next

'Initialize constants and variables.
Const xlAscending = 1
Const xlYes = 1
Const ADS_SCOPE_SUBTREE = 2
g_strContainer = "dc=na,dc=fabrikam,dc=com"
strQuery = "SELECT CN, operatingSystemVersion, operatingSystemServicePack " _
 & "FROM 'LDAP://" & g_strContainer & "' WHERE objectCategory='computer'"
strOutputFile = "c:\scripts\xpsp.xls"

g_strSP2 = ""
g_strSP1 = ""
g_strSP0 = ""
g_strNotXP = ""
g_intSP2 = 0
g_intSP1 = 0
g_intSP0 = 0
g_intNotXP = 0

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = ("ADsDSOObject")
objConnection.Open "Active Directory Provider"
If Err <> 0 Then
  HandleError Err, "Unable to connect to AD Provider with ADO."
End If
objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.CommandText = strQuery
Set objRecordSet = objCommand.Execute
If Err <> 0 Then
  HandleError Err, "Unable to execute ADO query."
End If

WScript.Echo "Gathering data from Active Directory ..."

objRecordSet.MoveFirst 
Do Until objRecordSet.EOF
  If objRecordSet.Fields("operatingSystemVersion").Value = "5.1 (2600)" Then
    If objRecordSet.Fields("operatingSystemServicePack").Value = _
     "Service Pack 2" Then
      g_strSP2 = g_strSP2 & objRecordSet.Fields("CN").Value & ","
      g_intSP2 = g_intSP2 + 1
    ElseIf objRecordSet.Fields("operatingSystemServicePack").Value = _
     "Service Pack 1" Then
      g_strSP1 = g_strSP1 & objRecordSet.Fields("CN").Value & ","
      g_intSP1 = g_intSP1 + 1
    Else
      g_strSP0 = g_strSP0 & objRecordSet.Fields("CN").Value & ","
      g_intSP0 = g_intSP0 + 1
    End If
  Else
    g_strNotXP = g_strNotXP & objRecordSet.Fields("CN").Value & ","
    g_intNotXP = g_intNotXP + 1
  End If
  objRecordSet.MoveNext 
Loop 

If Err <> 0 Then
  HandleError Err, "Unable to gather data."
End If

WScript.Echo "Writing data to spreadsheet ..."

WriteSpreadsheet strOutputFile

WScript.Echo "Data written to " & strOutputFile

'******************************************************************************

'Write data to spreadsheet.
Sub WriteSpreadsheet(strFileName)

'On Error Resume Next

'Create spreadsheet and open workbook.
Set objExcel = CreateObject("Excel.Application")
objExcel.Workbooks.Add

'Write data to spreadsheet.
objExcel.Cells(1,1).Value = "Inventory of Windows XP Service Packs"
objExcel.Cells(1,1).Font.Bold = True
objExcel.Cells(1,1).Font.Size = 13
objExcel.Cells(2,1).Value = "Time: " & Now
objExcel.Cells(2,1).Font.Bold = True
objExcel.Cells(2,5).Value = "Container: " & g_strContainer
objExcel.Cells(2,5).Font.Bold = True
objExcel.Cells(3,1).Value = "Total Computers: "
objExcel.Cells(3,1).Font.Bold = True
objExcel.Cells(3,3).Value = g_intSP2 + g_intSP1 + g_intSP0 + g_intNotXP
objExcel.Cells(3,3).Font.Bold = True

objExcel.Cells(4,1).Value = "Computers Running Windows XP"
objExcel.Cells(4,1).Font.Bold = True
objExcel.Cells(4,1).Font.Size = 12
objExcel.Cells(5,1).Value = "Number"
objExcel.Cells(5,1).Font.Bold = True
objExcel.Cells(6,1).Value = g_intSP2 + g_intSP1 + g_intSP0

objExcel.Cells(8,1).Value = "Service Pack 2"
objExcel.Cells(8,1).Font.Bold = True
objExcel.Cells(8,1).Font.Size = 11
objExcel.Cells(9,1).Value = "Number"
objExcel.Cells(9,1).Font.Bold = True
objExcel.Cells(10,1).Value = g_intSP2
objExcel.Cells(9,2).Value = "Names"
objExcel.Cells(9,2).Font.Bold = True

arrSP2 = Split(g_strSP2, ",")
x = 10
For Each strSP2 In arrSP2
  objExcel.Cells(x,2).Value = strSP2
  x = x + 1
Next
Set objRange = objExcel.Range("B1")
objRange.Activate
Set objRange = objExcel.ActiveCell.EntireColumn
objRange.AutoFit()
Set objSortRange = objExcel.Range("B9:B" & x)
Set objSortKey = objExcel.Range("B10")
objSortRange.Sort objSortKey,xlAscending,,,,,,xlYes

objExcel.Cells(8,3).Value = "Service Pack 1"
objExcel.Cells(8,3).Font.Bold = True
objExcel.Cells(8,3).Font.Size = 11
objExcel.Cells(9,3).Value = "Number"
objExcel.Cells(9,3).Font.Bold = True
objExcel.Cells(10,3).Value = g_intSP1
objExcel.Cells(9,4).Value = "Names"
objExcel.Cells(9,4).Font.Bold = True

arrSP1 = Split(g_strSP1, ",")
x = 10
For Each strSP1 In arrSP1
  objExcel.Cells(x,4).Value = strSP1
  x = x + 1
Next
Set objRange = objExcel.Range("D1")
objRange.Activate
Set objRange = objExcel.ActiveCell.EntireColumn
objRange.AutoFit()
Set objSortRange = objExcel.Range("D9:D" & x)
Set objSortKey = objExcel.Range("D10")
objSortRange.Sort objSortKey,xlAscending,,,,,,xlYes

objExcel.Cells(8,5).Value = "No Service Pack"
objExcel.Cells(8,5).Font.Bold = True
objExcel.Cells(8,5).Font.Size = 11
objExcel.Cells(9,5).Value = "Number"
objExcel.Cells(9,5).Font.Bold = True
objExcel.Cells(10,5).Value = g_intSP0
objExcel.Cells(9,6).Value = "Names"
objExcel.Cells(9,6).Font.Bold = True

arrSP0 = Split(g_strSP0, ",")
x = 10
For Each strSP0 In arrSP0
  objExcel.Cells(x,6).Value = strSP0
  x = x + 1
Next
Set objRange = objExcel.Range("F1")
objRange.Activate
Set objRange = objExcel.ActiveCell.EntireColumn
objRange.AutoFit()
Set objSortRange = objExcel.Range("F9:F" & x)
Set objSortKey = objExcel.Range("F10")
objSortRange.Sort objSortKey,xlAscending,,,,,,xlYes

objExcel.Cells(4,7).Value = "Not Running Windows XP"
objExcel.Cells(4,7).Font.Bold = True
objExcel.Cells(4,7).Font.Size = 12
objExcel.Cells(5,7).Value = "Number"
objExcel.Cells(5,7).Font.Bold = True
objExcel.Cells(6,7).Value = g_intNotXP
objExcel.Cells(5,8).Value = "Names"
objExcel.Cells(5,8).Font.Bold = True

arrNotXP = Split(g_strNotXP, ",")
x = 6
For Each strNotXP In arrNotXP
  objExcel.Cells(x,8).Value = strNotXP
  x = x + 1
Next
Set objRange = objExcel.Range("H1")
objRange.Activate
Set objRange = objExcel.ActiveCell.EntireColumn
objRange.AutoFit()
Set objSortRange = objExcel.Range("H5:H" & x)
Set objSortKey = objExcel.Range("H6")
objSortRange.Sort objSortKey,xlAscending,,,,,,xlYes

'Move active cell back to A1.
Set objRange = objExcel.Range("A1")
objRange.Activate

'Force save and close spreadsheet.
objExcel.DisplayAlerts = False
Set objWorkbook = objExcel.ActiveWorkbook
objWorkbook.SaveAs strFileName
objWorkbook.Close
objExcel.Quit

End Sub

'******************************************************************************

'Handle errors.
Sub HandleError(Err, strMsg)

On Error Resume Next

WScript.Echo "  " & strMsg
WScript.Echo "    Error Number: " & Err.Number
WScript.Echo "    Source: " & Err.Source
WScript.Echo "    Description: " & Err.Description
WScript.Quit

End Sub

努力のかいもあって、コンテナとその子コンテナ内のすべての Windows XP クライアントをサービス パックで並べ替えたアルファベット順のリストを含む、すばらしいレポートが Excel スプレッドシード内に完成しました。さらに上司を感動させるために、見栄えよく書式設定したり、実行すべき作業を強調表示したり、グラフやアニメーション、音楽などを追加することができます。私たちは、Excel の膨大な機能の調査を開始したばかりです。さらに掘り下げて調べるには、MSDN に掲載されている Scripting Guy の Greg Stemp による 2 つのコラムを参照してください (リンクは、このコラムの最後に示します)。

スプレッドシート

メモリ上には、スクリプトがまだ実行中であれば、Service Pack 2 がインストールされていないすべての Windows XP クライアントのリストが残っています。後は、2 つのグローバル変数 g_strSP1 と g_strSP0 を連結させるだけです。Service Pack 2 シリーズの最後となる (Doctor Scripto のスクリプト ショップの最後ではありません) 次回のコラムでは、この情報を使用してコンピュータに Service Pack 2 をインストールする方法について説明します。次回のコラムで紹介するコードは、このスクリプト (Active Directory 環境用) または前回のコラムのスクリプト (非 Active Directory ネットワーク用) と統合することができます。

詳細情報

ADSI

ADO

Excel

Windows XP Service Pack 2