Windows PowerShellより優れたインベントリ ツールを作成する

Don Jones

目次

オブジェクトの仕様は変わらない
柔軟な出力
柔軟な入力
エラーについて
関数に真の価値を提供する

前回のコラムでは、複数のリモート コンピュータからサービス パックのインベントリ情報を取得できる関数を作成しました。これは非常に役立つ実用的なツールですが、私はこのツールの開発に使用したプロセスについても興味を持ちました。今月のコラムでは、皆さんが独自の関数を作成する方法をより詳しく理解できるように、そのプロセスについてわかりやすく説明します。

前回のコラムで作成した関数は、次のとおりです。

Function Get-SPInventory { PROCESS { $wmi = Get-WmiObject Win32_OperatingSystem –comp $_ | Select CSName,BuildNumber, ServicePackMajorVersion Write-Output $wmi } }

この関数は、次のように使用します。

Get-Content c:\computernames.txt | Get-SPInventory

テキスト ファイル内で、1 行に 1 つのコンピュータ名が記述されている場合、この関数は適切に機能します。

この関数の大きな短所は、1 つの WMI (Windows Management Instrumentation) クラスからデータを返すという制限です。これでは、BIOS のシリアル番号も取得する必要がある場合に困ります。

オブジェクトの仕様は変わらない

問題は、先月作成した関数が、WMI から Win32_OperatingSystem クラスを取得し、単純にそのオブジェクトを出力していることです。他のすべてのオブジェクトと同様、Win32_OperatingSystem クラスが保持するのは、自身に含まれるデータのみです。つまり、Win32_OperatingSystem クラスに BIOS のシリアル番号が含まれることはありません。単純に Win32_OperatingSystem オブジェクトを出力するだけでは、シリアル番号を出力することはできません。

どれだけ WMI の中を探しても、サービス パック情報と BIOS のシリアル番号の両方を含む 1 つのオブジェクト クラスは見つかりません。そのため、この関数では 1 つの WMI クラスを単純に出力するわけにはいきません。この関数では、これから作成するカスタム オブジェクトを出力する必要があります。このカスタム オブジェクトには、必要なデータがすべて含まれます。

プロパティを含まない空のオブジェクトを新しく作成するには、次のコマンドを実行します。

$obj = New-Object PSObject

この新しいオブジェクトは $obj 変数に格納されます。この変数には、どのようなデータでも追加できます。追加したデータはすべて、関数の出力になります。

先月作成した関数内では、Win32_OperatingSystem クラスの情報を取得し、その情報を $wmi 変数に格納しました。この情報を使用するのは、$wmi のプロパティを参照するのと同じくらい簡単です。たとえば、BuildNumber プロパティを取得するには、次のコマンドを使用します。

$wmi.BuildNumber

カスタム オブジェクトにプロパティを追加するには、($obj 変数に格納されている) オブジェクトを Add-Member にパイプする必要があります。追加するプロパティの種類 (必ず NoteProperty を指定します)、追加するプロパティの名前、およびプロパティに指定する値を、次のように Add-Member に指示します。

$obj | Add-Member NoteProperty BuildNumber ($wmi.BuildNumber)

同様の方法で、コンピュータ システムの名前とサービス パックのバージョンも追加できます。以下にそのコマンドを示します。

$obj | Add-Member NotePropertyCSName ($wmi.CSName) $obj | Add-Member NotePropertySPVersion ($wmi.ServicePackMajorVersion)

これらのコマンドをまとめて、新しい関数を作成します (図 1 参照)。Write-Output の行を変更し、元の $wmi オブジェクトではなくカスタム オブジェクトを出力していることに注意してください。

図 1 カスタム オブジェクト

Function Get-SPInventory { PROCESS { $wmi = Get-WmiObject Win32_ OperatingSystem –comp $_ | Select CSName,BuildNumber,ServicePack MajorVersion $obj = New-Object PSObject $obj | Add-Member NoteProperty BuildNumber ($wmi.BuildNumber) $obj | Add-Member NoteProperty CSName ($wmi.CSName) $obj | Add-Member NoteProperty SPVersion ($wmi.ServicePackMajorVersion) Write-Output $obj } }

次は、BIOS のシリアル番号を取得する必要があります。Web 上で検索を行うと、SerialNumber プロパティを含む Win32_BIOS クラスが見つかります (図 2 はオンライン ドキュメント ページの一部を示しています)。このため、Win32_BIOS クラスに対してクエリを実行し、カスタム オブジェクトに SerialNumber プロパティを追加するだけで作業は完了します。変更後の関数は、図 3 のようになります。

