ハッシュテーブルについて知りたかったことのすべてEverything you wanted to know about hashtables

ここでは、一歩下がって、ハッシュテーブルについて説明します。I want to take a step back and talk about hashtables. 私は現在は常に使用しています。I use them all the time now. 昨晩ユーザー グループミーティングの後、これに関してあるユーザーに説明していたとき、私にも同じ混乱が生じていたことに気付きました。I was teaching someone about them after our user group meeting last night and I realized I had the same confusion about them as he had. ハッシュテーブルは PowerShell で非常に重要であるため、十分に理解しておくことをお勧めします。Hashtables are really important in PowerShell so it's good to have a solid understanding of them.

注意

この記事のオリジナル バージョンは、@KevinMarquette 氏のブログ記事です。The original version of this article appeared on the blog written by @KevinMarquette. このコンテンツを共有してくださった Kevin 氏に、PowerShell チームより感謝を申し上げます。The PowerShell team thanks Kevin for sharing this content with us. PowerShellExplained.com のブログをご確認ください。Please check out his blog at PowerShellExplained.com.

もののコレクションとしてのハッシュテーブルHashtable as a collection of things

まず、ハッシュテーブルの従来の定義どおり、ハッシュテーブルをコレクションとして見てください。I want you to first see a Hashtable as a collection in the traditional definition of a hashtable. この定義により、後でより高度なことに使用する場合の動作に関する基本的な理解が得られます。This definition gives you a fundamental understanding of how they work when they get used for more advanced stuff later. この理解をスキップすることは、多くの場合、混乱の原因になります。Skipping this understanding is often a source of confusion.

配列とはWhat is an array?

ハッシュテーブルとは何かを説明する前に、まず配列について説明する必要があります。Before I jump into what a Hashtable is, I need to mention arrays first. このディスカッションでは、配列は値またはオブジェクトのリストまたはコレクションです。For the purpose of this discussion, an array is a list or collection of values or objects.

$array = @(1,2,3,5,7,11)

項目を配列に格納したら、foreach を使用してリストを反復処理するか、インデックスを使用して配列内の個々の要素にアクセスすることができます。Once you have your items into an array, you can either use foreach to iterate over the list or use an index to access individual elements in the array.

foreach($item in $array)
{
    Write-Output $item
}

Write-Output $array[3]

また、同じ方法でインデックスを使用して値を更新することもできます。You can also update values using an index in the same way.

$array[2] = 13

配列のほんの一部に触れただけですが、ハッシュテーブルに進むときに、配列を適切なコンテキストに組み込んでくれるはずです。I just scratched the surface on arrays but that should put them into the right context as I move onto hashtables.

ハッシュテーブルとはWhat is a hashtable?

まず、一般的な意味でハッシュテーブルとは何かについて基本的な技術説明を行った後、PowerShell でのその他の使用方法に移ります。I'm going to start with a basic technical description of what hashtables are, in the general sense, before I shift into the other ways PowerShell uses them.

ハッシュテーブルは配列によく似たデータ構造ですが、キーを使用して各値 (オブジェクト) を格納する点が異なります。A hashtable is a data structure, much like an array, except you store each value (object) using a key. これは基本的なキー/値ストアです。It's a basic key/value store. まず、空のハッシュテーブルを作成します。First, we create an empty hashtable.

$ageList = @{}

ハッシュテーブルを定義するには、かっこではなく中かっこを使用することに注意してください。Notice that braces, instead of parentheses, are used to define a hashtable. その後、次のようなキーを使用して項目を追加します。Then we add an item using a key like this:

$key = 'Kevin'
$value = 36
$ageList.add( $key, $value )

$ageList.add( 'Alex', 9 )

人の名前がキーで、その年齢が保存する値です。The person's name is the key and their age is the value that I want to save.

アクセスに角かっこを使用するUsing the brackets for access

ハッシュテーブルに値を追加したら、同じキーを使用してその値を取り出すことができます (配列の場合のように数値インデックスを使用するのではなく)。Once you add your values to the hashtable, you can pull them back out using that same key (instead of using a numeric index like you would have for an array).

$ageList['Kevin']
$ageList['Alex']

Kevin の年齢を取得したい場合は、彼の名前を使用してアクセスします。When I want Kevin's age, I use his name to access it. このアプローチを使用して、ハッシュテーブルの値の追加または更新もできます。We can use this approach to add or update values into the hashtable too. これは、上記の add() 関数を使用する場合と似ています。This is just like using the add() function above.

$ageList = @{}

$key = 'Kevin'
$value = 36
$ageList[$key] = $value

$ageList['Alex'] = 9

値にアクセスして更新するために使用できるもう 1 つの構文があり、それについては後のセクションで説明します。There's another syntax you can use for accessing and updating values that I'll cover in a later section. 別の言語から PowerShell に移ってきたユーザーにとって、これらの例は、これまでハッシュテーブルを使用してきた方法に合致しているでしょう。If you're coming to PowerShell from another language, these examples should fit in with how you may have used hashtables before.

値を含むハッシュテーブルの作成Creating hashtables with values

ここまでの例では、空のハッシュテーブルを作成しました。So far I've created an empty hashtable for these examples. 作成時にキーと値をあらかじめ設定できます。You can pre-populate the keys and values when you create them.

$ageList = @{
    Kevin = 36
    Alex  = 9
}

ルックアップ テーブルとしてAs a lookup table

この種類のハッシュテーブルは、ルックアップ テーブルとして使用できることに真の価値があります。The real value of this type of a hashtable is that you can use them as a lookup table. 単純な例を次に示します。Here is a simple example.

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

$server = $environments[$env]

この例では、$env 変数に環境を指定することで、適切なサーバーが選択されます。In this example, you specify an environment for the $env variable and it will pick the correct server. このような選択には switch($env){...} を使用することもできますが、ハッシュテーブルは便利なオプションです。You could use a switch($env){...} for a selection like this but a hashtable is a nice option.

