PowerShell where-object の基本と応用サンプル集


PowerShell Where-Object の基本と応用サンプル集:データのフィルタリングをマスターする

はじめに:なぜ Where-Object が重要なのか

PowerShellは、システムの管理や自動化において強力なツールです。多くのコマンドレット(コマンド)は、オブジェクト指向のアプローチに基づき、データや情報を「オブジェクト」として出力します。例えば、Get-Process コマンドはシステムで実行中の各プロセスをオブジェクトとして出力し、Get-ChildItem コマンドはファイルやディレクトリをオブジェクトとして出力します。

これらのオブジェクトは、それぞれが持つ様々な「プロパティ」(属性)や「メソッド」(操作)を持っています。しかし、多くの場合、取得した全てのデータが必要なわけではありません。特定の条件を満たすオブジェクトだけを選択したい、つまり「フィルタリング」したいという要求が頻繁に発生します。

ここで登場するのが Where-Object コマンドレットです。Where-Object は、パイプラインを通じて流れてくる各オブジェクトに対して、指定された「条件」を評価し、その条件を満たすオブジェクトだけを次のコマンドレットや出力に渡す役割を果たします。これは、SQLデータベースにおける WHERE 句や、様々なプログラミング言語におけるフィルタリング機能に相当します。

Where-Object を効果的に使うことは、PowerShellスクリプトの効率性、可読性、そして目的に合った正確なデータ処理を実現するために不可欠です。この記事では、Where-Object の基本的な使い方から、論理演算子、ワイルドカード、正規表現、そしてスクリプトブロックを使った高度なフィルタリング方法まで、豊富なサンプルコードを交えながら詳細に解説します。

Where-Object の基本構文とプロパティへのアクセス

Where-Object の最も基本的な使い方は、オブジェクトが持つプロパティの値に基づいてフィルタリングを行うことです。構文は以下のようになります。

powershell
<コマンドレット> | Where-Object {-Property <プロパティ名> -Operator <演算子> -Value <値>}

または、より一般的なエイリアスである ? を使って記述することも多いです。

powershell
<コマンドレット> | ? {-Property <プロパティ名> -Operator <演算子> -Value <値>}

しかし、PowerShell 3.0以降では、この構文はさらに簡潔に書くことができます。スクリプトブロック {} の中に直接条件式を記述する方法です。こちらの方が直感的でよく使われます。

powershell
<コマンドレット> | Where-Object { $_.<プロパティ名> <演算子> <値> }

またはエイリアスで:

powershell
<コマンドレット> | ? { $_.<プロパティ名> <演算子> <値> }

この構文では、$_ という特別な自動変数を使用します。$_ はパイプラインを流れてくる「現在のオブジェクト」を表します。$_.<プロパティ名> は、現在のオブジェクトの特定のプロパティにアクセスすることを意味します。

<演算子> の部分には、比較演算子が入ります。代表的なものをいくつか紹介します。

演算子 説明
-eq 等しい $_.Status -eq 'Running'
-ne 等しくない $_.Status -ne 'Stopped'
-gt より大きい $_.ID -gt 1000
-ge より大きいか等しい $_.CPU -ge 50
-lt より小さい $_.Memory -lt 10MB
-le より小さいか等しい $_.Priority -le 5
-like ワイルドカードに一致 $_.Name -like 'svchost*'
-notlike ワイルドカードに不一致 $_.Name -notlike '*-test'
-match 正規表現に一致 $_.Path -match '^\w+:\\'
-notmatch 正規表現に不一致 $_.Path -notmatch '\.tmp$'
-contains 配列に指定要素を含む $_.Tags -contains 'Web'
-notcontains 配列に指定要素を含まない $_.Tags -notcontains 'DB'
-in 値が指定配列に含まれる $_.Status -in @('Running', 'Stopped')
-notin 値が指定配列に含まれない $_.ID -notin @(0, 4)
-is オブジェクトが指定型である $_.BaseObject -is [System.IO.DirectoryInfo]
-isnot オブジェクトが指定型でない $_.BaseObject -isnot [System.IO.FileInfo]

これらの演算子は大文字・小文字を区別しません。大文字・小文字を区別したい場合は、先頭に c を付けます (例: -ceq, -clike, -cmatch)。

基本的なフィルタリングの例

例1:特定の名前のサービスを検索する

システムで実行中のサービスの中から、「BITS」という名前のサービスを検索します。

powershell
Get-Service | Where-Object { $_.Name -eq 'BITS' }

