30分でPowerShellに入門する本を Zenn で書いた

Zenn ではじめて本を書きました。

題は「[30分でPowerShellに入門する本](https://zenn.dev/kumarstack55/books/quick-learning-powershell)」で無償公開です。

初版時点の主なチャプターをそのまま転記したのがこの Qiita の記事です。
もしお役に立てられるようでしたら、ご活用ください。

なお、デジタル本の特徴を活かして随時更新しようと考えてますが、更新は Zenn 側で行う予定です。 Git で更新したいので...。Qiita も対応してほしい。

以下、初版からの転記です：

---
# Hello, world!

ここでは `Hello, world!` を画面に出力する方法を学びます。

## シェルで実行する

`powershell.exe` を実行して、プロンプトでコマンドを実行します。

<!-- :::message alert -->
PowerShell Core 6.0 で `powershell.exe` は `pwsh.exe` にリネームされました。6.0以上のバージョンでは `pwsh.exe` を試してください。

<!-- ::: -->


実行結果は次の通り：

```powershell
PS > Write-Host "Hello, PowerShell!"
Hello, PowerShell!
```

## hello.ps1 ファイルを実行する

次のファイルを `hello.ps1` として保存する。ファイルのエンコーディングは UTF-8 with BOM がよいです。UTF-8 BOMなしの場合、文字列として日本語を扱うとき cp932 として扱われるのか、期待しない動作となります。UTF-8 with BOM ならこれを回避できます。

```powershell
Write-Host "Hello, PowerShell!"
```

実行するには、次のようにします。

```powershell
powershell -File .\hello.ps1
```

実行結果は次の通り：

```
PS > powershell -File .\hello.ps1
Hello, PowerShell!
```

# 変数、基本的な型、定数

## 変数

変数は `$` で始めます。整数、文字列、配列、ハッシュテーブル問わず `$` で始めます。変数の名前に日本語などの文字を使ってもさほど支障なく使えます。

注意点としては、変数の大文字、小文字は区別されない。例えば変数 `$a` と `$A` の値は常に同じです。ただし、変数一覧を得る `Get-Variable` の出力では、大文字・小文字は区別されて格納されているので、なるべく混在させずにコーディングしたほうが思わぬ不具合を回避できるかもしれません。

## 型

整数、浮動小数点、真偽、固定長配列、ハッシュテーブルなどがあります。

`@()` などで定義されるのは固定長配列なので、時間制約があり、要素の変更を繰り返し行う場合は ArrayList など別の型の利用を検討してください。より詳細は後続で扱います。

ハッシュテーブルについては、似たような機能を持つ別の型 PSCustomObject があるので混同しないようにしてください。より詳細は後続で扱います。

```powershell
$a = 1          # 整数は Int32
$a.GetType()    # --> Int32

$a = 1.2        # 浮動小数点, 大きな整数は Double

$a = "a"        # 文字列, String

$a = $true      # 真偽は Boolean
$a = $false

$a = 1,2,3      # 固定長の配列は Object[]
$a = @(1,2,3)   # @() でより明示的に配列を表現できる。
$a = @(1)       # @() なら要素数 1 個であっても、
$a = @()        # 0 個であっても、表現できる。

# ハッシュテーブルは Hashtable
$a = @{"key1"=1; "key2"=2}
$a = @{}

# PSCustomObject
$o = [PSCustomObject]@{"key1" = 10;"key2" = 20}
```

## 変数と型

strongly typed な変数を定義できます。

```powershell
[double]$d = 5/3
$d                  # --> 1.66666666666667

[int]$i = 5/3
$i                  # --> 1

[int[]]$ary = @(1, 2.5, "3")
$ary
    # --> 1
    #     2
    #     3

[hashtable]$ht = @{"key1"=1}
$ht
    # --> Name Value
    #     ---- -----
    #     key1 1
```

変換ルールがない場合、例外が発生します：

```powershell
[int]$i = "3a"
    # --> 値 "3a" を型 "System.Int32" に変換できません。
    #     エラー: "入力文字列の形式が正しくありません。
```

## 未初期化の変数の値

初期化されてない変数は null値が入っています。null値は型を持たないようです。また、PowerShell で null値は `$null` で表現します。

```powershell
$notAssignedVar.GetType()   # --> null 値の式ではメソッドを呼び出せません。

$a = 1
$a = $null
$a.GetType()                # --> null 値の式ではメソッドを呼び出せません。
```


## 型変換

```powershell
$o = "3"
$o.GetType()    # --> String

$o = [int]"3"
$o.GetType()    # --> Int32
```

## 型変換と真偽値

整数は 0 が偽と判定されます。そのほか、 `$null` や要素数ゼロの配列も偽となります。

```powershell
[bool[]]$boolArray = @(-1, 0, 1)
$boolArray
    # --> True
    #     False
    #     True

[bool[]]$boolArray = @($null)
$boolArray
    # --> False

[bool[]]$boolArray = @(@(), @(0), @(1), @{})
$boolArray
    # --> False
    #     False
    #     True
    #     True
```

## 定数

New-Variable で定数を宣言できます。

注意点は `-Name` の引数には `$` をつけないことです。

```powershell
New-Variable -Name readOnlyVar -Value 1 -Option Constant
$readOnlyVar        # --> 1
$readOnlyVar = 2    # --> 変数 readOnlyVar は読み取り専用または定数であるため、上書きできません。
```

# 制御構造、条件分岐、繰り返し

## if

`{}` カッコは必須で、省略できません。

```powershell
$a = "a"
if ($a -ceq "a") {
    Write-Host "x"
} elseif ($a -ceq "b") {
    Write-Host "y"
} else {
    Write-Host "z"
}
```

if を式のように評価できます。

```powershell
$a = "a"
$ret = if ($a -ceq "a") {
    "x"
} elseif ($a -ceq "b") {
    "y"
} else {
    "z"
}
Write-Host $ret     # --> x
```

## for

```powershell
for ($i = 0; $i -lt 2; $i++) {
    Write-Host $i
}
```

```powershell
$array = for ($i = 0; $i -lt 2; $i++) {
    $i * 2
}
```

## foreach

foreach があります。この foreach は ForEach-Object と似ていますが違うものなので混同しないようにしましょう。なお、 ForEach-Object のエイリアスとして foreach が設定されています。 ForEach-Object とエイリアスについては後述します。

```powershell
$array = @(1,2)
foreach ($n in $array) {
    Write-Host $n
}
```

```powershell
$array = @(1,2)
$array2 = foreach ($n in $array) {
    $n * 2
}
```

## switch

```powershell
$a = "a"
switch -CaseSensitive ($a) {
    "a" { Write-Host "x" }
    "b" { Write-Host "y" }
    default { Write-Host "z" }
}
```

switch を式のように評価できます。

```powershell
$a = "a"
$ret = switch -CaseSensitive ($a) {
    "a" { "x" }
    "b" { "y" }
    default { "z" }
}
Write-Host $ret     # --> x
```

## while

```powershell
$a = 1
while ($a -le 3) { Write-Host $a; $a++ }
```

## do-while

```powershell
$a = 4
do { Write-Host $a; $a++ } while ($a -le 3)
```

## do-until

```powershell
$a = 1
do { Write-Host $a; $a++ } until ($a -ge 3)
```

# パイプライン処理

bash などのシェルスクリプト言語のように、パイプ記号 `|` の左側の結果を、その右側に渡す処理をかけます。ただし、シェルスクリプトのように stream を渡すのではなく、PowerShell の場合はオブジェクトの各要素を渡します。例えば、配列をパイプの左に書けば、右側では各要素に対して処理ができます。

## Where-Object

要素の中で上限に合う要素のみにする。

```powershell
$array = @(5,1,3,2)
$array | Where-Object { $_ -gt 2 }
    # --> 5
    #     3
```

## ForEach-Object

すべての要素を処理します。

```powershell
$array = @(1,2)
$array | ForEach-Object { $_ * 2 }
    # --> 2
    #     4
```

## Select-Object

要素から一部を得ることができます。

### Select-Object で先頭からN個の要素を得る

`-First 2` で先頭から2つの要素を得ます：

```powershell
$array = @(1,2,3,4,5)
$array | Select-Object -First 2
    # --> 1
    #     2
```

### Select-Object で要素を展開する

```powershell
$ary = @(
    [PSCustomObject]@{"key1" = 10;"key2" = 20},
    [PSCustomObject]@{"key1" = 11;"key2" = 21}
)
$ary | Select-Object -ExpandProperty key1
    # --> 10
    #     20
```

## Sort-Object

```powershell
$array = @(2,1,3)
$array | Sort-Object
    # --> 1
    #     2
    #     3
```

# 主な型：文字列

## 初期化する

```powershell
$a = "line1"

$a = "line1`r`nline2`r`n"

# ヒアドキュメント
$a = @"
line1
line2
line3
"@
```

## 文字列の長さを得る

```powershell
$s = "abc"
$s.Length   # --> 3
```

## 文字列の部分文字列を得る

```powershell
$s = "abcd"
$s.Substring(1, 2)              # --> bc
$s                              # --> abcd

# 左3文字
$s.Substring(0, 3)              # --> abc

# 右3文字
$s.Substring($s.Length - 3, 3)  # --> bcd
```

## 文字列の両端の空白を消す

```powershell
$s1 = " a b "
$s2 = $s1.Trim()
Write-Host "[$s1]"  # --> [ a b ]
Write-Host "[$s2]"  # --> [a b]

$s3 = "  a  b  `r`n  c  d  "
$s4 = $s3.Trim()
Write-Host "[$s4]"
# --> [a  b
#       c  d]
```

## 文字列内の文字列を置き換える

```powershell
$s1 = "abc"
$s1.Replace("b", "XX")  # --> aXXc
$s1                     # --> abc
```

## 文字列を正規表現でマッチする

微妙な差はありますが、他のプログラミング言語などで、よくある正規表現の記法です。

注意すべきは、正規表現だけでなく、PowerShell全体で大文字・小文字が基本無視されるので `-match` ではなく、大文字・小文字を区別する `-cmatch` を使うべきかは配慮しましょう。

```powershell
# 正規表現でマッチする
"abc" -cmatch "b"       # --> True

# 正規表現でマッチしないか判定する
"abc" -cnotmatch "b"    # --> False

# マッチした範囲の全体を後方参照する
"abcd" -cmatch "b."     # --> True
$Matches[0]             # --> bc

# 後方参照する
"abcd" -cmatch "b(.)"           # --> True
$Matches[0]                     # --> bc
$Matches[1]                     # --> c

# 名前で後方参照する
"abcd" -cmatch "b(?<key1>.)"    # --> True
$Matches[0]                     # --> bc
$Matches.key1                   # --> c

# キャレット ^ を行頭にマッチさせるといった場合はスイッチを使う
"line1`r`nline2`r`nline3`r`n" -cmatch "^line2"       # --> False
"line1`r`nline2`r`nline3`r`n" -cmatch "(?m)^line2"   # --> True
```

## 文字列をワイルドカードで検索する

TODO

* `-like`
* `-nolike`

## 文字列から正規表現にあう箇所をすべて得る

Python の `re.findall()` のように、指定の正規表現にマッチする一覧を得る手段がわかっていません。

```powershell
function cmatchall {
    param($Text, $Pattern)
    $result = New-Object System.Collections.ArrayList
    $re = "(" + $Pattern + ")(?s)(.*)$"
    $t = $Text
    while ($t -cmatch $re) {
        $result.Add($Matches[1]) | Out-Null
        $t = $Matches[2]
    }
    $result
}

$text = "line1`r`nline2`r`nline3`r`n"
$array = cmatchall -Text $text -Pattern "line\d+"
    # --> line1
    #     line2
    #     line3
```

## 文字列内の正規表現でマッチする部分を置き換える

```powershell
"abc" -creplace "b.", "XX"  # --> aXX
```

## 文字列の配列を区切り文字で結合する

```powershell
@("a", "b", "c") -join ","  # --> a,b,c
```

## 文字列を区切り文字や正規表現で分割する

```powershell
"a,b,c" -split ","
    # --> a
    #     b
    #     c

"a,b, c" -split ",\s*"
    # --> a
    #     b
    #     c
```

## 文字列に値を埋め込む

```powershell
$var = "x"
Write-Host "a$var"          # --> ax
Write-Host "a${var}"        # --> ax

Write-Host ("[a{0}, {1}]" -f @(10,20))        # --> [a10, 20]

# 右寄せ、左寄せ
Write-Host ("[a{0,3}, {1}]" -f @(10,20))      # --> [a 10, 20]
Write-Host ("[a{0,-3}, {1}]" -f @(10,20))     # --> [a10 , 20]
```

## 文字列に式を埋め込む

```powershell
Write-Host "x$(1+2)"    # --> x3
```

# 主な型：Object[]

配列です。添え字の番号は 0 から始まります。

カンマで区切れば配列になります。が、要素数がゼロ、1のときは `@()` や `@(123)` のように明示できます。

配列は固定長配列です。固定長配列を結合するような処理は可能ですが、向きません。処理時間に制約がある場合は ArrayList など他の手段を検討してください。

## 初期化する

```powershell
$a = @(10,20)
```

## 要素数を得る

```powershell
$a = @(10,20)
$a.Count    # --> 2
```

## 要素を得る

```powershell
$a = @(10,20)

$a[0]       # --> 10
# または
$a.Item(0)  # --> 10
```

## 要素を加える

固定長配列であるため `Add()` できませんが、新たに配列を得ることで、似たような操作はできます。

```powershell
$a = @(1,2) + 3

$a = @(1,2) + @(3,4)

$a = @(1,2)
$a += @(3,4,5)
```

なお、次の通り `Add()` は失敗します：

```powershell
$a.IsFixedSize  # --> True
$a.Add(30)      # --> "1" 個の引数を指定して "Add" を呼び出し中に例外が発生しました: "コレクションは固定サイズです。
```

# 主な型：ArrayList

Object[] が固定長であることに比べて、ArrayList は動的ににサイズを変更できます。

## 初期化する

```powershell
$arrayList = New-Object System.Collections.ArrayList
$arrayList.AddRange(@(10,20))
```

## 要素数を得る

```powershell
$arrayList = New-Object System.Collections.ArrayList
$arrayList.AddRange(@(10,20))

$arrayList.Count    # --> 2
```

要素数を得るという目的にい大して、 Length は期待に反するので、注意が必要です。

```powershell
$arrayList = New-Object System.Collections.ArrayList
$arrayList.AddRange(@(10,20))

$arrayList.Length
    # --> 1
    #     1
```

## 要素を得る

```powershell
$arrayList = New-Object System.Collections.ArrayList
$arrayList.AddRange(@(10,20))

$arrayList[0]       # --> 10
# または
$arrayList.Item(0)  # --> 10
```

## 要素を加える

```powershell
$arrayList = New-Object System.Collections.ArrayList

$arrayList.Add(10)
```

## 要素を挿入する

```powershell
$arrayList = New-Object System.Collections.ArrayList
$arrayList.AddRange(@(10,11,12))

$arrayList.Insert(1, 99)
$arrayList
    # --> 10
    #     99
    #     11
    #     12
```

## 要素を消す

```powershell
$arrayList = New-Object System.Collections.ArrayList
$arrayList.AddRange(@(10,11,12))

$arrayList.RemoveAt(1)
$arrayList
    # --> 10
    #     12
```

# 主な型：Hashtable

キーに対して値を格納する型です。他の言語でディクショナリ、連想配列、ハッシュなどと呼ばれているものです。

ハッシュテーブルと似たオブジェクトで PSCustomObject があるので混同しないようにするとよいです。

## 初期化する

```powershell
$a = @{
    "key1"=1
    "key2"=2
}

$a = @{"key1"=1; "key2"=2}
```

## キーを得る

```powershell
$a = @{"k1"="v1"; "k2"="v2"}

$a.Keys
```

## キーの値を得る

```powershell
$a = @{"k1"="v1"; "k2"="v2"}

$a["k1"]
# または
$a.Item("k1")
```

## キーがあるか確認する

```powershell
$a = @{"k1"="v1"; "k2"="v2"}
$a.Contains("k1")
    # --> True
$a.Contains("k3")
    # --> False
```

## キーを消す

```powershell
$a = @{"k1"="v1"; "k2"="v2"}
$a.Remove("k1")
```

## PSCustomObject に変換する

```powershell
$a = @{"k1"="v1"; "k2"="v2"}
$b = [PSCustomObject]$a
```

# 主な型：PSCustomObject

## 初期化する

```powershell
$a = [PSCustomObject]@{
    "key1"=1
    "key2"=2
}

$a = [PSCustomObject]@{"key1"=1; "key2"=2}
```

## キーの一覧を得る

```powershell
$a = [PSCustomObject]@{"key1"=1; "key2"=2}
$a.psobject.Properties.Name
```

## キーがあるか確認する

```powershell
$a = [PSCustomObject]@{"key1"=1; "key2"=2}

$a.psobject.Properties.Match("key1").Count  # --> 1
$a.psobject.Properties.Match("key3").Count  # --> 0
```

## キーの値を得る

```powershell
$a = [PSCustomObject]@{"key1"=1; "key2"=2}

$a.key1
```

キーの指定は、変数で指定できる。

```powershell
$a = [PSCustomObject]@{"key1"=1; "key2"=2}
$propName = "key1"
$a.$propName    # --> 1
```

## キーを消す

```powershell
$a = [PSCustomObject]@{"key1"=1; "key2"=2}
$a.psobject.Properties.Remove("key1")
```

## PSCustomObject を Hashtable に変換する

```powershell
$o = [PSCustomObject]@{"key1"=1; "key2"=2}
$h = @{}
foreach ($name in $o.psobject.Properties.Name) {
    $h[$name] = $o.$name
}
```

# 計算、演算

## 算術演算

```powershell
# 加減剰余
1 + 2               # --> 3
2 * 3               # --> 6
10 / 3              # --> 3.33333333333333
[int](10 / 3)       # --> 3
10 % 3              # --> 1

# べき乗
[Math]::Pow(2, 10)  # --> 1024

# そのほかのMathの関数一覧
[Math] | Get-Member -Static

# 代入を伴う算術演算
$a = 1
$a += 2
$a          # --> 3
$a *= 3
$a          # --> 9
$a /= 3
$a          # --> 3
$a %= 2
$a          # --> 1

# インクリメント、デクリメント
$a++
$a          # --> 2
$a--
$a          # --> 1
++$a
$a          # --> 2
--$a
$a          # --> 1
```

インクリメントの評価前後の値を参照できます：

```powershell
# for ($a = 0; $a -lt 2; $a++) { ... } 相当
$a = 0
do { write-host $a } while ($a++ -lt 2)
```

## null 判定

他の言語であるように is null は使いません。 null は整数などと同じように比較します。

```powershell
$a = $null

$null -eq $a    # --> True
```

## 論理演算

```powershell
$b1 -and $b2
$b1 -or $b2
-not $b1
```

## 比較演算

他の言語でよくある不等号記号での比較は利用できません。 `-eq` など英単語を短縮したような演算子を使います。 bash, Perl などのでは整数比較と文字列比較で演算子が変わりますが、PowerShell ではどちらも同じ演算子を使います。

注意が必要なのは、大文字小文字は明示ない限りは無視されることです。 `-eq` は `A` と `a` を同一視します。同一視したくない場合は `c` をつけて比較しましょう。

```powershell
"A"   -eq "a"  # --> True
"A"  -ceq "a"  # --> False
"あ"  -eq "ぁ" # --> False

"A" -cgt "a"    # --> True
"a" -cgt "A"    # --> False
```

```powershell
$x  -eq $y  # equal
$x -ceq $y  # equal, case-sensitive
$x  -ne $y  # not equal
$x -cne $y  # not equal, case-sensitive
$x  -gt $y  # greater than
$x  -ge $y  # greater than or equal
$x  -lt $y  # less than
$x  -le $y  # less than or equal
```

# 別名、エイリアス

長いコマンドレットなどに別名をつけることができます。

注意すべきは、最初から定義されているエイリアスが多いことです。例えば、エイリアスで `?` は `Where-Object` であると定義され、 `%` や `foreach` は `ForEach-Object` であると定義されています。そのため、次のコードは等価です。

```powershell
# 次の2文は同じ
@(10, 20) | Where-Object { $_ -lt 15 }
@(10, 20) | ? { $_ -lt 15 }

# 次の3文は同じ
@(10, 20) | ForEach-Object { Write-Host $_ }
@(10, 20) | foreach { Write-Host $_ }
@(10, 20) | % { Write-Host $_ }
```

エイリアスの一覧は次のように得られます：

```powershell
Get-Alias
```

# 関数、ファンクション

## ファンクションを定義する

ファンクション `f` を定義して実行するには次のようにします：

```powershell
function f {
    Write-Host "a"
}

f       # --> a
```

## ファンクションと変数のスコープ

```powershell
function f {
    Write-Host "(1)ファンクション内 a: $a"  # --> 1
    $a = 2
    Write-Host "(2)ファンクション内 a: $a"  # --> 2
}

$a = 1
f
Write-Host "(3)ファンクション実行後 a: $a"  # --> 1
```

## ファンクションを呼び出すとき引数を指定する

```powershell
function f {
    param($Arg1)
    Write-Host "Arg1: $Arg1"
}

f 1
f -Arg1 1
```

引数を `function f($Arg1) { ... }` の形式で定義できますが、メジャーな記述でなく、やらないほうが無難です。

## ファンクションを呼び出すとき引数を参照渡しする

```powershell
function f {
    param([ref]$Arg1)
    $Arg1.Value++
}

$num = 1

f ([ref]$num)
$num
    # --> 2

f -Arg1 ([ref]$num)
$num
    # --> 3

$refNum = [ref]$num
f -Arg1 $refNum
$num
    # --> 4
```

## ファンクションを呼び出すとき引数をハッシュテーブルで指定する

```powershell
function f {
    param($Arg1)
    Write-Host "Arg1: $Arg1"
}

$hashtable = @{"Arg1"=1}
f @hashtable
    # --> Arg1: 1
```

## 値を返すファンクションを定義する

ブロック末尾か、あるいは、 `return` で返す。

```powershell
function f {
    "a"
}

f       # --> a
$a = f
$a      # --> a
```

## 引数を必須とするファンクションを定義する

```powershell
function f {
    param([parameter(Mandatory)]$Arg1)
    Write-Host "Arg1: $arg1"
}

f 1     # --> Arg1: 1
f       # 引数入力を求められる
```

## スイッチのための引数持つファンクションを定義する

`[switch]` を指定すると引数として真偽値を受け取ることができる。

注意すべきは `-Verbose` や `-Debug` などはコマンドレットの名称と衝突するので、コマンドレットの機能として、param内に書かないほうがいい。

```powershell
function f {
    param([switch]$Arg1)
    Write-Host "Arg1: $Arg1"
}

f           # --> Arg1: False
f -Arg1     # --> Arg1: True
```

## 引数の型を指定するファンクションを定義する

```powershell
function f {
    param([int]$Arg1)
    Write-Host "Arg1: $Arg1"
}

f 1     # --> arg1: 1
f "1a"   # --> f : パラメーター 'Arg1' の引数変換を処理できません。値 "a" を型 "System.Int32" に変換できません。
f "1"   # --> arg1: 1
```

## 返す値の型を指定するファンクションを定義する

型を明示することはできます。しかし、この型と異なる値を返しても処理は継続されます。

引数がなかったとしても、 `param()` を指定する必要があります：

```powershell
function f {
    [OutputType([String])]
    param()
    "a"
}

$a = f
$a      # --> a
```

次のように、文字列を返すことを定義し、定義に反して整数を返しても、異常終了は発生しません：

```powershell
function f {
    [OutputType([String])]
    param()
    123
}

$a = f
$a      # --> 123
```

## ファンクションのヘルプを表示する

`Get-Help` でヘルプを表示できる。表示はファンクションの定義やコメントから生成される。

```powershell
function f {
    [OutputType([String])]
    param($Foo, [parameter(Mandatory)]$Bar, [switch]$Baz, [int]$Hoehoe)
    $true
}

Get-Help f
```

```
名前
    f

構文
    f [[-Foo] <Object>] [-Bar] <Object> [[-Hoehoe] <int>]  [<CommonParameters>]


エイリアス
    なし


注釈
    なし
```

## パイプラインからアイテムを受け取るファンクションを定義する

`process` ブロックで、パイプラインから受け取ることができるようになります。かなり特殊なケースにおいてはパイプライン処理において process ブロックは必須ではないこともあるはずですが、ほとんどのケースで気にしなくてよいです。

```powershell
function f {
    process {
        Write-Host "PSItem: $PSItem"
    }
}

@(1,2) | f
```

PSItem を使わない記述方法もあります。

```powershell
function f {
    param ([Parameter(ValueFromPipeline)]$Item)
    process {
        Write-Host "Item: $Item"
    }
}

@(1,2) | f
```

# 例外、例外補足、キャッチ

## 例外を発生させる

```powershell
throw
```

## 例外を補足する

PowerShell で例外の扱いはかなりむつかしい。他の言語でよくある、例外クラスごとに catch することは言語としては不可能です。基本的には、例外の基底クラスのキャッチしかできないと思ってよいです。詳細を知りたい方は参考文献を参照してください。

```powershell
try {
    throw
} catch {
    write-host "a"
}
```

# 環境変数

## 環境変数の一覧を得る

```powershell
Get-ChildItem env:
```

## 環境変数を得る

```powershell
Get-Item env:USERPROFILE
```

## 環境変数を設定する

```powershell
Set-Item -Name env:AAA -Value a
```

## 環境変数を消す

```powershell
Remove-Item env:AAA
```

# ファイルパス

## ファイルパスを計算する

存在するファイルやディレクトリは `Get-Item` で得たオブジェクトのメンバで操作するのが簡単なことがある。一方、存在しないファイルやディレクトリは文字列でパスを操作することになります。

注意点としては、ファイルやディレクトリによってメソッドが異なることがあります。 `.FullName` など、共通で利用できるメンバを抑えておくとよいでしょう。

```powershell
# パスを結合する
Join-Path a b               # --> a\b

# パスを分割する
Split-Path x\y\z            # --> x\y
Split-Path x\y\z -Leaf      # --> z

# ディレクトリと、ディレクトリの親ディレクトリのパスを得る
$dirItem = Get-Item -LiteralPath $HOME
$dirItem.FullName               # --> c:\Users\xxx
$dirItem.Parent.FullName        # --> c:\Users

# ファイルと、ファイルの親ディレクトリのパスを得る
"a" | Out-File -LiteralPath $HOME\a.txt     # --> ファイルを作る
$fileItem = Get-Item -LiteralPath $HOME\a.txt
$fileItem.FullName                          # --> c:\Users\xxx\a.txt
$fileItem.Directory.FullName                # --> c:\Users\xxx
```

そのほかの注意点として、 `Get-Item` の引数に `-LiteralPath` を指定しない場合、パスがワイルドカードとして評価されるので気を付けましょう。次の例では、ブラケット記号がワイルドカードとして評価されるため、 `-LiteralPath` なしの場合に `Get-Item` が `$null` を返しています。

```powershell
"a" | Out-File -LiteralPath $HOME\[xxx]a.txt    # --> ファイルを作る

$fileItem = Get-Item $HOME\[xxx]a.txt
$null -eq $fileItem                             # --> True

$fileItem = Get-Item -LiteralPath $HOME\[xxx]a.txt
$null -eq $fileItem                             # --> False
$fileItem.Name                                  # --> [xxx]a.txt
```

## ファイルの存在を確認する

```powershell
# ファイルの存在を確認する
Test-Path $HOME\a.txt       # --> True
Remove-Item $HOME\a.txt
Test-Path $HOME\a.txt       # --> False
```

# 調査する

## 変数の型を確認する

```powershell
$a.GetType()
```

## 変数のメンバを確認する

```powershell
$a | gm
```

## クラスの静的メンバを確認する

```powershell
[String] | gm -Static
[Math] | gm -Static
```

# Tips、落穂ひろい

## 厳格なモードにする

初期化されてない変数の参照、存在しないオブジェクトのプロパティ参照、などを禁止します。雑ですが、他の言語でいう `-pendantic` や `use strict;` や `set -u` や `Option Explicit` などに近いものです。

```powershell
Set-StrictMode -Version Latest
```

## 2変数の値を交換する

値をスワップする。

```powershell
$a = 1
$b = 2
$a, $b = $b, $a
```

## 長い行を折り畳む

長い文を折りたたむことができます。

```powershell
@(10, 20) | ForEach-Object { $_ * 2 }

# 末尾にバッククォート記号で途中改行を明示できます。
@(10, 20) | `
ForEach-Object { $_ * 2 }

# ただし、パイプ記号が末尾の場合はバッククォートを省略できます。
@(10, 20) |
ForEach-Object { $_ * 2 }
```

ハッシュテーブルにおいては、次の表現は同じです。

```powershell
# この表現と、
@{"key1"=1; "key2"=2}

# この表現は同じ。
@{
    "key1"=1
    "key2"=2
}
```

<!-- ## プロンプトの表示を変更する -->

# 参考文献

書きながら調べた場合は、ここに加筆していきます。
筆者の実行環境は 5.1 ですが、参照ドキュメントとしてそれ以降のバージョンのドキュメントを参照することがあります。

* microsoft.com
    * [About Comparison Operators](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comparison_operators?view=powershell-7.1)
    * [About Functions OutputTypeAttribute](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_outputtypeattribute?view=powershell-7.1)
    * [About Regular Expressions](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_regular_expressions?view=powershell-7.1)
    * [Everything you wanted to know about ShouldProcess](https://docs.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-shouldprocess?view=powershell-7.1)
    * [Examples of Comment-Based Help](https://docs.microsoft.com/en-us/powershell/scripting/developer/help/examples-of-comment-based-help?view=powershell-7.1)
    * [Set-StrictMode](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/set-strictmode?view=powershell-7.1#parameters)
    * [What's New in PowerShell Core 6.0](https://docs.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-core-60?view=powershell-7.1)
* microsoft.com以外
    * [環境変数一覧を表示する](https://win.just4fun.biz/?PowerShell/%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E4%B8%80%E8%A6%A7%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%E3%83%BBGet-ChildItem+env:#gb6fccfe)
    * [PowerShell の -Verbose と -Debug 引数](https://qiita.com/motoJinC25/items/c4aa6701c3ceb6ea3d2b)
    * [PowerShell計算機（べき乗と平方根）](https://qiita.com/Takeru/items/d15138f16dea4056168f)
    * [PowerShell エラーハンドリングの方法について その１ Try-Catch編](https://ymstech.net/blog/powershell-error-handling-try-catch/)
    * [PowerShell エラーハンドリングの方法について その２　Trap編](https://ymstech.net/blog/powershell-error-handling-trap/)