これは、後で使用するためにルックアップ テーブルを動的に構築する場合に、さらに優れています。This gets even better when you dynamically build the lookup table to use it later. したがって、何かを相互参照する必要がある場合は、このアプローチの使用を検討してください。So think about using this approach when you need to cross reference something. PowerShell での Where-Object によるパイプのフィルター処理がそれほど優れていなければ、これをさらに多く目にするだろうと思います。I think we would see this even more if PowerShell wasn't so good at filtering on the pipe with Where-Object. パフォーマンスが重要になる状況では、このアプローチを検討する必要があります。If you're ever in a situation where performance matters, this approach needs to be considered.

より高速であるとは言えませんが、パフォーマンスが重要ならテストせよという規則に適合します。I won't say that it's faster, but it does fit into the rule of If performance matters, test it.

複数選択Multiselection

一般に、ハッシュテーブルはキーと値のペアと考えることができます。そこでは、1 つのキーを指定し、1 つの値を取得します。Generally, you think of a hashtable as a key/value pair, where you provide one key and get one value. PowerShell では、複数の値を取得するためにキーの配列を指定できます。PowerShell allows you to provide an array of keys to get multiple values.

$environments[@('QA','DEV')]
$environments[('QA','DEV')]
$environments['QA','DEV']

この例では、上記と同じルックアップ ハッシュテーブルを使用し、3 つの異なる配列スタイルを指定して一致項目を取得します。In this example, I use the same lookup hashtable from above and provide three different array styles to get the matches. これは、ほとんどの方が気付いていない PowerShell の隠された宝石です。This is a hidden gem in PowerShell that most people aren't aware of.

ハッシュテーブルの反復処理Iterating hashtables

ハッシュテーブルはキーと値のペアのコレクションであるため、配列または通常の項目リストとは異なる方法で反復処理します。Because a hashtable is a collection of key/value pairs, you iterate over it differently than you do for an array or a normal list of items.

まず注目すべき点は、ハッシュテーブルをパイプ処理するとき、ハッシュテーブルは 1 つのオブジェクトのように扱われるということです。The first thing to notice is that if you pipe your hashtable, the pipe treats it like one object.

PS> $ageList | Measure-Object
count : 1

ただし、それに含まれる値の数は .count プロパティでわかります。Even though the .count property tells you how many values it contains.

PS> $ageList.count
2

値だけが必要な場合は、.values プロパティを使用してこの問題を回避します。You get around this issue by using the .values property if all you need is just the values.

PS> $ageList.values | Measure-Object -Average
Count   : 2
Average : 22.5

多くの場合、キーを列挙し、それらを使用して値にアクセスする方が便利です。It's often more useful to enumerate the keys and use them to access the values.

PS> $ageList.keys | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_, $ageList[$_]
    Write-Output $message
}
Kevin is 36 years old
Alex is 9 years old

foreach(){...} ループを使用した同じ例を次に示します。Here is the same example with a foreach(){...} loop.

foreach($key in $ageList.keys)
{
    $message = '{0} is {1} years old' -f $key, $ageList[$key]
    Write-Output $message
}

ハッシュテーブルの各キーを順に調べ、それを使用して値にアクセスします。We are walking each key in the hashtable and then using it to access the value. これは、ハッシュテーブルをコレクションとして使用する場合の一般的なパターンです。This is a common pattern when working with hashtables as a collection.

GetEnumerator()GetEnumerator()

ここで、ハッシュテーブルを反復処理するために GetEnumerator() を使用します。That brings us to GetEnumerator() for iterating over our hashtable.

$ageList.GetEnumerator() | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_.key, $_.value
    Write-Output $message
}

この列挙子は、各キーと値のペアを 1 つずつ渡してくれます。The enumerator gives you each key/value pair one after another. このユース ケース専用に設計されています。It was designed specifically for this use case. このことを思い出させてくれた Mark Kraus さんに感謝いたします。Thank you to Mark Kraus for reminding me of this one.

BadEnumerationBadEnumeration

重要な詳細の 1 つは、列挙中はハッシュテーブルを変更できないことです。One important detail is that you can't modify a hashtable while it's being enumerated. 基本的な $environments の例から始めます。If we start with our basic $environments example:

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

すべてのキーを同じサーバー値に設定しようとすると失敗します。And trying to set every key to the same server value fails.

$environments.Keys | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

An error occurred while enumerating through a collection: Collection was modified; enumeration operation may not execute.
+ CategoryInfo          : InvalidOperation: tableEnumerator:HashtableEnumerator) [], RuntimeException
+ FullyQualifiedErrorId : BadEnumeration

これも問題ないように見えますが、やはり失敗します。This will also fail even though it looks like it should also be fine:

foreach($key in $environments.keys) {
    $environments[$key] = 'SrvDev03'
}

Collection was modified; enumeration operation may not execute.
    + CategoryInfo          : OperationStopped: (:) [], InvalidOperationException
    + FullyQualifiedErrorId : System.InvalidOperationException

この状況に対処する秘訣は、列挙を行う前にキーを複製することです。The trick to this situation is to clone the keys before doing the enumeration.

$environments.Keys.Clone() | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

プロパティのコレクションとしてのハッシュテーブルHashtable as a collection of properties

ここまでは、ハッシュテーブルに配置したオブジェクトの型はすべて同じ型のオブジェクトでした。So far the type of objects we placed in our hashtable were all the same type of object. これらのすべての例で年齢を使用し、キーは人の名前でした。I used ages in all those examples and the key was the person's name. これは、それぞれに名前が付いているオブジェクトのコレクションでは、それを確認するための優れた方法です。This is a great way to look at it when your collection of objects each have a name. PowerShell でハッシュテーブルを使用するもう 1 つの一般的な方法は、プロパティの名前をキーとして持つプロパティのコレクションを保持することです。Another common way to use hashtables in PowerShell is to hold a collection of properties where the key is the name of the property. 次の例では、このことについて説明します。I'll step into that idea in this next example.

