Articles

ForEachとWhere magic methods

ForEachとWhereは、バージョン1が2006年にリリースされて以来、PowerShellで使用されてきた二つの頻繁に使用される概念です。 ForEachは、ステートメントとコマンドレット(ForEach-Object)の両方として使用できるようになり、オブジェクトのコレクションを反復処理し、そのコレクション内のオ Whereはコマンドレット(Where-Object)として使用でき、コレクション内のオブジェクトのプロパティまたはコレクションに属するオブジェクト自体を使用して評価される条件である条件を渡さないコレクション内の項目をフィルタリングできます。 コレクション内のアイテムに対してアクションを実行するForEach機能とコレクションをフィルター処理するWhere機能は、非常に有用な機能であり、使用されているPowerShellのバージョンに関係なく、多くのPowerShellスクリプト、コマンド、およびモジュールの背後にあるロジックにあるフォームまたは別の形式に含まれています。 実際、それらは非常に頻繁に使用されているため、PowerShellバージョン3.0および4.0のパフォーマンス、機能、構文の改善のための焦点領域となっています。

Windows PowerShell4.0のリリースでは、Windows PowerShellのForEachおよびWhere機能にアクセスするための新しい構文を提供するコレクション型に対して、2つの新しい”魔法の”メソッ これらのメソッドは適切にForEachとWhereという名前です。 私はこれらのメソッドを”魔法”と呼んでいます。 これらは、-Forceとrequest-MemberType Allを適用しても、Get-Member出力には表示されません。 しかし、それらはプライベートクラスに実装されたプライベート拡張メソッドであるため、広範な検索が必要です。 しかし、彼らはカバーの下を覗かずに発見できないにもかかわらず、彼らはあなたがそれらを必要とするときにそこにあり、彼らは彼らの古い対応よりも高速であり、彼らは彼らの古い対応では利用できなかった機能を含んでいるので、あなたがPowerShellでそれらを使用するときに彼らがあなたを残す”魔法”の感覚。 残念なことに、これらの方法は、彼らが公にリリースされて以来、ほぼ一年、今日でも文書化されていないままなので、多くの人々は、これらの方法で利用可能 この記事では、PowerShellを使用するときにこの魔法を活用できるように、使用できる場所と動作方法を説明することで、これを修正しようとします。ForeachとWhereメソッドがどのように機能するかを説明する前に、これらの2つのメソッドとPowerShell3.0に関して何かを言及する必要があります。 ForEachとWhereメソッドはPowerShell4.0以降のバージョン、PowerShell3でのみ利用可能になったのは事実です。0はまだ多くの環境で非常に広く使用されており、PowerShell4.0以降で標準化された環境でPowerShellを使用していない限り、PowerShell3.0を使用するときに新しいメソッ 私はこれが対処する価値のある制限だと感じたので、私は最近GitHubとPowerShellリソースギャラリー(別名PowerShellGetパブリックリポジトリ、現在限定プレビュー中)で公開したTypePxモジュー この実装にはいくつかの欠点がありますが、この記事の後半で強調します。

ForEachメソッド

ForEachは、オブジェクトのコレクションをすばやく反復処理し、そのコレクション内の各オブジェクトに対して何らかのアクショ このメソッドは、以前の対応するもの(foreachステートメントおよびForEach-Objectコマンドレット)よりも高速なパフォーマンスを提供し、コレクション内のオブジェク このメソッドによって出力されるすべてのオブジェクトは、System型の汎用コレクションで返されます。コレクション。ObjectModel。コレクション1

このメソッドを呼び出すには、サポートされている6つの方法があり、それぞれの方法については、以下で詳しく説明します。 ForEachメソッドを呼び出すときに使用できるサポートされている引数は次のとおりです:

  • ForEach(scriptblock式)
  • ForEach(型convertToType)
  • foreach(文字列propertyName)
  • ForEach(文字列propertyName,オブジェクトnewValue)
  • ForEach(文字列methodName)
  • ForEach(文字列methodName,オブジェクト引数)
  • ForEach(scriptblock式,オブジェクト引数)
  • ForEach(scriptblock式,オブジェクト引数)