fig02.gif

図 2 Win32_BIOS クラスのドキュメント ページ (画像をクリックすると拡大表示されます)

図 3 SerialNumber プロパティを含む関数

Function Get-SPInventory { PROCESS { $wmi = Get-WmiObject Win32_ OperatingSystem –comp $_ | Select CSName,BuildNumber,ServicePack MajorVersion $obj = New-Object PSObject $obj | Add-Member NoteProperty BuildNumber ($wmi.BuildNumber) $obj | Add-Member NoteProperty CSName ($wmi.CSName) $obj | Add-Member NoteProperty SPVersion ($wmi.ServicePackMajorVersion) $wmi = Get-WmiObject Win32_BIOS –comp $_ | Select SerialNumber $obj | Add-Member NoteProperty BIOSSerial ($wmi.SerialNumber) Write-Output $obj } }

柔軟な出力

私は、出力用のカスタム オブジェクトを使用して、この関数のさまざまな使用方法を考案しました。たとえば、Windows Server 2008 コンピュータのみを含めるには、次のコマンドを使用します。

Gc c:\computernames.txt | Get-SPInventory | Where { $_.BuildNumber –eq 6001 }

また、Service Pack 1 がインストールされていないすべての Windows Vista コンピュータが記述された HTML ファイルを作成するには、次のコマンドを使用します。

Gc c:\computernames.txt | Get-SPInventory | Where { $_.BuildNumber –eq 6000 } | ConvertTo-HTML | Out-File c:\VistaInventory.html

他にも、XML、CSV、テーブル、リスト、HTML、並べ替え、フィルタ処理、グループ化など、この関数の可能性は無限大です。Windows PowerShell に組み込まれているコマンドレットは、オブジェクトを操作するように設計されています。出力用のオブジェクトを作成すれば、追加作業を行うことなく、Windows PowerShell で可能なあらゆる処理を実行できます。

柔軟な入力

残念ながら、この関数の入力は、出力ほど柔軟性がありません。この原因の 1 つは、Windows PowerShell のバージョン 1 における関数の設計方法にあります。バージョン 2 ではスクリプト コマンドレットが導入され、それによって柔軟性が大幅に向上します。

この関数には、パイプラインの入力として単純な文字列オブジェクトを指定する必要があります。Get-Content コマンドを、Active Directory からすべてのコンピュータ名を取得する次のようなコマンドレットに置き換えた場合、この関数は正しく機能しません。

Get-QADComputer | Get-SPInventory

この場合、Get-QADComputer コマンド (quest.com/powershell で入手できる無償の Active Directory 管理用コマンドレット セットに含まれています) は、単純な文字列オブジェクトではなく、Name プロパティを含むオブジェクトを返します。このコマンドを正しく機能させるには、図 4 のように関数を変更する必要があります。変更箇所は赤色になっています。

図 4 最終的な関数

Function Get-SPInventory { PROCESS { $wmi = Get-WmiObject Win32_OperatingSystem –comp $_.Name | Select CSName,BuildNumber, ServicePackMajorVersion $obj = New-Object PSObject $obj | Add-Member NoteProperty BuildNumber ($wmi.BuildNumber) $obj | Add-Member NoteProperty CSName ($wmi.CSName) $obj | Add-Member NoteProperty SPVersion ($wmi.ServicePackMajorVersion) $wmi = Get-WmiObject Win32_BIOS –comp $_.Name | Select SerialNumber $obj | Add-Member NoteProperty BIOSSerial ($wmi.SerialNumber) Write-Output $obj } }

これで、パイプライン オブジェクト全体が –computerName パラメータに渡されなくなり、パイプライン オブジェクトの Name プロパティが関数内で処理されるようになります。これはわずかな変更ですが、柔軟性という点では非常に意味のある変更です。Get-QADComputer コマンドを使用すると、入力を特定の組織単位に含まれるコンピュータに制限したり、Active Directory の属性に基づくその他の条件に一致するコンピュータに制限したりできます。

エラーについて

この関数を使用すると、接続できないコンピュータや、接続するためのアクセス許可が与えられていないコンピュータに必ず遭遇します。現時点では、この関数は Windows PowerShell コンソール ウィンドウ内にエラー メッセージを赤いテキストで出力した後、次のコンピュータの処理を実行します。