解説:
1. Get-Service がシステム上の全てのサービスオブジェクトを取得します。
2. パイプライン | を通って、各サービスオブジェクトが Where-Object に渡されます。
3. Where-Object は、渡されてきた各オブジェクト($_)に対して { $_.Name -eq 'BITS' } という条件式を評価します。
4. $_.Name は現在のサービスオブジェクトの Name プロパティの値を取得します。
5. -eq 'BITS' は、その値が文字列 'BITS' と等しいかどうかを比較します。
6. 条件が真 (True) と評価されたサービスオブジェクトだけがパイプラインの後続(この場合は標準出力)に渡されます。

例2:実行中のサービスを検索する

ステータスが「Running」のサービスを一覧表示します。

powershell
Get-Service | Where-Object { $_.Status -eq 'Running' }

解説:
-eq 'Running' を使用して、Status プロパティの値が 'Running' であるオブジェクトをフィルタリングしています。

例3:特定のIDより大きいプロセスを検索する

プロセスID (ID) が1000より大きいプロセスを検索します。

powershell
Get-Process | Where-Object { $_.Id -gt 1000 }

解説:
-gt 1000 を使用して、Id プロパティの値が数値 1000 より大きいオブジェクトをフィルタリングしています。数値の比較には -gt, -ge, -lt, -le を使用します。

論理演算子 (-and, -or, -not) との組み合わせ

Where-Object では、-and-or-not といった論理演算子を使って複数の条件を組み合わせることができます。これにより、より複雑なフィルタリングが可能になります。

  • -and: 両方の条件が真である場合に真となります。
  • -or: いずれか一方または両方の条件が真である場合に真となります。
  • -not または !: 条件を否定します。

複数の条件を指定する例

例4:特定の名前で実行中のサービスを検索する

名前が「BITS」または「Spooler」であり、かつステータスが「Running」のサービスを検索します。

powershell
Get-Service | Where-Object { ($_.Name -eq 'BITS' -or $_.Name -eq 'Spooler') -and ($_.Status -eq 'Running') }

解説:
1. ($_.Name -eq 'BITS' -or $_.Name -eq 'Spooler'): サービス名が ‘BITS’ または ‘Spooler’ であるかどうかの条件です。-or で結ばれています。条件のグループ化のために括弧 () を使用しています。
2. ($_.Status -eq 'Running'): ステータスが ‘Running’ であるかどうかの条件です。こちらも括弧でグループ化しています。
3. 上記の2つの条件が -and で結ばれています。これにより、両方の条件が真であるオブジェクトのみが選択されます。

括弧は条件の評価順序を明確にするために非常に重要です。括弧がない場合、演算子の優先順位に従って評価されますが、意図しない結果になる可能性があるため、複雑な条件では積極的に括弧を使用することをお勧めします。

例5:特定のメモリ使用量より大きく、CPU使用率が0より大きいプロセスを検索する

ワーキングセットメモリ (WS) が50MBより大きく、かつCPU使用率 (CPU) が0より大きいプロセスを検索します。CPU プロパティは瞬間的な値なので、ここでは単に0より大きいことを確認します。

powershell
Get-Process | Where-Object { $_.WS -gt 50MB -and $_.CPU -gt 0 }

解説:
-and を使用して、WS プロパティと CPU プロパティに対する条件を組み合わせています。50MB のように単位付きで数値を指定することも可能です。PowerShellはこれを適切な数値に変換してくれます。

例6:実行中でないサービスを検索する

ステータスが「Running」ではないサービスを全て検索します。

“`powershell
Get-Service | Where-Object { -not ($_.Status -eq ‘Running’) }

またはよりシンプルに

Get-Service | Where-Object { $_.Status -ne ‘Running’ }
“`

解説:
-not 演算子は後続の条件式の結果を反転させます。($_.Status -eq 'Running') が真であれば -not は偽となり、偽であれば真となります。この例のように、単純な否定であれば -ne を使う方が直感的です。

ワイルドカードを使ったパターンマッチ (-like, -notlike)

文字列プロパティの値に対して、固定の文字列ではなくパターンでフィルタリングしたい場合があります。このような場合に便利なのが、ワイルドカードを使った -like および -notlike 演算子です。

PowerShellで使われるワイルドカードは主に以下の2種類です。

  • *: 任意の0個以上の文字に一致します。
  • ?: 任意の1個の文字に一致します。

ワイルドカードフィルタリングの例

例7:特定の文字列で始まるサービスを検索する

名前が「Win」で始まるサービスを検索します。

powershell
Get-Service | Where-Object { $_.Name -like 'Win*' }