これらはサポートされている引数のペアリングであり、foreachメソッドで使用できる異なるオーバーロードではないことに注意してください。 これら以外の引数ペアリングを使用すると、実際の問題が何であるかを明確に識別しないエラーが発生する可能性があります。

ForEach(scriptblock expression)およびForEach(scriptblock expression,object arguments)

スクリプトブロック式をForEachメソッドに渡すと、foreachステートメントまたはForEach-Objectコマンドレットで使用するスクリプト また、ForEach-Objectコマンドレットと同様に、processed_変数とPs PSItem変数はどちらも処理中の現在の項目を参照します。 最初のスクリプトブロック引数を超えて指定した引数は、スクリプトブロックの引数として使用されます。 これは、ForEach-Objectコマンドレットで-ArgumentListパラメーターがどのように機能するかと同じです。 ここでは、これを使用してコレクション内の各項目でスクリプトブロックを実行する方法を示す例を示します。

# Get a set of services$services = Get-Service c*# Display the names and display names of all services in the collection$services.foreach{"$($_.Name) ($($_.DisplayName))"}# Select a property name to expand using a script block argument$services.foreach({param($PropertyName); $_.$PropertyName}, 'DisplayName')

スクリプトブロック自体を角かっこでラップしなかったため、この構文について何か奇妙なことに気づいたかもしれません。 これは、PowerShellパーサーが拡張され、単一のスクリプトブロック引数を受け入れるメソッドを呼び出すたびに角かっこを省略できるようになったためです。 また、foreachステートメントやForEach-Objectコマンドレットと同様に、指定したスクリプトブロックが現在のスコープで呼び出されます。 つまり、そのスクリプトブロック内で行う変数の割り当ては、ForEachメソッドの実行が完了した後も保持されます。

ForEach(type convertToType)

ForEachメソッドに固有の、コレクション内のすべての項目を別の型に変換する場合は、ForEachメソッドに型を渡すことができます。 たとえば、オブジェクトのコレクションがあり、それらのオブジェクトを同等の文字列に変換するとします。 ForEachメソッドでどのように見えるかは次のとおりです。

# Get a collection of processes$processes = Get-Process# Convert the objects in that collection into their string equivalent$processes.foreach()

コレクションをstring型の配列(例えば]processes processes)に型キャストすることで同じタスクを実行することができ、配列を型キャストすることは実際には大幅に高速ですが、非常に大きなコレクションで作業していない限り、実行時間の違いに気付かない可能性は非常に高いです。 時間差にもかかわらず、私が書いたスクリプトで余分な丸括弧を避けることによって実装の優雅さを維持できる場合、私は特定の状況でForEachメソッド

ForEach(string propertyName)

PowerShell3.0以降では、ForEach-Objectの唯一のパラメータ値としてプロパティ名を渡すだけで、特定のプロパティの値をより簡単に取得できるように、ForEach-Objectに第二のパラメータセットが追加されました。 この便利さは、ForEachメソッドでも提供されています。 ここでは、コレクションを反復処理してそのコレクションのプロパティを返す方法を示す例を示します。

# Get all services whose name starts with "w"$services = Get-Service w*# Return the names of those services$services.foreach('Name')