もちろん、これこそが目的の動作かもしれませんが、エラー メッセージを表示したくない場合もあるでしょう。その動作は、関数の PROCESS スクリプト ブロックの先頭に次の 1 行を追加するだけで、簡単に実現できます。

$ErrorActionPreference = "SilentlyContinue"

ただし、より複雑な方法を使用して、アクセスできなかったコンピュータの名前が記録されたエラー ログを作成することもできます。この操作は、Windows PowerShell を使用して行うことができますが、その方法については来月説明します。

今月のコマンドレット: Export-Alias と Import-Alias

作成したカスタム エイリアスを共有したり、シェルが起動するたびにカスタム エイリアスを簡単に読み込んだりできる、すばらしい方法を紹介します。必要なエイリアスをすべて作成したら、次のコマンドを実行して、それらのエイリアスをファイルにエクスポートします。

Export-Alias c:\aliases.xml

その後、次のコマンドを実行して、エクスポートしたエイリアスを再びシェルに読み込みます。

Import-Alias c:\aliases.xml

上記の 2 つ目のコマンドを Windows PowerShell プロファイル スクリプトに追加すると、シェルが起動するたびにこのコマンドを実行できるようになります。また、エクスポートしたファイルをネットワーク共有上に配置すると、同時に作業している他の管理者がこのファイルを簡単に使用できるようになります。

関数に真の価値を提供する

ここまで皆さんは、この関数を .ps1 ファイルに入力した後、そのスクリプトを実行していたと思います。それはそれで良いのですが、ユーザビリティに関する問題がいくつか存在することも事実です。1 つの問題は、この関数を使用するときは必ず、そのスクリプト ファイルを開き、関数を呼び出す行を変更 (出力コマンド、並べ替えコマンド、または必要なその他のコマンドをすべて追加) した後、スクリプトを保存して実行する必要があることです。コマンドレットのように、関数自体をシェルのコンソール ウィンドウ内で直接使用できれば、作業は大幅に簡単になります。

これを実現する方法は 2 つあります。1 つ目の方法は、単純に関数を Windows PowerShell プロファイル スクリプトにコピーすることです。Windows PowerShell プロファイル スクリプトは、シェルが起動するたびに自動的に実行される (存在する場合) 4 つのスクリプト ファイルの 1 つです。これらのファイルの場所は、Windows PowerShell と共にインストールされるクイック スタート ガイドに記載されています。

通常、私は profile.ps1 というファイルを使用します。このファイルは、ドキュメント フォルダ (Windows XP コンピュータと Windows Server 2003 コンピュータの場合はマイ ドキュメント フォルダ) 内の WindowsPowerShell (空白なし) というフォルダに格納されます。関数をプロファイルに追加した後は、いつでもシェルのコマンド プロンプトからその関数を使用できます。

2 つ目は、スクリプトに関数のみを含め (その他のコードは含めません)、シェルにそのスクリプトをドット ソース形式で読み込む方法です。通常、スクリプトを実行すると、Windows PowerShell によってそのスクリプト用の新しいスコープが作成されます。関数の定義など、スクリプト内で発生する処理はすべて、そのスコープ内で実行されます。スクリプトの実行が完了すると、そのスコープは破棄され、スコープ内で発生したすべての処理の内容が失われます。

そのため、Get-SPInventory 関数のみを含むスクリプト ファイルを実行すると、新しいスコープが作成され、関数が定義された後、最終的にスコープが破棄され、関数が失われます。当然、あまり都合の良い動作とは言えません。

一方、ドット ソース形式でファイルを読み込むと、新しいスコープを作成せずにスクリプトを実行できます。MyFunction.ps1 というファイルに Get-SPInventory を追加した場合、以下のようにドット ソース形式でファイルを読み込むことができます。

PS C:\> . c:\functions\myfunction

ピリオドの後にスペースを入力し、その後にスクリプトのパスとファイル名を入力します。これにより、現在のスコープ内でスクリプトを実行することがシェルに指示されます。つまり、スクリプトの実行が完了しても、スクリプト内で発生した処理の内容は失われません。その結果、Get-SPInventory スクリプトがシェル内で定義され、このスクリプトをコマンドレットのように直接コマンド ラインから使用できるようになります。

Don Jones は、Concentrated Technology の共同創設者で、IT 関連の書籍を多数執筆しています。ConcentratedTech.com で、Don は、毎週 WindowsPowerShell に関するヒントを紹介しています。また、このサイトから、Don に連絡を取ることもできます。