解説:
'Win*' は「’Win’ という文字列で始まり、その後に任意の0個以上の文字が続く」パターンに一致します。

例8:特定の文字列を含むサービスを検索する

名前に「Update」という文字列を含むサービスを検索します。

powershell
Get-Service | Where-Object { $_.Name -like '*Update*' }

解説:
'*Update*' は「任意の0個以上の文字で始まり、’Update’ という文字列を含み、その後に任意の0個以上の文字が続く」パターンに一致します。

例9:特定のパターンに一致しないファイルを検索する

現在のディレクトリにあるファイルのうち、拡張子が .log または .tmp でないものを検索します。(-notlike の例)

powershell
Get-ChildItem -File | Where-Object { $_.Name -notlike '*.log' -and $_.Name -notlike '*.tmp' }

解説:
-File パラメータでファイルのみを取得し、-notlike-and で組み合わせて使用しています。「.logで終わらない」かつ「.tmpで終わらない」という条件でフィルタリングしています。

正規表現を使ったパターンマッチ (-match, -notmatch)

ワイルドカードよりも複雑なパターンで文字列をフィルタリングしたい場合は、正規表現を使います。正規表現は非常に強力で柔軟なパターン記述方法です。Where-Object では -match および -notmatch 演算子で正規表現を使用できます。

正規表現のパターンマッチングは、デフォルトでは大文字・小文字を区別しません。区別したい場合は -cmatch を使用します。

正規表現の詳しい解説はここでは行いませんが、基本的な要素だけ紹介します。

  • .: 任意の一文字
  • ^: 文字列の先頭
  • $: 文字列の末尾
  • *: 直前の要素の0回以上の繰り返し
  • +: 直前の要素の1回以上の繰り返し
  • ?: 直前の要素の0回または1回の繰り返し
  • [abc]: a, b, c のいずれか一文字
  • [^abc]: a, b, c 以外のいずれか一文字
  • \d: 数字 ([0-9] と同じ)
  • \w: 英数字とアンダースコア ([a-zA-Z0-9_] と同じ)
  • \s: 空白文字 (スペース、タブなど)
  • |: OR (例: a|b は a または b に一致)
  • (...): グループ化

正規表現フィルタリングの例

例10:特定のパターンに一致するファイルパスを検索する

システムパス環境変数 $env:Path を構成するパスのうち、ドライブレターで始まり、その後にコロンと円マーク(バックスラッシュ)が続くパターン(例: C:\, D:\)に一致するものを検索します。

powershell
$env:Path.Split(';') | Where-Object { $_ -match '^[A-Za-z]:\\.*' }

解説:
1. $env:Path.Split(';') は、環境変数 Path の文字列をセミコロン ; で分割し、パス文字列の配列を作成します。
2. Where-Object は配列の各要素(各パス文字列、$_)に対してフィルタリングを行います。
3. '^[A-Za-z]:\\.*' が正規表現パターンです。
* ^: 文字列の先頭に一致。
* [A-Za-z]: 任意の大文字または小文字のアルファベット一文字に一致(ドライブレター)。
* :: コロン : に一致。
* \\: 円マーク(バックスラッシュ \)に一致。正規表現では \ はエスケープ文字なので、\\ と記述します。
* .*: 任意の文字 (.) が0回以上 (*) 繰り返されるパターンに一致(その後のディレクトリ名など)。
4. このパターンに一致する文字列(パス)のみがフィルタリングされます。

例11:特定のポート範囲を使用しているプロセスを検索する

これは少し複雑な例ですが、Get-NetTCPConnection と組み合わせて、特定のローカルポート範囲(例: 50000番台)を使用しているプロセスを検索します。

powershell
Get-NetTCPConnection | Where-Object { $_.State -eq 'Listen' -and $_.LocalPort -match '^5\d{4}$' } | Select-Object -Property LocalAddress,LocalPort,RemoteAddress,RemotePort,State,OwningProcess

解説:
1. Get-NetTCPConnection は現在のTCP/IP接続情報を取得します。各接続オブジェクトには LocalPort プロパティなどがあります。
2. Where-Object で、State が ‘Listen’ であり (-and)、かつ LocalPort が正規表現 '^5\d{4}$' に一致するものをフィルタリングします。
* ^: 文字列の先頭
* 5: 数字の 5 に一致
* \d{4}: 数字 (\d) がちょうど4回繰り返される ({4}) パターンに一致
* $: 文字列の末尾に一致
* この正規表現は、「5」で始まり、その後に正確に4桁の数字が続く文字列、つまり50000~59999の数字に一致します。
3. フィルタリングされたオブジェクトから、必要なプロパティだけを Select-Object で選択して表示しています。