もちろん、PowerShellのバージョン3.0services.Name すべてのサービスの名前を取得するには、ForEachメソッドの代替よりも速く完了します(ただし、非常に大きなコレクションのパフォーマンスの違いは数十万; しかし、これはコレクション自体にないプロパティに対してのみ機能し、コマンドが何をするかの暗黙的な性質のために、一部のスクリプターが慣れていない構文です。 新しいForEachメソッド構文は、もう少し自己文書化するという追加の利点を持つ、より明示的な代替手段を提供します。

ForEach(string propertyName,object newValue)

オブジェクトのコレクションのプロパティを取得できるだけでなく、オブジェクトのコレクションのプロパティを設定 これは、スクリプトブロックを明示的に作成しない限り、他のforeachでは使用できない機能です。 プロパティを設定するには、プロパティ名とそのプロパティを設定するときに使用する値を次のように指定します。

# Note, this is not a realistic example# This would be used more commonly on configuration data$services = Get-Service c*# Now change the display names of every service to some new value$services.foreach('DisplayName','Hello')

equals演算子を使

ForEach(string methodName)and ForEach(string methodName,object arguments)

メソッドを呼び出すには、最初の引数としてメソッド名を指定し、次にそのメソッドの引数を第二、第三、第四などと指定します。 引数。 メソッドが引数を取らない場合は、単にメソッド名を渡すことができ、引数なしで呼び出されます。 ここでは、特定のプログラムを実行しているプロセスの束を殺すことができる方法を示す例です:

# Get all processes running Chrome$processes = Get-Process -Name Chrome# Now kill all of those processes$processes.foreach('Kill')

ここでは別の例ですが、今回は引数を持つメソッドを使用しながら、一般的に使用されるパラメータに適切な名前とエイリアスを使用

ForEachメソッドで現在使用可能なすべての機能をカバーします。 ご覧のとおり、このメソッドには多くの新しい機能が提供されていませんが、オブジェクトのコレクションに対して簡単なタスクを実行するときの構文の改善は素晴らしく、foreachメソッドのパフォーマンスの改善は、ForEach-Object pipelineの同等のforeachステートメントと比較しても歓迎される改善です。 その説明が邪魔にならないように、Whereメソッドに移りましょう。

Whereメソッド

Whereは、オブジェクトのコレクションをフィルタリングできるメソッドです。 これはWhere-Objectコマンドレットと非常によく似ていますが、WhereメソッドもSelect-ObjectおよびGroup-Objectに似ており、Where-Objectコマンドレット自体ではネイティブにサポートされていないいくつかの追加機能が含まれています。 このメソッドは、シンプルでエレガントなコマンドでWhere-Objectよりも高速なパフォーマ ForEachメソッドと同様に、このメソッドによって出力されるオブジェクトはすべて、System型のジェネリックコレクションで返されます。コレクション。ObjectModel。コレクション1.

このメソッドには、次のように記述できるバージョンが1つしかありません:

Where(scriptblock expression])

