次の方法で共有


ハッシュテーブルについて知りたかったすべてのこと

私は一歩戻って ハッシュテーブルについて話したいと思います。 私は今、それらを常に使用しています。 私は先週の夜のユーザーグループミーティングの後、彼らについて誰かに教えていましたが、私は彼と同じ混乱を抱えていることに気付きました。 ハッシュテーブルは PowerShell で非常に重要であるため、それらをしっかりと理解することをお勧めします。

この記事の 元のバージョン は、 @KevinMarquetteによって書かれたブログに掲載されました。 PowerShell チームは、このコンテンツを Microsoft と共有してくれた Kevin に感謝します。 PowerShellExplained.com で彼のブログをチェックしてください。

モノのコレクションとしてのハッシュテーブル

まず、 ハッシュテーブル の従来の定義でハッシュテーブルをコレクションとして見て欲しいと思います。 この定義により、後でより高度な機能に使用される場合の動作の基本的な理解が得られます。 この理解をスキップすることは、多くの場合、混乱の原因です。

配列とは

ハッシュテーブルとは何かについて飛び込む前に、まず配列について言及する必要があります。 この説明では、配列は値またはオブジェクトのリストまたはコレクションです。

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

項目を配列に格納したら、 foreach を使用してリストを反復処理するか、インデックスを使用して配列内の個々の要素にアクセスできます。

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

Write-Output $array[3]

同じ方法でインデックスを使用して値を更新することもできます。

$array[2] = 13

私は配列の表面を傷つけただけですが、ハッシュテーブルに移動するにつれて適切なコンテキストに入れる必要があります。

ハッシュテーブルとは

まず、PowerShell で使用される他の方法に移行する前に、ハッシュテーブルとは何かについて基本的な技術的な説明から始めます。

ハッシュテーブルは、キーを使用して各値 (オブジェクト) を格納する点を除き、配列とよく似たデータ構造です。 これは基本的なキーと値のストアです。 まず、空のハッシュテーブルを作成します。

$ageList = @{}

かっこではなく中かっこを使用してハッシュテーブルを定義していることに注意してください。 次に、次のようなキーを使用して項目を追加します。

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

$ageList.Add( 'Alex', 9 )

ユーザーの名前がキーであり、その年齢が保存する値です。

アクセスに角かっこを使用する

ハッシュテーブルに値を追加したら、配列のように数値インデックスを使用する代わりに、同じキーを使用して値をプルバックできます。

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

ケビンの年齢が必要な場合は、彼の名前を使用してアクセスします。 このアプローチを使用して、ハッシュテーブルに値を追加または更新することもできます。 これは、上記の Add() メソッドを使用するのと同じです。

$ageList = @{}

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

$ageList['Alex'] = 9

値へのアクセスと更新に使用できるもう 1 つの構文があります。これについては、後のセクションで説明します。 別の言語から PowerShell を使用する場合、これらの例は、以前にハッシュテーブルを使用した方法に適合している必要があります。

値を含むハッシュテーブルの作成

ここまで、これらの例用に空のハッシュテーブルを作成しました。 キーと値は、作成時に事前に設定できます。

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

ルックアップ テーブルとして

この種類のハッシュテーブルの実際の値は、参照テーブルとして使用できることです。 簡単な例を次に示します。

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

$server = $environments[$env]

この例では、 $env 変数の環境を指定すると、正しいサーバーが選択されます。 このような選択には switch($env){...} を使用できますが、ハッシュテーブルは便利なオプションです。

これは、後で参照テーブルを使用するために動的に作成する場合にさらに優れています。 したがって、何かを相互参照する必要がある場合は、このアプローチを使用することを検討してください。 PowerShell が Where-Objectを使用してパイプをフィルター処理する方法があまり得意でない場合は、これがさらに多く表示されると思います。 パフォーマンスが重要な状況が発生した場合は、このアプローチを検討する必要があります。

私はそれがより速いとは言いませんが、 パフォーマンスが重要な場合のルールに適合します。テストしてください。

複数選択

一般に、ハッシュテーブルはキーと値のペアと考え、1 つのキーを指定して 1 つの値を取得します。 PowerShell を使用すると、複数の値を取得するためのキーの配列を提供できます。

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