データ型によるフィルタリング (-is, -isnot)

PowerShellのオブジェクトはそれぞれ特定のデータ型を持っています。オブジェクトの型そのものに基づいてフィルタリングしたい場合があります。これには -is および -isnot 演算子を使用します。

-is <TypeName> は、オブジェクトが指定された型またはその派生型である場合に真となります。
-isnot <TypeName> は、オブジェクトが指定された型またはその派生型でない場合に真となります。

型名は、.NET Framework (または .NET Core/.NET 5+) の型名を指定します。例えば、文字列型は [System.String]、整数型は [System.Int32]、ファイル情報は [System.IO.FileInfo]、ディレクトリ情報は [System.IO.DirectoryInfo] などです。括弧 [] で囲んで指定します。

データ型フィルタリングの例

例12:ファイル情報オブジェクトのみを検索する

Get-ChildItem の出力にはファイルとディレクトリの両方が含まれます。その中からファイル(System.IO.FileInfo 型)だけをフィルタリングします。

powershell
Get-ChildItem | Where-Object { $_ -is [System.IO.FileInfo] }

解説:
Get-ChildItem の出力オブジェクト($_)が [System.IO.FileInfo] 型であるかを -is で判定しています。

例13:ディレクトリ情報オブジェクトのみを検索する

同様に、ディレクトリ(System.IO.DirectoryInfo 型)だけをフィルタリングします。

powershell
Get-ChildItem | Where-Object { $_ -is [System.IO.DirectoryInfo] }

例14:特定の型のオブジェクトを除外する

Get-ChildItem の出力からディレクトリを除外してファイルだけを表示する(例12と同じ結果になりますが、-isnot を使った例です)。

powershell
Get-ChildItem | Where-Object { $_ -isnot [System.IO.DirectoryInfo] }

解説:
オブジェクトがディレクトリ型ではない (-isnot) という条件でフィルタリングしています。

スクリプトブロックを使った高度なフィルタリング

これまでの例では、オブジェクトの「プロパティの値」と「特定の値」を比較することでフィルタリングを行ってきました。しかし、プロパティの値そのものではなく、その値を加工したり、オブジェクトのメソッドを呼び出したり、複数のプロパティを組み合わせて計算した結果に基づいたりしてフィルタリングを行いたい場合があります。

このような高度なフィルタリングを行うには、Where-Object のスクリプトブロック {} の中に、評価したい任意のPowerShellコードを記述します。スクリプトブロック内で最後に評価された式、または return ステートメントで返された値が、そのオブジェクトに対するフィルタリング結果となります。結果が $true と評価されればオブジェクトは通過し、$false と評価されればオブジェクトは破棄されます。

スクリプトブロック内でも、現在のオブジェクトは $_ または $PSItem という自動変数で参照できます。($PSItem はPowerShell 3.0以降で導入された $_ の別名で、特にネストされたループなどで混乱を避けるために使われることがあります。)

スクリプトブロックフィルタリングの例

例15:特定の時間より後に書き換えられたファイルを検索する

現在のディレクトリにあるファイルのうち、最終書き込み時刻 (LastWriteTime) が過去24時間以内のものを検索します。

powershell
$cutoffTime = (Get-Date).AddDays(-1)
Get-ChildItem -File | Where-Object { $_.LastWriteTime -ge $cutoffTime }

解説:
1. $cutoffTime = (Get-Date).AddDays(-1) で、現在時刻から1日前(過去24時間前)の時刻を計算し、変数に格納します。
2. Where-Object のスクリプトブロック {} の中で、$_.LastWriteTime (現在のファイルオブジェクトの最終書き込み時刻) が $cutoffTime と比較されます。
3. -ge 演算子で、「より大きいか等しい」、つまり $cutoffTime 以降の時刻であるかを判定します。
4. 条件が真であるファイルオブジェクトが選択されます。

この例は比較的シンプルですが、$_.LastWriteTime プロパティの値そのものを使うのではなく、外部で計算した値($cutoffTime)と比較している点がポイントです。

例16:特定のメソッド呼び出しの結果でフィルタリングする

Get-Process の出力には各プロセスの応答性を示すプロパティは直接ありませんが、Processオブジェクトには Responding というメソッド(またはプロパティとして公開されている場合もある)があります。これを使って、応答中のプロセスだけをフィルタリングします。

“`powershell
Get-Process | Where-Object { $_.Responding -eq $true }

またはよりシンプルに

Get-Process | Where-Object { $_.Responding }
“`