角かっこで示されているように、expression scriptブロックは必須であり、mode列挙とnumberToReturn整数引数は省略可能なので、1、2、または3の引数を使用してこのメソ 特定の引数を使用する場合は、その引数の左側にすべての引数を指定する必要があります(つまり、numberToReturnの値を指定する場合は、modeとexpressionの値も指定する必

Where(scriptblock expression)

Whereメソッドの最も基本的な呼び出しは、単にスクリプトブロック式を引数として取ります。 スクリプトブロック式は、処理中のコレクション内のオブジェクトごとに1回評価され、trueを返す場合、オブジェクトはWhereメソッドによって返されま これは、Where-Objectコマンドレットを呼び出してスクリプトブロックを渡すのと同じ機能です。 Where-Objectコマンドレットと同様に、script_変数とPs PSItem変数を使用して、スクリプトブロック内で処理されている現在の項目を参照できます。ここでは、実行中のサービスのリストを取得する方法を示す、非常に簡単な例です。

# Get all services$services = Get-Service# Now filter out any services that are not running$services.where{$_.Status -eq 'Running'}

これは新しい機能を提供しませんが、Where-Objectよりもはるかに高速なパフォーマンスを提供し、構文は非常に簡単に従うので、変数に格納されているコレクションのクライアント側のフィルタリングを実行しているときは、スクリプトにこれを考慮する必要があります。

Where(scriptblock expression,WhereOperatorSelectionMode mode)

Whereメソッドのオプションのパラメータを見始めると、物事ははるかに面白くなり始めます。 Windows PowerShellバージョン4.0には、systemの型名を持つ新しい列挙体が含まれていました。管理。自動化。WhereOperatorSelectionMode。 その型名の接尾辞”SelectionMode”に注意してください。 これは、Where構文で強力な選択機能を提供するために使用されます。 この列挙体に含まれる値とその定義を次に示します:

Default エクスプレッションスクリプトブロックを使用してコレクションをフィルタリングし、最大数が指定されている場合は最大数に、コレクション内のすべてのオブジェクトにデフォルト設定されていない場合は、コレクション内のすべてのオブジェクトにデフォルト設定されている。番号を入力してください。
First 式スクリプトブロックフィルタを渡す最初のN個のオブジェクトを返します。
Last 式スクリプトブロックフィルタを渡す最後のN個のオブジェクトを返します。
SkipUntil オブジェクトが式スクリプトブロックフィルタを渡すまでコレクション内のオブジェクトをスキップし、最初のN個のオブジェクトを返します。numberToReturnに最大カウントが指定されていない場合は、残りのすべてのオブジェクトにデフォルト設定されます。
Until オブジェクトが式スクリプトブロックフィルタを渡すまで、コレクション内の最初のN個のオブジェク
Split コレクションを二つに分割し、式スクリプトブロックフィルタを渡すすべてのオブジェクトをnumberToReturnで指定された場合は最大数まで最初のコレクションに配置し、最大数が指定されていない場合は通過するすべてのオブジェクトを配置し、最初のコレクションに入れられていない他のすべてのオブジェクトを第二のコレクションに配置します。

これらのそれぞれは、データのコレクションを処理するときに一意の値を提供するため、各選択モード

Default

驚くことではありませんが、mode引数のデフォルト値は’Default’です。 デフォルトの選択モードでは、選択モードをまったく提供しない場合と同じ機能が提供されます。 たとえば、前の例の最後の行を次のように書くことができます:この例では、引数を指定しなかった場合とまったく同じことを行うため、余分な引数は必要ありません。 また、次のように、numberToReturn引数を使用して、デフォルトの選択モードを使用して返すオブジェクトの最大数を指定することもできます:

# Get the first 10 services in our collection that are running$services.where({$_.Status -eq 'Running'},'Default',10)

最初の選択モード(これについては後で説明します)を使用するときにも正確な機能が利用可能であることに注意することが重要であるため、デフォルトの選択モードを使用しているときにオプションのパラメータをまったく使用することは実用的ではありません。

First

ご想像のとおり、最初の選択モードでは、スクリプトブロック式フィルターを渡すコレクション内の最初のオブジェクトを選択できます。 Numbertoreturn引数に値を指定せずにFirstを使用する場合、またはNumbertoreturn引数に値0を指定してFirstを使用する場合、フィルターを渡す最初のオブジェクトのみが返されます。 この場合、多くのオブジェクトが返されます(フィルタを渡すオブジェクトが多数あると仮定します)。

ここでは、アクションの最初の選択モードを示す私たちのサービスコレクションを使用していくつかの例があります:

# Get the first service in our collection that is running$services.where({$_.Status -eq 'Running'},'First')# Get the first service in our collection that is running$services.where({$_.Status -eq 'Running'},'First',1)# Get the first 10 services in our collection that are running$services.where({$_.Status -eq 'Running'},'First',10)

これらの例の第二のコマンドは、最初の選択モードが使用されているときにnumberToReturn引数のデフォルト値を明示的に渡すだけであるため、第一のコ

Last

最後の選択モードは、最初の選択モードとよく似ており、スクリプトブロック式フィルターを渡すコレクション内の最後のオブジェクトを選 Numbertoreturn引数に値を指定せずにLastを使用する場合、またはNumbertoreturn引数に値0を指定してLastを使用する場合、フィルターを渡す最後のオブジェクトのみが返されま この場合、多くのオブジェクトが返されます(フィルタを渡すオブジェクトが多数あると仮定します)。

最後の選択モードを示すservicesコレクションを使用した例を次に示します:

# Get the last service in our collection that is running$services.where({$_.Status -eq 'Running'},'Last')# Get the last service in our collection that is running$services.where({$_.Status -eq 'Running'},'Last',1)# Get the last 10 services in our collection that are running$services.where({$_.Status -eq 'Running'},'Last',10)

また、最初の選択モードの例と同様に、これらの例の2番目のコマンドは、最後の選択モードが使用されたときにnumberToReturn引数のデフォルト値を

SkipUntil

SkipUntil選択モードでは、スクリプトブロック式フィルターを渡すオブジェクトが見つかるまで、コレクション内のすべてのオブジェクトをスキ フィルタを渡すオブジェクトが見つかると、SkipUntilモードは、numberToReturn引数に値または0が指定されていない場合はコレクションに残っているすべてのオブジェク どちらの場合も、結果にはフィルタを渡した最初のオブジェクトが含まれます。

サービスコレクションのサブセットを使用して、SkipUntil選択モードを実際に表示する例をいくつか示します:

# Get a collection of services whose name starts with "c"$services = Get-Service c*# Skip all services until we find one with a status of "Running"$services.where({$_.Status -eq 'Running'},'SkipUntil')# Skip all services until we find one with a status of "Running", then# return the first 2$services.where({$_.Status -eq 'Running'},'SkipUntil',2)

Until

Until選択モードは、SkipUntil選択モードの反対の機能を提供します。 スクリプトブロック式フィルターを渡すオブジェクトが見つかるまで、コレクション内のオブジェクトを返すことができます。 フィルターを通過するオブジェクトが見つかると、Whereメソッドはオブジェクトの処理を停止します。 NumberToReturn引数に値を指定しない場合、または値0を指定すると、until selectionモードは、スクリプトブロック式フィルターを渡す最初のオブジェクトまでのコレクション内のすべてのオブジェクトを返します。 NumberToReturn引数に0より大きい値を指定すると、Until selectionモードは最大でもその数のオブジェクトを返します。

サービスコレクションの別のサブセットを使用して、Until selection mode in actionを表示する例をいくつか示します。

# Get a collection of services whose name starts with "p"$services = Get-Service p*# Return all services until we find one with a status of "Stopped"$services.where({$_.Status -eq 'Stopped'},'Until')# Return the first 2 services unless we find one with a status of# "Stopped" first$services.where({$_.Status -eq 'Stopped'},'Until',2)

Split

Split selection mode is unique. 新しいコレクションで始まるコレクションのサブセットを返す代わりに、2つの別々のコレクションを内部的に含む新しいコレクションを返します。 これらのネストされたコレクションに含まれるものは、分割選択モードの使用方法に依存します。 分割(Split)オブジェクトのコレクションを2つに分割することができます。 既定では、numberToReturn引数に値を指定しない場合、またはnumberToReturn引数に値0を指定すると、Splitは、スクリプトブロック式フィルターを渡すすべてのオブジェクトを最初の NumberToReturn引数に0より大きい値を指定すると、splitは最初のコレクションのサイズをその最大量に制限し、コレクション内の残りのすべてのオブジェクトは、

ここでは、分割選択モードは、異なる方法でオブジェクトのコレクションを分割するために使用することができる方法を示すいくつかの例:

# Get all services$services = Get-Service# Split the services into two groups: Running and not Running$running,$notRunning = $services.Where({$_.Status -eq 'Running'},'Split')# Show the Running services$running# Show the services that are not Running$notRunning# Split the services into the same two groups, but limit the Running group# to a maximum of 10 items$10running,$others = $services.Where({$_.Status -eq 'Running'},'Split',10)# Show the first 10 Running services$10running# Show all other services$others

この例からわかるように、Splitは非常に強力な選択モードであり、単一のコマンド呼び出しでフィルタリング、グループ化、選択を混在させ

この選択モードのコレクションは、単一のパイプラインで結合されたWhere-Object、Group-Object、Select-Objectよりも、Whereメソッドを高速かつエレガントにすると同時に、より強力 それについて好きではないものは何ですか?

ForeachとTypePxのWhereスクリプトメソッドの欠点

この記事で前述したように、PowerShell3の型拡張機能を記述しました。0以降ではTypePxと呼ばれるモジュールにパッケージ化されています。 TypePxは、powershellで完全に記述されたスクリプトモジュールで、PowerShell3.0以降で実行されます。 PowerShell3.0を使用している場合(およびPowerShell3.0を使用している場合のみ)、TypePxは、PowerShell4.0以降のForEachおよびWhereメソッドの動作を模倣するForEachおよびWhere scriptメソッドを PowerShellの拡張型システムはこの動作を模倣することを可能にしますが、PowerShellに実装されているため、これらの型拡張機能をどれだけ使用できたかに影響 このセクションでは、PowerShell3.0を使用している場合に注意する必要がある、TypePxのForEachおよびWhereスクリプトメソッドに存在するいくつかの違いと制限につ

スクリプトメソッドから呼び出されたスクリプトブロックは、子スコープで実行されます

Foreachとは異なり、Powershell4の一部として実装されたWhereメソッメソッドが呼び出される現在のスコープ内のexpression scriptブロックを呼び出す0以降、PowerShell3.0で実装されたForEachおよびWhereスクリプトメソッドは、子スコープ内のexpression これは、最初から存在していたPowerShellの制限です(制限は、言語としてのPowerShellの最大の欠点だと思うかもしれません)。

この制限により、式スクリプトブロック内で割り当てた変数は、子スコープ内でのみ変更されます。 これは、式スクリプトブロックがForEachまたはWhereを呼び出すスコープ内の変数を更新することを意図している場合に意味があります。 Where式スクリプトブロック内の変数を変更することはあまり一般的ではないため、Whereの使用中にこれが問題を引き起こす可能性は低いですが、ForEachスク私はこの制限を完全に削除したいと思いますが、そうできると信じていますが、この記事を書いた時点ではまだこの修正を実装していません。

PowerShell4.0以降では、String、XmlNode、およびIDictionaryを実装する型を除いて、Ienumerableを実装するすべての型でForEachおよびWhereメソッドが魔法のように使用できるようになりました。 PowerShellでは、拡張型システムでは、型に対してのみインターフェイスに対して拡張を作成することはできません。 ForEachやWhereのような広範な拡張を作成したい場合、これは挑戦です。 TypePxのこれらの拡張機能の現在の実装では、TypePxモジュールは、現在のアプリドメインにロードされているすべてのアセンブリ内のすべての型と、Ienumerableを定義しているがIDictionaryではないすべての非ジェネリック型(StringとXmlNodeを除く)、およびPSObject、Object、String、Int32、またはInt64のジェネリックコレクションに対してIenumerableを定義しているがIDictionaryではないすべてのジェネリック型について、ForEachおよびWhereスクリプトメソッドが作成されます。これは、私自身のテストでは十分であった多数の型をカバーしていますが、これらのメソッドを使用したい型に遭遇する可能性があり、使用できません。 その場合は、GitHubを介して私に知らせてください。 これは削除したい制限でもありますが、PowerShell4.0以降で定義されているように定義できるコンパイル済みアセンブリで同等の機能を実装する方法

PowerShellスクリプトはコンパイルされたコードほど高速ではありません

これはおそらく言うまでもないことですが、解釈された言語であるPowerShellで何かを書くと、c#のような言語で同等のロジックを書いた場合と同じくらい速くは実行されません。 これは、ネイティブのForEachメソッドとWhereメソッドの動作を模倣するために、ForEach-Object、Where-Object、およびパイプラインを内部的に使用するForEachおよびWhereスクリプトメソ この場合、これらのコマンドを持つことの利点は、それらが提供するエレガントな構文と機能に加えて、PowerShell3.0および4.0のスクリプトでこれらを使 ForEachとWhereのパフォーマンス上の利点は、PowerShell4.0ネイティブ実装にのみあります。

PowerShell3.0では、すべてのメソッドパラメーターを括弧で囲む必要があります

上記の例では、リテラルscriptブロックを追加の括弧でラップせずに、単一のliteral scriptブロックパラメーターを使用してメソッドを呼び出すことができたことを述べました。 この機能はPowerShell4にのみ存在します。そのリリースでパーサーに行われた改善のために0以降。 PowerShell3.0では、パーサーはこれをサポートしていないため、ForEachおよびWhereスクリプトメソッドがそのバージョンの単一のリテラルscriptブロックパラメーターで動作するためには、角かっこが常に必要です。Powershell3.0のForEachとWhere magicメソッドの動作を模倣しようとしたとき、それらが提供する機能の量をあまり認識していませんでした。 これらのメソッドの背後にある技術的な詳細を掘り下げて、TypePxで必要な拡張機能を作成することで、これらの非常に強力なメソッドの隠された機能をすべて明らかにすることができました。 この情報が、PowerShell3.0をまだ使用している場合でも、PowerShellの作業でこの素晴らしい新しい機能セットを活用するのに役立つことを願っています。 幸せなスクリプト!