この例では、上記と同じルックアップ ハッシュテーブルを使用し、3 つの異なる配列スタイルを指定して一致を取得します。 これは、ほとんどの人が気づいていない PowerShell の隠された宝石です。

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

ハッシュテーブルはキーと値のペアのコレクションであるため、配列または通常の項目リストの場合とは異なる方法で反復処理します。

最初に気づくのは、ハッシュテーブルをパイプすると、パイプが 1 つのオブジェクトのように扱うことです。

PS> $ageList | Measure-Object
count : 1

Count プロパティは、含まれる値の数を示しますが、それでも。

PS> $ageList.Count
2

この問題を回避するには、 Values プロパティを使用します。必要なのは値だけです。

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

多くの場合、キーを列挙し、それらを使用して値にアクセスする方が便利です。

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(){...} ループの同じ例を次に示します。

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

ハッシュテーブル内の各キーをウォークし、それを使用して値にアクセスします。 これは、ハッシュテーブルをコレクションとして使用する場合の一般的なパターンです。

GetEnumerator()

これにより、ハッシュテーブルのイテレーションをGetEnumerator()で行うことができます。

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

列挙子は、各キーと値のペアを 1 つずつ提供します。 このユース ケース専用に設計されています。 マーク ・クラウス にこのことを思い出していただきありがとうございます。

不適切な列挙

1 つの重要な詳細は、列挙中にハッシュテーブルを変更できないことです。 基本的な $environments の例から始める場合:

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

また、すべてのキーを同じサーバー値に設定しようとすると失敗します。

$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

これは問題ないように見えても失敗します。

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

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

この状況のトリックは、列挙を行う前にキーを複製することです。

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

1 つのキーを含むハッシュテーブルを複製することはできません。 PowerShell がエラーを発生させます。 代わりに、 Keys プロパティを配列に変換し、配列を反復処理します。

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

プロパティのコレクションとしてのハッシュテーブル

これまで、ハッシュテーブルに配置したオブジェクトの型はすべて同じ種類のオブジェクトでした。 私はこれらのすべての例で年齢を使用し、キーは人の名前でした。 これは、オブジェクトのコレクションに名前が付けられている場合に見る優れた方法です。 PowerShell でハッシュテーブルを使用するもう 1 つの一般的な方法は、キーがプロパティの名前であるプロパティのコレクションを保持することです。 この次の例では、このアイデアにステップ インします。

プロパティ ベースのアクセス

プロパティ ベースのアクセスを使用すると、ハッシュテーブルのダイナミクスと、PowerShell でそれらを使用する方法が変わります。 上記のキーをプロパティとして扱う通常の例を次に示します。

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

上記の例と同様に、この例では、ハッシュテーブルにまだ存在しない場合にこれらのキーを追加します。 キーの定義方法と値に応じて、これは少し奇妙であるか、完全に適合します。 年齢リストの例は、この時点までうまく機能しています。 今後正しいと感じるには、新しい例が必要です。

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

また、次のように $person に属性を追加してアクセスできます。

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

突然、このハッシュテーブルはオブジェクトのように感じ、動作し始めます。 これは依然としてモノのコレクションであるため、上記のすべての例が引き続き適用されます。 別の観点からアプローチするだけです。

キーと値の確認

ほとんどの場合、次のような値をテストできます。

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

それは簡単ですが、私は私のロジックの重要な詳細を見落としていたので、私にとって多くのバグの原因となっています。 キーが存在するかどうかをテストするためにそれを使用し始めました。 値が $false またはゼロの場合、そのステートメントは予期せず $false を返します。

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

これはゼロ値のための問題を回避しますが、$nullキーと存在しないキーの違いについては解決しません。 ほとんどの場合、その区別を行う必要はありませんが、その場合の方法があります。

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

また、キーを知ったり、コレクション全体を反復処理したりせずに値をテストする必要がある状況に対する ContainsValue() もあります。

キーの削除とクリア

Remove() メソッドを使用してキーを削除できます。

$person.Remove('age')

$null値を割り当てると、結局$null値を持つキーだけが残ります。

ハッシュテーブルをクリアする一般的な方法は、空のハッシュテーブルに初期化するだけです。

$person = @{}

これは機能しますが、代わりに Clear() メソッドを使用してみてください。