解説:
1. Get-Process がプロセスオブジェクトを生成します。
2. Where-Object は各プロセスオブジェクトに対して { $_.Responding } を評価します。
3. 多くのオブジェクト指向言語と同様に、PowerShellではブール値を返すプロパティやメソッドの結果を直接条件式として使用できます。$_.Responding は応答中であれば $true、そうでなければ $false を返すため、これがそのままフィルタリング条件となります。$true と明示的に比較する必要はありません。

例17:複数のプロパティを組み合わせた計算結果でフィルタリングする

これは仮想的な例ですが、例えばディスクオブジェクトに対して、空き容量 (FreeSpace) が総容量 (Capacity) の10%未満であるディスクを検索するとします。

powershell
Get-Volume | Where-Object { ($_.FreeSpace / $_.Capacity) -lt 0.10 }

解説:
1. Get-Volume でボリューム情報(Windowsの場合)を取得します。
2. Where-Object のスクリプトブロック内で、$_.FreeSpace$_.Capacity で割った値、つまり空き容量の割合を計算しています。
3. この計算結果が 0.10 (10%) より小さいかを -lt で判定しています。
4. 空き容量が10%未満のボリュームオブジェクトが選択されます。

このように、スクリプトブロックを使えば、オブジェクトのプロパティに対して任意の計算や操作を行い、その結果に基づいてフィルタリングすることが可能です。これは Where-Object の最も強力な側面の1つです。

遅延スクリプトブロックの利用 (PowerShell 3.0以降)

PowerShell 3.0以降では、Where-Object のスクリプトブロックの構文がさらに簡潔になりました。プロパティの名前、比較演算子、比較したい値を指定するだけで、スクリプトブロック {} の中に $_.<プロパティ名> <演算子> <値> と書くのと同じ意味になります。

構文:
powershell
<コマンドレット> | Where-Object <プロパティ名> <演算子> <値>

またはエイリアスで:
powershell
<コマンドレット> | ? <プロパティ名> <演算子> <値>

この構文は、比較対象が単一のプロパティの値であり、かつ単純な比較演算子 (-eq, -ne, -gt など) を使う場合にのみ適用できます。-and, -or などの論理演算子で複数の条件を組み合わせたい場合や、スクリプトブロック内で複雑な処理を行いたい場合は、従来の { $_.<プロパティ名> <演算子> <値> } 構文またはより高度なスクリプトブロック構文を使用する必要があります。

内部的には、PowerShellはこの遅延スクリプトブロック構文を { $_.<プロパティ名> <演算子> <値> } の形に自動的に変換して実行します。この構文を「遅延バインディングスクリプトブロック」や「簡易構文」と呼ぶこともあります。

遅延スクリプトブロックの例

例18:実行中のサービスを検索する(簡易構文)

例2と同じ処理を簡易構文で記述します。

powershell
Get-Service | Where-Object Status -eq 'Running'

解説:
Status (プロパティ名)、-eq (演算子)、'Running' (値) を並べて指定するだけで、先ほどの { $_.Status -eq 'Running' } と同じ効果が得られます。

例19:特定のIDより大きいプロセスを検索する(簡易構文)

例3と同じ処理を簡易構文で記述します。

powershell
Get-Process | Where-Object Id -gt 1000

解説:
Id (プロパティ名)、-gt (演算子)、1000 (値) でフィルタリングしています。

注意点:
この簡易構文は非常に便利ですが、常に使えるわけではありません。

  • 複数の条件 (-and, -or) を組み合わせる場合、この構文は使えません。
  • スクリプトブロック内でプロパティの値以外のもの(例: 変数、計算結果)と比較したり、メソッドを呼び出したりする場合、この構文は使えません。
  • 比較演算子以外の演算子 (-like, -match, -is など) はこの構文で使用できます。

結局のところ、最も柔軟で強力なのは、$_ を使ったスクリプトブロック構文 { $_.<...> } または { ... } です。簡易構文は、条件が単純な場合に記述を簡潔にするためのものです。初心者はまず $_ を使ったスクリプトブロック構文をマスターし、慣れてきたら簡易構文も使う、というステップが良いかもしれません。

パフォーマンスに関する考慮事項

Where-Object はパイプラインの途中でフィルタリングを行うため、パフォーマンスに関して重要な側面があります。

パイプラインの早い段階でフィルタリングする:

PowerShellのパイプラインは、コマンドレットがオブジェクトを一つずつ処理していくストリームのようなものです。Where-Object はパイプラインからオブジェクトを受け取り、条件を満たせば次のコマンドレットに渡しますが、満たさなければ破棄します。