プロパティベースのアクセスProperty-based access

プロパティベースのアクセスを使用すると、ハッシュテーブルのダイナミクスと PowerShell での使用方法が変わります。The use of property-based access changes the dynamics of hashtables and how you can use them in PowerShell. 上記の例で、キーをプロパティとして扱う例を次に示します。Here is our usual example from above treating the keys as properties.

$ageList = @{}
$ageList.Kevin = 35
$ageList.Alex = 9

上記の例と同じように、この例では、まだハッシュテーブルに存在しないキーは追加されます。Just like the examples above, this example adds those keys if they don't exist in the hashtable already. キーの定義方法と値の種類によって、これは少し奇妙なものか、最高の選択肢になります。Depending on how you defined your keys and what your values are, this is either a little strange or a perfect fit. この時点までは、年齢リストの例はうまく機能しています。The age list example has worked great up until this point. 納得して先へ進むには、このための新しい例が必要です。We need a new example for this to feel right going forward.

$person = @{
    name = 'Kevin'
    age  = 36
}

このように $person に属性を追加してアクセスすることもできます。And we can add and access attributes on the $person like this.

$person.city = 'Austin'
$person.state = 'TX'

このハッシュテーブルは、突然、オブジェクトのように機能し始めます。All of a sudden this hashtable starts to feel and act like an object. まだもののコレクションであるため、上記のすべての例が引き続き適用されます。It's still a collection of things, so all the examples above still apply. ここでは、別の観点からアプローチします。We just approach it from a different point of view.

キーと値の確認Checking for keys and values

ほとんどの場合、次のような方法で値をテストできます。In most cases, you can just test for the value with something like this:

if( $person.age ){...}

これは単純ですが、私はこのロジックで 1 つの重要な詳細を見落とししていたため、多くのバグの原因になっていました。It's simple but has been the source of many bugs for me because I was overlooking one important detail in my logic. キーが存在するかどうかをテストするために使用しました。I started to use it to test if a key was present. 値が $false または 0 の場合、そのステートメントは予期せず $false を返しました。When the value was $false or zero, that statement would return $false unexpectedly.

if( $person.age -ne $null ){...}

これは、0 の値に対してはこの問題を回避しますが、$null と存在しないキーの比較では回避できません。This works around that issue for zero values but not $null vs non-existent keys. ほとんどの場合、この区別を行う必要はありませんが、区別が必要な場合にはそのための関数があります。Most of the time you don't need to make that distinction but there are functions for when you do.

if( $person.ContainsKey('age') ){...}

また、キーがわからない場合に値をテストする、またはコレクション全体を反復処理しながら値をテストする必要がある状況では、ContainsValue() を使用できます。We also have a ContainsValue() for the situation where you need to test for a value without knowing the key or iterating the whole collection.

キーの削除とクリアRemoving and clearing keys

.Remove() 関数を使用してキーを削除できます。You can remove keys with the .Remove() function.

$person.remove('age')

$null 値を割り当てると、$null 値を持つキーが残ります。Assigning them a $null value just leaves you with a key that has a $null value.

ハッシュテーブルをクリアする一般的な方法は、空のハッシュテーブルに初期化することです。A common way to clear a hashtable is to just initialize it to an empty hashtable.

$person = @{}

それでも機能しますが、代わりに clear() 関数を使用してみてください。While that does work, try to use the clear() function instead.

$person.clear()

これは、関数を使用することで自己文書化コードを実現し、コードの意図を明確にできる事例の 1 つです。This is one of those instances where using the function creates self-documenting code and it makes the intentions of the code very clean.

すべての楽しいものAll the fun stuff

順序付きハッシュテーブルOrdered hashtables

既定では、ハッシュテーブルは順序付け (または並べ替え) されていません。By default, hashtables aren't ordered (or sorted). 従来のコンテキストでは、常にキーを使用して値にアクセスする場合、順序は重要ではありません。In the traditional context, the order doesn't matter when you always use a key to access values. プロパティを定義した順序で維持する必要が生じる場合があります。You may find that you want the properties to stay in the order that you define them. 幸いにも、ordered キーワードを使用してこれを行う方法があります。Thankfully, there's a way to do that with the ordered keyword.

$person = [ordered]@{
    name = 'Kevin'
    age  = 36
}

これで、キーと値を列挙すると、その順序が維持されるようになります。Now when you enumerate the keys and values, they stay in that order.

インライン ハッシュテーブルInline hashtables

1 行にハッシュテーブルを定義する場合は、キーと値のペアをセミコロンで区切ることができます。When you're defining a hashtable on one line, you can separate the key/value pairs with a semicolon.

$person = @{ name = 'kevin'; age = 36; }

これは、パイプで作成する場合に便利です。This will come in handy if you're creating them on the pipe.

一般的なパイプライン コマンドのカスタム式Custom expressions in common pipeline commands

ハッシュテーブルを使用してカスタム プロパティや計算されるプロパティを作成するためのコマンドレットがいくつかあります。There are a few cmdlets that support the use of hashtables to create custom or calculated properties. これは一般的に Select-ObjectFormat-Table で使用されます。You commonly see this with Select-Object and Format-Table. ハッシュテーブルには特殊な構文があり、完全に展開すると次のようなものです。The hashtables have a special syntax that looks like this when fully expanded.

$property = @{
    name = 'totalSpaceGB'
    expression = { ($_.used + $_.free) / 1GB }
}

name は、コマンドレットがその列に付けるラベルです。The name is what the cmdlet would label that column. expression は実行されるスクリプト ブロックで、$_ はパイプのオブジェクトの値です。The expression is a script block that is executed where $_ is the value of the object on the pipe. このスクリプトの動作は次のとおりです。Here is that script in action:

$drives = Get-PSDrive | Where Used
$drives | Select-Object -Properties name, $property

Name     totalSpaceGB
----     ------------
C    238.472652435303

変数に配置しましたが、インラインで簡単に定義することもできます。また、namen に短縮し、expressione に短縮できます。I placed that in a variable but it could easily be defined inline and you can shorten name to n and expression to e while you're at it.

$drives | Select-Object -properties name, @{n='totalSpaceGB';e={($_.used + $_.free) / 1GB}}

コマンドが長くなるうえ、説明は省きますが問題のある行動を助長させることも多いため、個人的には好きではありません。I personally don't like how long that makes commands and it often promotes some bad behaviors that I won't get into. 私なら、スクリプトでこのアプローチを使用する代わりに、必要なすべてのフィールドとプロパティを含む新しいハッシュテーブルまたは pscustomobject を作成するでしょう。I'm more likely to create a new hashtable or pscustomobject with all the fields and properties that I want instead of using this approach in scripts. しかし、これを行うコードはたくさんあるため、認識していただくことが目的でした。But there's a lot of code out there that does this so I wanted you to be aware of it. pscustomobject の作成については後で説明します。I talk about creating a pscustomobject later on.

カスタムの並べ替え式Custom sort expression

並べ替えの対象となるデータがオブジェクトに含まれている場合は、コレクションを簡単に並べ替えることができます。It's easy to sort a collection if the objects have the data that you want to sort on. 並べ替える前にオブジェクトにデータを追加するか、Sort-Object のカスタム式を作成することができます。You can either add the data to the object before you sort it or create a custom expression for Sort-Object.

Get-ADUser | Sort-Object -Parameter @{ e={ Get-TotalSales $_.Name } }

この例では、ユーザーの一覧を取得し、いくつかのカスタム コマンドレットを使用して、並べ替えのためだけに追加情報を取得しています。In this example I'm taking a list of users and using some custom cmdlet to get additional information just for the sort.

ハッシュテーブルの一覧の並べ替えSort a list of Hashtables

ハッシュテーブルの一覧を並べ替える場合、Sort-Object ではキーがプロパティとして扱われないことがわかります。If you have a list of hashtables that you want to sort, you'll find that the Sort-Object doesn't treat your keys as properties. カスタムの並べ替え式を使用することで、これを回避できます。We can get a round that by using a custom sort expression.

$data = @(
    @{name='a'}
    @{name='c'}
    @{name='e'}
    @{name='f'}
    @{name='d'}
    @{name='b'}
)

$data | Sort-Object -Property @{e={$_.name}}

コマンドレットでのハッシュテーブルのスプラッティングSplatting hashtables at cmdlets

これはハッシュテーブルに関する私のお気に入りの 1 つで、早い段階で気付く人は少ないでしょう。This is one of my favorite things about hashtables that many people don't discover early on. すべてのプロパティを 1 行でコマンドレットに渡すのではなく、まずハッシュテーブルにパッケージ化するというアイデアです。The idea is that instead of providing all the properties to a cmdlet on one line, you can instead pack them into a hashtable first. その後、特別な方法でハッシュテーブルを関数に渡すことができます。Then you can give the hashtable to the function in a special way. 通常の方法で DHCP スコープを作成する例を次に示します。Here is an example of creating a DHCP scope the normal way.

Add-DhcpServerv4Scope -Name 'TestNetwork' -StartRange'10.0.0.2' -EndRange '10.0.0.254' -SubnetMask '255.255.255.0' -Description 'Network for testlab A' -LeaseDuration (New-TimeSpan -Days 8) -Type "Both"

スプラッティングを使用しない場合は、これらすべてを 1 行で定義する必要があります。Without using splatting, all those things need to be defined on a single line. 画面の外にはみ出るか、適当な場所で折り返されます。It either scrolls off the screen or will wrap where ever it feels like. ここで、スプラッティングを使用するコマンドと比較します。Now compare that to a command that uses splatting.

$DHCPScope = @{
    Name        = 'TestNetwork'
    StartRange  = '10.0.0.2'
    EndRange    = '10.0.0.254'
    SubnetMask  = '255.255.255.0'
    Description = 'Network for testlab A'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type = "Both"
}
Add-DhcpServerv4Scope @DHCPScope

$ ではなく @ 記号を使用することで、スプラッティング操作が呼び出されます。The use of the @ sign instead of the $ is what invokes the splat operation.

少し時間を取って、その例がいかに読みやすいかを確認してください。Just take a moment to appreciate how easy that example is to read. これらはすべて同じ値を使用したまったく同じコマンドです。They are the exact same command with all the same values. 2 つ目の方法は、理解しやすく、今後も維持しやすくなっています。The second one is easier to understand and maintain going forward.

コマンドが長くなりすぎる場合、私は常にスプラッティングを使用します。I use splatting anytime the command gets too long. 定義が長すぎると、ウィンドウが右にスクロールします。I define too long as causing my window to scroll right. 関数に 3 つのプロパティを入力する場合は、スプラッティングされたハッシュテーブルを使用して書き直す方がよいでしょう。If I hit three properties for a function, odds are that I'll rewrite it using a splatted hashtable.

省略可能なパラメーターのスプラッティングSplatting for optional parameters

私がスプラッティングをよく使用する方法の 1 つは、スクリプト内の他の場所から取得された省略可能なパラメーターを処理することです。One of the most common ways I use splatting is to deal with optional parameters that come from some place else in my script. たとえば、省略可能な $Credential 引数を持つ Get-CIMInstance 呼び出しをラップする関数があるとします。Let's say I have a function that wraps a Get-CIMInstance call that has an optional $Credential argument.

$CIMParams = @{
    ClassName = 'Win32_Bios'
    ComputerName = $ComputerName
}

if($Credential)
{
    $CIMParams.Credential = $Credential
}

Get-CIMInstance @CIMParams