$person.Clear()

これは、メソッドを使用して自己文書化コードを作成し、コードの意図を非常にクリーンにするインスタンスの 1 つです。

すべての楽しいもの

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

既定では、ハッシュテーブルは順序付け (または並べ替え) されません。 従来のコンテキストでは、常にキーを使用して値にアクセスする場合、順序は関係ありません。 定義した順序でプロパティを維持したい場合があります。 有難いことに、 ordered キーワードを使用してこれを行う方法があります。

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

これで、キーと値を列挙しても、その順序のままです。

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

1 行でハッシュテーブルを定義する場合は、キーと値のペアをセミコロンで区切ることができます。

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

パイプラインで作成する場合に便利です。

一般的なパイプライン コマンドにおけるカスタム式

カスタム プロパティまたは計算プロパティを作成するためのハッシュテーブルの使用をサポートするコマンドレットがいくつかあります。 これは一般的に、 Select-ObjectFormat-Tableで確認できます。 ハッシュテーブルには、完全に展開されると次のような特殊な構文があります。

$property = @{
    Name = 'TotalSpaceGB'
    Expression = { ($_.Used + $_.Free) / 1GB }
}

Nameは、コマンドレットがその列にラベルを付けるものです。 Expressionは、$_がパイプ上のオブジェクトの値である場合に実行されるスクリプト ブロックです。 そのスクリプトの動作を次に示します。

$drives = Get-PSDrive | where Used
$drives | Select-Object -Property Name, $property

Name     TotalSpaceGB
----     ------------
C    238.472652435303

私はそれを変数に入れたが、それは簡単にインラインで定義することができ、 Namen に短縮し、 Expressione することができます。

$drives | Select-Object -Property Name, @{n='TotalSpaceGB';e={($_.Used + $_.Free) / 1GB}}

私はそれによってコマンドが長くなるのが個人的に好きではありませんし、そして、しばしば記述しない悪い行動を促進します。 スクリプトでこのアプローチを使用する代わりに、必要なすべてのフィールドとプロパティを含む新しいハッシュテーブルまたは pscustomobject を作成する可能性が高くなります。 しかし、そこにこれを行う多くのコードがあるので、私はあなたがそれに気づいてほしいと思いました。 後で pscustomobject の作成について説明します。

カスタム並べ替え式

オブジェクトに並べ替えるデータがある場合は、コレクションを簡単に並べ替えることができます。 並べ替える前にデータをオブジェクトに追加するか、 Sort-Objectのカスタム式を作成できます。

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

この例では、ユーザーの一覧を取得し、並べ替え用にだけ、いくつかのカスタムコマンドレットを使用して追加の情報を取得します。

ハッシュテーブルの一覧を並べ替える

並べ替えるハッシュテーブルの一覧がある場合、 Sort-Object はキーをプロパティとして扱わないことがわかります。 カスタム並べ替え式を使用することで回避策を講じることができます。

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

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

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

これは、多くの人が初期段階で気づかないハッシュテーブルの特徴の一つで、私のお気に入りです。 この考え方は、すべてのプロパティを 1 行でコマンドレットに提供する代わりに、最初にハッシュテーブルにパックすることができます。 その後、関数にハッシュテーブルを特別な方法で渡すことができます。 通常の方法で DHCP スコープを作成する例を次に示します。

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 行で定義する必要があります。 画面からスクロールするか、ランダムに折り返します。 次に、スプラッティングを利用しているコマンドと比較してみましょう。

$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

@の代わりに$記号を使用すると、splat 操作が呼び出されます。

一息ついて、その例がいかに読みやすいかに気付いてください。 これらはすべて同じ値を持つまったく同じコマンドです。 2 つ目は、今後の理解と維持が容易になります。

コマンドが長くなりすぎる場合は、いつでもスプラッティングを使用します。 ウィンドウが右にスクロールする状態を「長すぎる」と定義します。 関数に対して3つのプロパティを対象とした場合、おそらくスプラットされたハッシュテーブルを使用して書き換えるでしょう。

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

私がスプラッティングを使用する最も一般的な方法の1つは、スクリプト内の他の場所から来る省略可能なパラメータを処理することです。 省略可能なGet-CimInstance引数を持つ$Credential呼び出しをラップする関数があるとします。

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

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