したがって、データソースに近い、パイプラインの早い段階でデータを絞り込むほど、後続のコマンドレットの処理対象オブジェクトの数を減らすことができ、全体的な処理速度が向上します

例えば、ある条件を満たすイベントログを検索し、その数を数えたいとします。

非効率な例: まず全てのイベントログを取得し、その後でフィルタリングしてカウント。

“`powershell

非効率な例

Get-EventLog -LogName Application | Where-Object { $_.Source -eq ‘Microsoft-Windows-WindowsUpdateClient’ } | Measure-Object
“`

この場合、Get-EventLog は大量のイベントログ(数千、数万件に及ぶことも)を全て取得してパイプラインに流します。その後、Where-Object がそれらを一つずつフィルタリングします。

効率的な例: Get-EventLog 自体が持つフィルタリングパラメータを利用し、その後に Where-Object でさらにフィルタリング(または Get-EventLog のフィルタリングパラメータで全てを済ませる)。

多くのデータソースを提供するコマンドレット(例: Get-EventLog, Get-ADUser, Get-VM, Get-CimInstance, Get-WmiObject など)は、データを取得する際にプロバイダー側でフィルタリングを行うための専用のパラメータを持っています。例えば、Get-EventLog には -Source, -EntryType, -Newest などのパラメータがあります。Get-ADUser には -Filter-LDAPFilter パラメータがあります。

これらのプロバイダー側のフィルタリングパラメータは、Where-Object よりもはるかに高速です。なぜなら、プロバイダー側でのフィルタリングは、データソースからクライアント(PowerShell)に転送されるデータの量自体を削減するからです。データソース側で不要なデータが捨てられるため、ネットワーク帯域やクライアント側のメモリ使用量も節約できます。

上記のイベントログの例を、Get-EventLog-Source パラメータを使って効率化します。

“`powershell

効率的な例

Get-EventLog -LogName Application -Source ‘Microsoft-Windows-WindowsUpdateClient’ | Measure-Object
“`

この例では、Get-EventLog'Microsoft-Windows-WindowsUpdateClient' をソースとするイベントログのみを取得し、パイプラインに流します。これにより、Measure-Object に渡されるオブジェクトの数が大幅に減り、処理が速くなります。

結論として、可能な限りプロバイダー側のフィルタリングパラメータを優先して使用し、それだけでフィルタリングできない場合にのみ Where-Object を使うのがベストプラクティスです。

ただし、Get-ChildItem のように、プロパティベースのフィルタリングパラメータを持たないコマンドレットから取得したオブジェクトをフィルタリングする場合や、複数のコマンドレットの出力を組み合わせてフィルタリングする場合など、Where-Object が必須となる状況も多くあります。そのような場合は、Where-Object をパイプラインのできるだけ早い段階に配置することを心がけましょう。

よくある落とし穴とトラブルシューティング