まず、共通パラメーターを使用してハッシュテーブルを作成します。I start by creating my hashtable with common parameters. $Credential が存在する場合は追加します。Then I add the $Credential if it exists. ここではスプラッティングを使用しているので、コード内で Get-CIMInstance を 1 回呼び出すだけで済みます。Because I'm using splatting here, I only need to have the call to Get-CIMInstance in my code once. この設計パターンは非常にクリーンであり、多数の省略可能なパラメーターを簡単に処理できます。This design pattern is very clean and can handle lots of optional parameters easily.

実際には、パラメーターの $null 値を許可するようにコマンドを記述できるでしょう。To be fair, you could write your commands to allow $null values for parameters. 呼び出している他のコマンドを常に制御できるわけではありません。You just don't always have control over the other commands you're calling.

複数スプラッティングMultiple splats

複数のハッシュテーブルを同じコマンドレットにスプラッティングできます。You can splat multiple hashtables to the same cmdlet. 元のスプラッティングの例を見直すと、次のようになります。If we revisit our original splatting example:

$Common = @{
    SubnetMask  = '255.255.255.0'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type = "Both"
}

$DHCPScope = @{
    Name        = 'TestNetwork'
    StartRange  = '10.0.0.2'
    EndRange    = '10.0.0.254'
    Description = 'Network for testlab A'
}

Add-DhcpServerv4Scope @DHCPScope @Common

多くのコマンドに渡されるパラメーターの共通セットがある場合に、私はこの方法を使用します。I'll use this method when I have a common set of parameters that I'm passing to lots of commands.

クリーンなコードのためのスプラッティングSplatting for clean code

コードをすっきりさせることができるなら、1 つのパラメーターをスプラッティングしてもかまいません。There's nothing wrong with splatting a single parameter if makes you code cleaner.

$log = @{Path = '.\logfile.log'}
Add-Content "logging this command" @log

実行可能ファイルのスプラッティングSplatting executables

スプラッティングは、/param:value 構文を使用する一部の実行可能ファイルでも機能します。Splatting also works on some executables that use a /param:value syntax. たとえば、Robocopy.exe には、次のようないくつかのパラメーターがあります。Robocopy.exe, for example, has some parameters like this.

$robo = @{R=1;W=1;MT=8}
robocopy source destination @robo

これは役に立つかどうかはわかりませんが、興味深いと思いました。I don't know that this is all that useful, but I found it interesting.

ハッシュテーブルの追加Adding hashtables

ハッシュテーブルでは、2 つのハッシュテーブルを結合する加算演算子がサポートされています。Hashtables support the addition operator to combine two hashtables.

$person += @{Zip = '78701'}

これは、2 つのハッシュテーブルがキーを共有していない場合にのみ機能します。This only works if the two hashtables don't share a key.

入れ子になったハッシュテーブルNested hashtables

ハッシュテーブルは、ハッシュテーブル内の値として使用できます。We can use hashtables as values inside a hashtable.

$person = @{
    name = 'Kevin'
    age  = 36
}
$person.location = @{}
$person.location.city = 'Austin'
$person.location.state = 'TX'

ここでは、2 つのキーを含む基本的なハッシュテーブルから始めました。I started with a basic hashtable containing two keys. 空のハッシュテーブルを持つ location という名前のキーを追加しました。I added a key called location with an empty hashtable. 次に、その location ハッシュテーブルに、最後の 2 つの項目を追加しました。Then I added the last two items to that location hashtable. これをすべてインラインで行うこともできます。We can do this all inline too.

$person = @{
    name = 'Kevin'
    age  = 36
    location = @{
        city  = 'Austin'
        state = 'TX'
    }
}

これにより、上記と同じハッシュテーブルが作成され、同じ方法でプロパティにアクセスできます。This creates the same hashtable that we saw above and can access the properties the same way.

$person.location.city
Austin

オブジェクトの構造にはさまざまな方法で対処できます。There are many ways to approach the structure of your objects. 入れ子になったハッシュテーブルを確認するもう 1 つの方法です。Here is a second way to look at a nested hashtable.

$people = @{
    Kevin = @{
        age  = 36
        city = 'Austin'
    }
    Alex = @{
        age  = 9
        city = 'Austin'
    }
}

ここには、ハッシュテーブルをオブジェクトのコレクションとして使用する概念と、プロパティのコレクションとして使用する概念が混在しています。This mixes the concept of using hashtables as a collection of objects and a collection of properties. これらの値は、入れ子になっている場合でも、任意の方法で簡単にアクセスできます。The values are still easy to access even when they're nested using whatever approach you prefer.

PS> $people.kevin.age
36
PS> $people.kevin['city']
Austin
PS> $people['Alex'].age
9
PS> $people['Alex']['City']
Austin

プロパティのように扱うときは、私はドット プロパティを使用する傾向があります。I tend to use the dot property when I'm treating it like a property. これらは一般に、コード内で静的に定義してあり、すぐに思い出せるものです。Those are generally things I've defined statically in my code and I know them off the top of my head. 一覧を調べたり、キーにプログラムでアクセスしたりする必要がある場合は、角かっこを使用してキー名を指定します。If I need to walk the list or programmatically access the keys, I use the brackets to provide the key name.

foreach($name in $people.keys)
{
    $person = $people[$name]
    '{0}, age {1}, is in {2}' -f $name, $person.age, $person.city
}

ハッシュテーブルを入れ子にできることにより、柔軟性と選択の幅が大きく向上します。Having the ability to nest hashtables gives you a lot of flexibility and options.

入れ子になったハッシュテーブルの確認Looking at nested hashtables

ハッシュテーブルを入れ子にし始めるとすぐに、コンソールから簡単に確認する方法が必要になります。As soon as you start nesting hashtables, you're going to need an easy way to look at them from the console. たとえば最後のハッシュテーブルでは、次のような出力が得られます。If I take that last hashtable, I get an output that looks like this and it only goes so deep:

PS> $people
Name                           Value
----                           -----
Kevin                          {age, city}
Alex                           {age, city}

私がこれらを確認するために使用する go to コマンドは ConvertTo-JSON です。これは非常にクリーンであり、私は他の処理で JSON を頻繁に使用するためです。My go to command for looking at these things is ConvertTo-JSON because it's very clean and I frequently use JSON on other things.

PS> $people | ConvertTo-Json
{
    "Kevin":  {
                "age":  36,
                "city":  "Austin"
            },
    "Alex":  {
                "age":  9,
                "city":  "Austin"
            }
}

JSON について知らない場合でも、探しているものを確認できます。Even if you don't know JSON, you should be able to see what you're looking for. このような構造化データ用には Format-Custom コマンドがありますが、私は JSON ビューの方が好きです。There's a Format-Custom command for structured data like this but I still like the JSON view better.

オブジェクトの作成Creating objects

オブジェクトが必要な場合に、ハッシュテーブルを使用してプロパティを保持するだけでは目的を達成できないことがあります。Sometimes you just need to have an object and using a hashtable to hold properties just isn't getting the job done. キーを列名として表示したい場合がよくあります。Most commonly you want to see the keys as column names. pscustomobject を使用すると、簡単に実現できます。A pscustomobject makes that easy.

$person = [pscustomobject]@{
    name = 'Kevin'
    age  = 36
}

$person

name  age
----  ---
Kevin  36

最初から pscustomobject として作成していない場合でも、必要に応じていつでもキャストできます。Even if you don't create it as a pscustomobject initially, you can always cast it later when needed.

$person = @{
    name = 'Kevin'
    age  = 36
}

[pscustomobject]$person

name  age
----  ---
Kevin  36

pscustomobject については詳細な記事を既に書いたので、この記事の後で読むことをお勧めします。I already have detailed write-up for pscustomobject that you should go read after this one. ここで学習したさまざまな要素に基づいて書かれています。It builds on a lot of the things learned here.

ファイルに対するハッシュテーブルの読み取りと書き込みReading and writing hashtables to file

CSV に保存するSaving to CSV

ハッシュテーブルを CSV に保存するために苦労することは、上記で言及した問題の 1 つです。Struggling with getting a hashtable to save to a CSV is one of the difficulties that I was referring to above. ハッシュテーブルを pscustomobject に変換すると、CSV に正しく保存されます。Convert your hashtable to a pscustomobject and it will save correctly to CSV. 列の順序が保持されるように pscustomobject から始めておけば役立ちます。It helps if you start with a pscustomobject so the column order is preserved. ただし、必要な場合はインラインで pscustomobject にキャストできます。But you can cast it to a pscustomobject inline if needed.

$person | ForEach-Object{ [pscustomobject]$_ } | Export-CSV -Path $path

これについても、pscustomobject の使用に関する記事をご覧ください。Again, check out my write-up on using a pscustomobject.

入れ子になったハッシュテーブルをファイルに保存するSaving a nested hashtable to file

入れ子になったハッシュテーブルをファイルに保存し、再度読み取る必要がある場合は、JSON コマンドレットを使用します。If I need to save a nested hashtable to a file and then read it back in again, I use the JSON cmdlets to do it.

$people | ConvertTo-JSON | Set-Content -Path $path
$people = Get-Content -Path $path -Raw | ConvertFrom-JSON

この方法には重要な点が 2 つあります。There are two important points about this method. 1 点目は、JSON は複数行に書き出されるため、-Raw オプションを使用して 1 つの文字列に読み込み直す必要があるということです。First is that the JSON is written out multiline so I need to use the -Raw option to read it back into a single string. 2 点目は、インポートされたオブジェクトは [hashtable] ではなくなるということです。The Second is that the imported object is no longer a [hashtable]. これは [pscustomobject] であり、予期していない場合は問題が発生する可能性があります。It's now a [pscustomobject] and that can cause issues if you don't expect it.

深い入れ子になったハッシュテーブルを監視します。Watch for deeply-nested hashtables. JSON に変換した場合、期待どおりの結果が得られないことがあります。When you convert it to JSON you might not get the results you expect.

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json

{
  "a": {
    "b": {
      "c": "System.Collections.Hashtable"
    }
  }
}

Depth パラメーターを使用して、入れ子になったすべてのハッシュテーブルが展開されていることを確認します。Use Depth parameter to ensure that you have expanded all the nested hashtables.

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json -Depth 3

{
  "a": {
    "b": {
      "c": {
        "d": "e"
      }
    }
  }
}

インポート時に [hashtable] であることが必要な場合は、Export-CliXml および Import-CliXml コマンドを使用する必要があります。If you need it to be a [hashtable] on import, then you need to use the Export-CliXml and Import-CliXml commands.

JSON をハッシュテーブルに変換するConverting JSON to Hashtable

JSON を [hashtable] に変換する必要がある場合に、.NET で JavaScriptSerializer を使用して実行する方法を 1 つ知っています。If you need to convert JSON to a [hashtable], there's one way that I know of to do it with the JavaScriptSerializer in .NET.

[Reflection.Assembly]::LoadWithPartialName("System.Web.Script.Serialization")
$JSSerializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new()
$JSSerializer.Deserialize($json,'Hashtable')

PowerShell v6 以降では、JSON サポートに NewtonSoft JSON.NET が使用され、ハッシュテーブルのサポートが追加されます。Beginning in PowerShell v6, JSON support uses the NewtonSoft JSON.NET and adds hashtable support.

'{ "a": "b" }' | ConvertFrom-Json -AsHashtable

Name      Value
----      -----
a         b

PowerShell 6.2 が ConvertFrom-JsonDepth パラメーターを追加しました。PowerShell 6.2 added the Depth parameter to ConvertFrom-Json. 既定の Depth は 1024 です。The default Depth is 1024.

ファイルから直接読み取るReading directly from a file

PowerShell 構文を使用しているハッシュテーブルを含むファイルがある場合は、それを直接インポートする方法があります。If you have a file that contains a hashtable using PowerShell syntax, there's a way to import it directly.

$content = Get-Content -Path $Path -Raw -ErrorAction Stop
$scriptBlock = [scriptblock]::Create( $content )
$scriptBlock.CheckRestrictedLanguage( $allowedCommands, $allowedVariables, $true )
$hashtable = ( & $scriptBlock )

ファイルの内容を scriptblock にインポートした後、それを実行する前に、他の PowerShell コマンドが含まれていないことを確認します。It imports the contents of the file into a scriptblock, then checks to make sure it doesn't have any other PowerShell commands in it before it executes it.

それに関連して、モジュール マニフェスト (psd1 ファイル) はハッシュテーブルであることを知っていましたか。On that note, did you know that a module manifest (the psd1 file) is just a hashtable?

キーには任意のオブジェクトを指定できます。Keys can be any object

ほとんどの場合、キーは文字列にすぎません。Most of the time, the keys are just strings. したがって、任意のものを引用符で囲んで、キーにすることができます。So we can put quotes around anything and make it a key.

$person = @{
    'full name' = 'Kevin Marquette'
    '#' = 3978
}
$person['full name']

気付いていないかもしれませんが、いくつかの奇妙なことを行うことができます。You can do some odd things that you may not have realized you could do.

$person.'full name'

$key = 'full name'
$person.$key

何かを行うことができるからといって、推奨されるわけではありません。Just because you can do something, it doesn't mean that you should. 最後のものは、バグが発生するのを待っているようです。このコードを読む人によって簡単に誤解されるでしょう。That last one just looks like a bug waiting to happen and would be easily misunderstood by anyone reading your code.

厳密に言うと、キーは文字列である必要はありませんが、文字列のみを使用する方が考えやすくなります。Technically your key doesn't have to be a string but they're easier to think about if you only use strings. ただし、インデックス作成は複雑なキーでは適切に機能しません。However, indexing doesn't work well with the complex keys.

$ht = @{ @(1,2,3) = "a" }
$ht

Name                           Value
----                           -----
{1, 2, 3}                      a

キーによってハッシュテーブルの値にアクセスするのは、常に機能するわけではありません。Accessing a value in the hashtable by its key doesn't always work. 次に例を示します。For example:

$key = $ht.keys[0]
$ht.$key
$ht[$key]
a

メンバー アクセス (.) 表記を使用すると、何も返されません。Using the member access (.) notation returns nothing. ただし、配列インデックス ([]) 表記を使用することはできます。But using the array index ([]) notation works.

自動変数での使用Use in automatic variables

$PSBoundParameters$PSBoundParameters

[$PSBoundParameters][] は、関数のコンテキスト内にのみ存在する自動変数です。[$PSBoundParameters][] is an automatic variable that only exists inside the context of a function. これには、関数の呼び出し時に指定されたすべてのパラメーターが含まれます。It contains all the parameters that the function was called with. これは厳密にはハッシュテーブルではありませんがよく似ているため、そのように扱うことができます。This isn't exactly a hashtable but close enough that you can treat it like one.

これには、キーの削除と他の関数へのスプラッティングが含まれます。That includes removing keys and splatting it to other functions. プロキシ関数を作成する場合は、これについて詳細を調べてください。If you find yourself writing proxy functions, take a closer look at this one.

詳細については、about_Automatic_Variablesに関するページを参照してください。See about_Automatic_Variables for more details.

PSBoundParameters の注意事項PSBoundParameters gotcha

覚えておくべき重要な点の 1 つは、これにはパラメーターとして渡された値のみが含まれることです。One important thing to remember is that this only includes the values that are passed in as parameters. 既定値を持つパラメーターもある場合、それらが呼び出し元から渡されないときは、$PSBoundParameters にはこれらの値が含まれていません。If you also have parameters with default values but aren't passed in by the caller, $PSBoundParameters doesn't contain those values. これはよく見落とされます。This is commonly overlooked.

$PSDefaultParameterValues$PSDefaultParameterValues

この自動変数を使用すると、コマンドレットを変更せずに、任意のコマンドレットに既定値を割り当てることができます。This automatic variable lets you assign default values to any cmdlet without changing the cmdlet. 次の例を見てみましょう。Take a look at this example.

$PSDefaultParameterValues["Out-File:Encoding"] = "UTF8"

これにより、UTF8Out-File -Encoding パラメーターの既定値として設定するエントリが $PSDefaultParameterValues ハッシュテーブルに追加されます。This adds an entry to the $PSDefaultParameterValues hashtable that sets UTF8 as the default value for the Out-File -Encoding parameter. これはセッション固有であるため、自分の $profile 内に置く必要があります。This is session-specific so you should place it in your $profile.

私は頻繁に入力する値を事前に割り当てるために、これをよく使用します。I use this often to pre-assign values that I type quite often.

$PSDefaultParameterValues[ "Connect-VIServer:Server" ] = 'VCENTER01.contoso.local'

また、ワイルドカードも使用できるため、値を一括して設定できます。This also accepts wildcards so you can set values in bulk. 次のような方法で使用できます。Here are some ways you can use that:

$PSDefaultParameterValues[ "Get-*:Verbose" ] = $true
$PSDefaultParameterValues[ "*:Credential" ] = Get-Credential

詳細については、Michael Sorens 氏による自動既定値に関するすばらしい記事を参照してください。For a more in-depth breakdown, see this great article on Automatic Defaults by Michael Sorens.

正規表現の $MatchesRegex $Matches

-match 演算子を使用すると、一致の結果を保持する $matches という名前の自動変数が作成されます。When you use the -match operator, an automatic variable called $matches is created with the results of the match. 正規表現に部分式がある場合は、それらの部分一致も一覧表示されます。If you have any sub expressions in your regex, those sub matches are also listed.

$message = 'My SSN is 123-45-6789.'

$message -match 'My SSN is (.+)\.'
$Matches[0]
$Matches[1]

名前付き一致Named matches

これは、ほとんどの人が知らないお気に入りの機能の 1 つです。This is one of my favorite features that most people don't know about. 名前付き正規表現照合を使用すると、一致項目に名前でアクセスできます。If you use a named regex match, then you can access that match by name on the matches.

$message = 'My Name is Kevin and my SSN is 123-45-6789.'

if($message -match 'My Name is (?<Name>.+) and my SSN is (?<SSN>.+)\.')
{
    $Matches.Name
    $Matches.SSN
}

上記の例では、(?<Name>.*) は名前付きの部分式です。In the example above, the (?<Name>.*) is a named sub expression. その後、この値は $Matches.Name プロパティに格納されます。This value is then placed in the $Matches.Name property.

Group-Object -AsHashtableGroup-Object -AsHashtable

Group-Object のあまり知られていない機能の 1 つとして、一部のデータセットをハッシュテーブルに変換できるという点があります。One little known feature of Group-Object is that it can turn some datasets into a hashtable for you.

Import-CSV $Path | Group-Object -AsHashtable -Property email

これにより、各行がハッシュテーブルに追加され、指定されたプロパティがそれにアクセスするためのキーとして使用されます。This will add each row into a hashtable and use the specified property as the key to access it.

ハッシュテーブルのコピーCopying Hashtables

覚えておくべき重要な点の 1 つは、ハッシュテーブルがオブジェクトであることです。One important thing to know is that hashtables are objects. 各変数は、オブジェクトへの参照にすぎません。And each variable is just a reference to an object. つまり、ハッシュテーブルの有効なコピーを作成するには、より多くの作業が必要になります。This means that it takes more work to make a valid copy of a hashtable.

参照型を割り当てるAssigning reference types

ハッシュテーブルが 1 つある場合に、それを 2 つ目の変数に割り当てると、両方の変数が同じハッシュテーブルを指します。When you have one hashtable and assign it to a second variable, both variables point to the same hashtable.

PS> $orig = @{name='orig'}
PS> $copy = $orig
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [copy]

一方の値を変更するともう一方の値も変更されるため、これらは同じであることがよくわかります。This highlights that they're the same because altering the values in one will also alter the values in the other. これは、ハッシュテーブルを他の関数に渡すときにも当てはまります。This also applies when passing hashtables into other functions. これらの関数がハッシュテーブルに変更を加えた場合、元のハッシュテーブルも変更されます。If those functions make changes to that hashtable, your original is also altered.

シャロー コピー、単一レベルShallow copies, single level

上記の例のような単純なハッシュテーブルがある場合は、.Clone() を使用してシャロー コピーを作成できます。If we have a simple hashtable like our example above, we can use .Clone() to make a shallow copy.

PS> $orig = @{name='orig'}
PS> $copy = $orig.Clone()
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [orig]

これにより、他方に影響を与えずに、一方に対していくつかの基本的な変更を行うことができます。This will allow us to make some basic changes to one that don't impact the other.

シャロー コピー、入れ子Shallow copies, nested

シャロー コピーと呼ばれる理由は、基本レベルのプロパティのみがコピーされるためです。The reason why it's called a shallow copy is because it only copies the base level properties. これらのプロパティのいずれかが参照型 (別のハッシュテーブルなど) である場合、入れ子になったオブジェクトは引き続き互いを指します。If one of those properties is a reference type (like another hashtable), then those nested objects will still point to each other.

PS> $orig = @{
        person=@{
            name='orig'
        }
    }
PS> $copy = $orig.Clone()
PS> $copy.person.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.person.name
PS> 'Orig: [{0}]' -f $orig.person.name

Copy: [copy]
Orig: [copy]

ハッシュテーブルを複製したにもかかわらず、person への参照は複製されていないことがわかります。So you can see that even though I cloned the hashtable, the reference to person wasn't cloned. 最初のハッシュテーブルにリンクされていない、本当の意味で 2 つ目のハッシュテーブルを作成するには、ディープ コピーを作成する必要があります。We need to make a deep copy to truly have a second hashtable that isn't linked to the first.

ディープ コピーDeep copies

この記事の執筆時点で、ハッシュテーブルのディープ コピーを作成する (そしてハッシュテーブルとして保持する) ための巧妙な方法を私は知りません。At the time of writing this, I'm not aware of any clever ways to just make a deep copy of a hashtable (and keep it as a hashtable). これについてはだれかが執筆する必要があります。That's just one of those things that someone needs to write. これを行う簡単な方法を次に示します。Here is a quick method to do that.

function Get-DeepClone
{
    [CmdletBinding()]
    param(
        $InputObject
    )
    process
    {
        if($InputObject -is [hashtable]) {
            $clone = @{}
            foreach($key in $InputObject.keys)
            {
                $clone[$key] = Get-DeepClone $InputObject[$key]
            }
            return $clone
        } else {
            return $InputObject
        }
    }
}

他の参照型または配列は処理されませんが、出発点として適しています。It doesn't handle any other reference types or arrays, but it's a good starting point.

その他Anything else?

この記事では、多岐にわたって手早く説明しました。I covered a lot of ground quickly. この記事を読むたびに、新しいことを学んだり、理解を深めたりしていただければ幸いです。My hope is that you walk away leaning something new or understanding it better every time you read this. この機能の全範囲について説明したので、すぐにはご自身に該当しない側面もあるでしょう。Because I covered the full spectrum of this feature, there are aspects that just may not apply to you right now. それはまったく問題ではなく、どの程度 PowerShell を使用するかに応じて予想されることです。That is perfectly OK and is kind of expected depending on how much you work with PowerShell.