Get-CimInstance @CIMParams

まず、共通のパラメーターを使用してハッシュテーブルを作成します。 その後、 $Credential が存在する場合は追加します。 ここではスプラッティングを使用しているため、コード内で Get-CimInstance を呼び出す必要があるのは 1 回だけです。 この設計パターンは非常にクリーンで、多くの省略可能なパラメーターを簡単に処理できます。

公平にするために、パラメーターの $null 値を許可するコマンドを記述できます。 呼び出している他のコマンドを常に制御できるわけではありません。

複数のスプラット演算子

同じコマンドレットに複数のハッシュテーブルをスプラッタできます。 オリジナルのスプラッティング手法についてもう一度検討すると:

$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

このメソッドは、多くのコマンドに渡す共通のパラメーターセットがある場合に使用します。

クリーンなコードを実現するためのスプラット適用

コードをクリーンにする場合、1 つのパラメーターをスプラッティングしても問題はありません。

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

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

スプラッティングは、 /param:value 構文を使用する一部の実行可能ファイルでも機能します。 Robocopy.exeたとえば、次のようないくつかのパラメーターがあります。

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

私はこれがすべて有用であることを知らないが、私はそれが興味深いことがわかった。

ハッシュテーブルの追加

ハッシュテーブルでは、2 つのハッシュテーブルを結合する加算演算子がサポートされています。

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

これは、2 つのハッシュテーブルがキーを共有していない場合にのみ機能します。

入れ子構造のハッシュテーブル

ハッシュテーブル内の値としてハッシュテーブルを使用できます。

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

私は2つのキーを含む基本的なハッシュテーブルから始めました。 空のハッシュテーブルで location というキーを追加しました。 次に、その location ハッシュテーブルに最後の 2 つの項目を追加しました。 これもすべてインラインで行うことができます。

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

これにより、上記と同じハッシュテーブルが作成され、同じ方法でプロパティにアクセスできます。

$person.location.city
Austin

オブジェクトの構造にアプローチするには、さまざまな方法があります。 入れ子になったハッシュテーブルを見る 2 つ目の方法を次に示します。

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

これにより、オブジェクトのコレクションとしてハッシュテーブルを使用する概念と、プロパティのコレクションとして使用する概念が組み合わさります。 これらの値は、任意のアプローチを使用して入れ子になっている場合でも、簡単にアクセスできます。

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

私はそれをプロパティのように扱っているときにドットプロパティを使用する傾向があります。 これらは一般的に私のコードで静的に定義したもので、私は頭の上からそれらを知っています。 リストをウォークする必要がある場合、またはプログラムでキーにアクセスする必要がある場合は、角かっこを使用してキー名を指定します。

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

ハッシュテーブルを入れ子にする機能を使用すると、多くの柔軟さと選択肢を得られます。

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

ハッシュテーブルの入れ子を開始したら、コンソールからそれらを確認する簡単な方法が必要です。 その最後のハッシュテーブルを取得すると、次のような出力が表示されますが、この深さまでしか届きません。

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

私のお勧めのコマンドは ConvertTo-Json です。なぜなら、それが非常にクリーンで、他の作業でJSONを頻繁に使用するからです。

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

JSON がわからない場合でも、探しているものを確認できる必要があります。 このような構造化データには Format-Custom コマンドがありますが、私はまだJSONビューが好きです。

オブジェクトの作成

場合によっては、オブジェクトが必要で、ハッシュテーブルを使用してプロパティを保持するだけでは、ジョブが完了しない場合があります。 最も一般的には、キーを列名として表示する必要があります。 pscustomobjectを使用すると、それが簡単になります。

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

$person

name  age
----  ---
Kevin  36

最初に pscustomobject として作成しない場合でも、必要に応じて後でいつでもキャストできます。

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

[pscustomobject]$person

name  age
----  ---
Kevin  36

私はすでにあなたがこの後に読んでほしい pscustomobject の詳細な記事を持っています。 ここで学んだことの多くを基にしています。

ハッシュテーブルの読み取りとファイルへの書き込み

CSV への保存