Where-Object を使う上で遭遇しやすい問題と、その解決策について解説します。

  1. NULL値の扱い:
    オブジェクトのプロパティが $null である可能性があります。Where-Object$null と比較する際は注意が必要です。

    “`powershell

    プロパティが $null であるオブジェクトを検索

    Get-ChildItem | Where-Object { $_.Length -eq $null }

    プロパティが $null ではないオブジェクトを検索

    Get-ChildItem | Where-Object { $_.Length -ne $null }
    “`

    存在しないプロパティにアクセスしようとするとエラーになる場合もありますが、多くの場合は $null を返します。予期せぬ $null 値によって条件式が正しく評価されないことがあるため、特に複雑なスクリプトブロック内では $null チェックを適切に行うことが重要です。例えば、プロパティが存在することを確認してから値を使う、といった書き方を検討します。

    “`powershell

    Length プロパティが存在し、$null でないオブジェクトを検索

    Get-ChildItem | Where-Object { $.PSObject.Properties[‘Length’] -ne $null -and $.Length -ne $null }
    ``
    しかし、これは通常過剰であり、多くの場合
    $.<プロパティ名> -ne $nullで十分です。$.<プロパティ名>が存在しない場合、評価結果は$null -ne $nullとなり$false` となります。

  2. データ型の不一致:
    比較演算子を使う場合、比較する両辺の値のデータ型が重要です。PowerShellは多くの場合、自動的に型変換を行いますが、意図しない結果になることもあります。例えば、文字列としての数値と、数値としての数値を比較する場合などです。

    “`powershell

    文字列としての ‘1000’ と数値の 1000 を比較

    ‘1000’ -eq 1000 # => True (PowerShellが自動変換)

    数値の比較演算子を使う場合

    ‘1000’ -gt 500 # => True (PowerShellが自動変換)

    しかし、意図的に型を指定することもできる

    Get-Process | Where-Object { $.Id -gt [int]’1000′ } # 文字列を数値に明示的にキャスト
    ``
    プロパティの値の型を確認するには、
    Get-Memberコマンドレットを使用するか、オブジェクトのプロパティの型情報を参照します ($
    ..GetType().Name`)。

  3. ワイルドカードと正規表現の混同:
    -like はワイルドカード (*, ?) を使い、-match は正規表現を使います。それぞれのメタ文字(特別な意味を持つ文字)のルールは異なります。例えば、ファイルパスの区切り文字である円マーク \ は、ワイルドカードでは特別な意味を持ちませんが、正規表現ではエスケープ文字なので \\ と記述する必要があります。混同すると、意図したパターンに一致しない原因となります。

  4. 複雑な条件のデバッグ:
    複数の -and-or を組み合わせた複雑な条件式は、読みにくく、間違いを犯しやすいです。デバッグの際は、条件式を小さな部分に分割して評価してみたり、括弧を使って評価順序を明確にしたりすることが有効です。また、Write-Host などを使って、パイプラインの途中でオブジェクトのプロパティの値や中間的な条件式の評価結果を表示させて確認するのも手です。

    “`powershell

    デバッグの例:複雑な条件を分割して確認

    Get-Service | ForEach-Object {
    $isNameMatch = ($.Name -eq ‘BITS’ -or $.Name -eq ‘Spooler’)
    $isStatusRunning = ($.Status -eq ‘Running’)
    Write-Host “Service: $($
    .Name), NameMatch: $isNameMatch, StatusRunning: $isStatusRunning, Result: $($isNameMatch -and $isStatusRunning)”
    if ($isNameMatch -and $isStatusRunning) {
    $_ # 条件を満たすオブジェクトを通過させる
    }
    }
    ``
    この例は
    Where-Objectの代わりにあえてForEach-Object` を使って、各オブジェクトに対する条件評価の過程を可視化しています。デバッグ時にはこのような手法も有効です。

応用例

これまでに紹介した Where-Object の使い方を組み合わせた、より実践的な応用例を紹介します。

例20:特定の期間に発生したエラーイベントを検索する

過去7日間に発生した、ソースが「Service Control Manager」のエラーイベントを検索します。

powershell
$startDate = (Get-Date).AddDays(-7)
Get-EventLog -LogName System -EntryType Error -Source 'Service Control Manager' -After $startDate

解説:
この例では、Get-EventLog のプロバイダー側フィルタリングパラメータ -EntryType, -Source, -After を最大限に活用しています。これにより、PowerShell側で処理するオブジェクトの数を劇的に減らすことができます。もしこれらのパラメータだけでは不十分な場合(例えば、特定のメッセージ本文を含むイベントだけを検索したい場合)に、さらに Where-Object をパイプラインで繋ぎます。

“`powershell

さらにメッセージ本文でフィルタリングする場合

$startDate = (Get-Date).AddDays(-7)
Get-EventLog -LogName System -EntryType Error -Source ‘Service Control Manager’ -After $startDate |
Where-Object { $_.Message -match ‘failed to start’ }
``
この例では、まずプロバイダー側で日付、種類、ソースで絞り込み、その後
Where-Object` でメッセージ本文に「failed to start」という文字列(正規表現でマッチ)を含むイベントをフィルタリングしています。

例21:特定のファイルサイズ範囲のファイルを検索する

現在のディレクトリとそのサブディレクトリにあるファイルのうち、サイズが1MBより大きく、10MB以下のファイルを検索します。

powershell
Get-ChildItem -Recurse -File | Where-Object { $_.Length -gt 1MB -and $_.Length -le 10MB }

解説:
1. Get-ChildItem -Recurse -File で、現在のディレクトリ以下の全てのファイルを再帰的に取得します。
2. Where-Object で、Length プロパティ(ファイルのサイズをバイト単位で示す)が 1MB より大きく、かつ 10MB 以下であるかを -and で組み合わせた条件でフィルタリングしています。PowerShellは 1MB, 10MB のような単位付きの数値を自動的にバイト数に変換してくれます。

例22:特定のユーザーが所有するプロセスを検索する

システムで実行中のプロセスの中から、特定のユーザーアカウント(例: “SYSTEM”)が所有するプロセスを検索します。プロセスオブジェクトには直接ユーザー情報がありませんが、Get-CimInstanceGet-WmiObject を使うと取得できます。ここでは Get-CimInstance と関連するWMIクラスを使います。