CSVに保存するためにハッシュテーブルを取得することに苦労することは、私が上記で言及していた困難の1つです。 ハッシュテーブルを pscustomobject に変換すると、CSV に正しく保存されます。 列の順序が維持されるように、 pscustomobject から始める場合に役立ちます。 ただし、必要に応じてインラインで pscustomobject にキャストできます。

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

ここでも、 pscustomobject の使用に関する私の書き込みを確認してください。

入れ子になったハッシュテーブルをファイルに保存する

入れ子になったハッシュテーブルをファイルに保存してから再度読み込む必要がある場合は、JSON コマンドレットを使用してこれを行います。

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

この方法には 2 つの重要な点があります。 1つ目は、JSONが複数行に書き出されるため、 -Raw オプションを使用して単一の文字列に読み戻す必要があるということです。 2 つ目は、インポートされたオブジェクトが [hashtable]でなくなったということです。 これは [pscustomobject] になり、予期しない場合に問題を引き起こす可能性があります。

深く入れ子になったハッシュテーブルを監視します。 JSON に変換すると、期待した結果が得られない可能性があります。

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

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

Depth パラメーターを使用して、入れ子になったハッシュテーブルをすべて展開していることを確認します。

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

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

インポート時に [hashtable] にする必要がある場合は、 Export-CliXml コマンドと Import-CliXml コマンドを使用する必要があります。

JSON から Hashtable への変換

JSON を [hashtable]に変換する必要がある場合は、.NET の JavaScriptSerializer でこれを行う方法が 1 つあります。

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

PowerShell v6 以降、JSON サポートでは NewtonSoft JSON.NET が使用され、ハッシュテーブルのサポートが追加されます。

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

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

PowerShell 6.2 では 、Depth パラメーターが ConvertFrom-Jsonに追加されました。 既定の 深度 は 1024 です。

ファイルから直接読み取る

PowerShell 構文を使用してハッシュテーブルを含むファイルがある場合は、直接インポートする方法があります。

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

ファイルの内容を scriptblockにインポートし、実行する前に他の PowerShell コマンドがないことを確認します。

そのメモでは、モジュール マニフェスト ( .psd1 ファイル) が単なるハッシュテーブルであることをご存知でしたか?

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

ほとんどの場合、キーは単なる文字列です。 そのため、引用符を何でも囲んでキーにすることができます。

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

あなたができることに気づかなかったかもしれないいくつかの奇妙なことをすることができます。

$person.'full name'

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

何かを実行できるからといって、そうする必要があるわけではありません。 最後のバグは発生を待っているようで、コードを読んでいる人は簡単に誤解されます。

技術的には、キーは文字列である必要はありませんが、文字列のみを使用する方が簡単に考えられます。 ただし、インデックス作成は複雑なキーではうまく機能しません。

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

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

ハッシュテーブル内の値にキーでアクセスしても、常に機能するとは限りません。 例えば次が挙げられます。

$key = $ht.Keys[0]
$ht.$($key)
a
$ht[$key]
a

キーが配列の場合は、メンバー アクセス ($key) 表記で使用できるように、.変数を部分式でラップする必要があります。 または、配列インデックス ([]) 表記を使用できます。

自動変数で使用する

$PSBoundParameters

$PSBoundParameters は、関数のコンテキスト内にのみ存在する自動変数です。 これには、関数が呼び出されたすべてのパラメーターが含まれています。 これは厳密にはハッシュテーブルではありませんが、それと見なせるほど近い存在です。

これには、キーの削除と他の関数へのスプラッティングが含まれます。 プロキシ関数を記述している場合は、この関数を詳しく見てみましょう。

詳細については、 about_Automatic_Variables を参照してください。

PSBoundParameters の注意点

覚えておくべき重要な点の 1 つは、パラメーターとして渡される値のみが含まれていることです。 既定値を持つパラメーターがあっても、呼び出し元によって渡されない場合、 $PSBoundParameters にはそれらの値は含まれません。 これは一般的に見落とされます。

$PSDefaultParameterValues

この自動変数を使用すると、コマンドレットを変更せずに、任意のコマンドレットに既定値を割り当てることができます。 この例を見てみましょう。

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

これにより、$PSDefaultParameterValues パラメーターの既定値としてUTF8を設定するOut-File -Encoding ハッシュテーブルにエントリが追加されます。 これはセッション固有であるため、 $PROFILEに配置する必要があります。

私はこれを頻繁に使用して、頻繁に入力した値を事前に割り当てます。

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

これはワイルドカードも受け入れるため、値を一括で設定できます。 これを使用する方法を次に示します。

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

詳細な内訳については、Michael Sorens による自動既定値に関するこの優れた記事を参照してください。

Regex $Matches

-match演算子を使用すると、$Matchesと呼ばれる自動変数が一致の結果と共に作成されます。 正規表現にサブ式がある場合は、それらのサブ一致も一覧表示されます。

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

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

名前付き一致

これは、ほとんどの人が知らない私のお気に入りの機能の一つです。 名前付き正規表現の一致を使用する場合は、一致する名前で一致するものにアクセスできます。

$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>.*) は名前付きサブ式です。 この値は、 $Matches.Name プロパティに配置されます。

Group-Object -AsHashtable

Group-Objectのあまり知られていない機能の 1 つは、一部のデータセットをハッシュテーブルに変換できることです。

Import-Csv $Path | Group-Object -AsHashtable -Property Email

これにより、各行がハッシュテーブルに追加され、指定したプロパティがキーとして使用されてアクセスされます。

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

知っておくべき重要な点の 1 つは、ハッシュテーブルがオブジェクトであるということです。 また、各変数は単なるオブジェクトへの参照です。 つまり、ハッシュテーブルの有効なコピーを作成するには、さらに多くの作業が必要になります。

参照型の割り当て

ハッシュテーブルが 1 つあり、それを 2 つ目の変数に割り当てると、両方の変数が同じハッシュテーブルを指します。

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]

これは、一方の値を変更するともう一方の値も変更されるため、同じであることを強調しています。 これは、ハッシュテーブルを他の関数に渡す場合にも当てはまります。 これらの関数がそのハッシュテーブルに変更を加えた場合、元の関数も変更されます。

浅いコピー、単一レベル

上記の例のような単純なハッシュテーブルがある場合は、 Clone() を使用して浅いコピーを作成できます。

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]

これにより、もう一方に影響を与えない基本的な変更を行うことができます。

入れ子になった浅いコピー

簡易コピーと呼ばれる理由は、基本レベルのプロパティのみをコピーするためです。 これらのプロパティの 1 つが参照型 (別のハッシュテーブルなど) の場合、入れ子になったオブジェクトは引き続き互いを指し示します。

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 への参照が複製されなかったことがわかります。 最初のハッシュテーブルにリンクされていない 2 つ目のハッシュテーブルを本当に持つには、ディープ コピーを作成する必要があります。

ディープコピー

ハッシュテーブルのディープ コピーを作成する (およびハッシュテーブルとして保持する) 方法はいくつかあります。 PowerShell を使用してディープ コピーを再帰的に作成する関数を次に示します。

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
        }
    }
}

他の参照型や配列は処理されませんが、出発点として適しています。

もう 1 つの方法は、.NET を使用して、次の関数のように CliXml を使用して逆シリアル化することです。

function Get-DeepClone
{
    param(
        $InputObject
    )
    $TempCliXmlString = [System.Management.Automation.PSSerializer]::Serialize($obj, [int32]::MaxValue)
    return [System.Management.Automation.PSSerializer]::Deserialize($TempCliXmlString)
}

非常に大きなハッシュテーブルの場合、逆シリアル化関数はスケールアウト時に高速になります。ただし、このメソッドを使用する場合は、考慮すべき点がいくつかあります。 CliXml を使用するため、メモリ負荷が高く、巨大なハッシュテーブルを複製する場合は問題になる可能性があります。 CliXml のもう 1 つの制限は、深度制限が 48 である場合です。 つまり、48 層の入れ子になったハッシュテーブルを持つハッシュテーブルがある場合、複製は失敗し、ハッシュテーブルはまったく出力されません。

ほかに何か。

私は短時間で多くの仕事をこなしました。 私の願いは、あなたが読むたびに何か新しいことを学んだり、それをより理解できたりすることです。 私はこの機能の全範囲をカバーしたので、今あなたには当てはまらないかもしれない側面があります。 これは完全に問題ありません。PowerShell を使用する量に応じて期待されます。