powershell
Get-CimInstance -ClassName Win32_Process |
Where-Object {
$ownerSID = $_.GetOwner().Sid
# 複雑な例では $ownerSID からユーザー名を取得する必要がある
# 簡単のため、ここでは GetOwner() メソッド呼び出しが成功するかでフィルタリング
# または特定のSID/ユーザー名と比較
try {
$owner = $_.GetOwner()
$owner.User -eq 'SYSTEM'
} catch {
$false # エラーが発生した場合(Ownerを持たないプロセスなど)は除外
}
}

解説:
1. Get-CimInstance -ClassName Win32_Process は、WMI (またはCIM) を通じてプロセス情報を取得します。このオブジェクトは Get-Process の出力オブジェクトとは異なりますが、より多くの情報(所有者など)を持っています。
2. Where-Object のスクリプトブロック内で、各プロセスオブジェクト ($_) に対して GetOwner() というメソッドを呼び出しています。このメソッドはプロセス所有者の情報を含むオブジェクトを返します。
3. $owner.User -eq 'SYSTEM' で、取得した所有者オブジェクトの User プロパティが 'SYSTEM' であるかを比較しています。
4. try/catch ブロックは、GetOwner() メソッドが一部の特殊なプロセス(例: Idle)でエラーになるのを回避するためのものです。エラーが発生した場合は $false を返し、そのプロセスは除外されます。

この例は、Where-Object のスクリプトブロック内でメソッドを呼び出し、その結果を利用したフィルタリングの強力さを示しています。

例23:配列に含まれる値でフィルタリングする (-in, -contains)

特定のサービスのリストの中から、現在実行中または停止中のサービスを検索します。

powershell
$serviceList = @('BITS', 'Spooler', 'Schedule', 'WSearch')
$serviceList | Get-Service | Where-Object { $_.Status -in @('Running', 'Stopped') }

解説:
1. $serviceList という配列に検索したいサービス名を定義します。
2. $serviceList | Get-Service で、配列の各サービス名に対応するサービスオブジェクトを取得します。Get-Service はパイプラインでサービス名を受け取ることができます。
3. Where-Object { $_.Status -in @('Running', 'Stopped') } で、取得したサービスオブジェクトのうち、Status プロパティの値が配列 @('Running', 'Stopped') の中に含まれる (-in) ものだけをフィルタリングします。

-in 演算子は左辺の単一の値が右辺の配列に含まれているかを判定します。
-contains 演算子は左辺の配列が右辺の単一の値を含んでいるかを判定します。使い分けに注意してください。上の例のように、オブジェクトのプロパティ値が特定の複数の値のいずれかに一致するかを確認する場合は -in が適切です。

まとめ

この記事では、PowerShellの Where-Object コマンドレットについて、その基本から応用までを詳細に解説しました。

  • Where-Object は、パイプラインを流れるオブジェクトを、指定した条件に基づいてフィルタリングするコマンドレットです。
  • オブジェクトのプロパティにアクセスするには $_.<プロパティ名> を使用します。
  • -eq, -ne, -gt, -lt などの比較演算子や、-like, -match, -is などの演算子を使って条件を記述します。
  • -and, -or, -not といった論理演算子を使って複数の条件を組み合わせることができます。括弧 () は条件の評価順序を制御するのに重要です。
  • {} で囲まれたスクリプトブロック内に任意のPowerShellコードを記述することで、プロパティの値だけでなく、メソッド呼び出しの結果や計算結果に基づいた高度なフィルタリングが可能です。スクリプトブロックの評価結果が $true のオブジェクトが選択されます。
  • PowerShell 3.0以降では、単一プロパティの単純な比較には簡易構文 Where-Object <プロパティ名> <演算子> <値> を使用できます。
  • パフォーマンスの観点から、データソース側のフィルタリングパラメータ(もしあれば)を優先し、Where-Object はパイプラインのできるだけ早い段階に配置することが推奨されます。
  • NULL値、データ型の不一致、ワイルドカードと正規表現の混同などがトラブルの原因になることがあります。複雑な条件は分割してデバッグしましょう。

Where-Object は、PowerShellで取得したデータを加工・分析する上で最も頻繁に使用されるコマンドレットの一つです。この記事で紹介した様々なテクニックを習得することで、より効率的かつ柔軟に目的のデータを抽出できるようになります。日々のスクリプト作成や対話的な操作において、ぜひ積極的に活用してみてください。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール