PowerShell 使い方メモ

目指せ、脱バッチファイル。

# 環境
Windows 7 SP1

# PowerShell とは
マイクロソフトが開発した CUI のシェル。.NET 上で動作する。

コマンドプロンプトよりはるかに高機能で、 Windows の管理がコマンドだけでできるようになる。

## バージョン
Windows 7 以降は標準で搭載されているが、 OS のバージョンが上がるごとに PowerShell のバージョンも異なっている。
一応手動でアップデートが可能だが、デフォルトのバージョンしか利用できないケースも多いと思うので、 OS と PowerShell のバージョンの関係は頭に入れておいたほうがいいかもしれない。

|           OS           | 標準の PowerShell のバージョン |
|------------------------|--------------------------------|
| Windows 7              | 2.0                            |
| Windows 8              | 3.0                            |
| Windows 8.1            | 4.0                            |
| Windows 10             | 5.0                            |
| Windows Server 2008 R2 | 2.0                            |
| Windows Server 2012    | 3.0                            |
| Windows Server 2012 R2 | 4.0                            |

# Hello World
`Windows + R` で「ファイル名を指定して実行」を開き、 `powershell` で起動。

```ps1:
> echo "Hello World!!"
Hello World!!
```

コマンドプロンプトと同じ要領でコマンドが入力できる。

# コマンドレット
PowerShell では、コマンドのことを **コマンドレット** と呼ぶ。

コマンドレットは、 `<動詞>-<名詞>` の命名規則に従って定義されている。
例えば、 `Get-Content` とか、 `Get-Date` のような感じ。

## エイリアス
「フォルダを移動するときのコマンドレットは、 `Set-Location` です」

と説明すると、 100 人中 190 人くらいは「ありえへん！」と感じ、 PowerShell を勉強する気が失せると思う。

さすがにフォルダ移動するたびに `Set-Location` と入力してると自殺したくなるので、 PowerShell にはデフォルトでエイリアスが定義されている。

`Set-Location` のエイリアスは、 `cd` と `chdir` と `sl` が定義されている。
なので、 `cd <移動先のパス>` でフォルダを切り替えることができる。

`cd` 以外にも、よく使うコマンドレットには、コマンドプロンプトや Unix のシェルなどでよく使われる名前でエイリアスが定義されている。

```ps1:エイリアスの確認
> alias

CommandType  Name   Definition
-----------  ----   ----------
Alias        %      ForEach-Object
Alias        ?      Where-Object
Alias        ac     Add-Content
Alias        asnp   Add-PSSnapIn
Alias        cat    Get-Content
Alias        cd     Set-Location
Alias        chdir  Set-Location
Alias        clc    Clear-Content

（中略）

Alias        swmi   Set-WMIInstance
Alias        tee    Tee-Object
Alias        type   Get-Content
Alias        where  Where-Object
Alias        wjb    Wait-Job
Alias        write  Write-Output
```

## 入力補完
コマンドレットを全て覚えるのは大変。入力するのも大変。

そんな時は、 `Tab` を入力することでコマンドレットの補完ができる。

# コマンドレットのヘルプを見る
```ps1:
> help Get-Content

名前
    Get-Content

概要
    指定された場所の項目の内容を取得します。


構文
    Get-Content [-LiteralPath] <string[]> [-Credential <PSCredential>] [-Exclude <string[]>] [-Filter <string>] [-Force] [-Include <string[]>] [-ReadCount <Int64>] [-TotalCount <Int64>] [-UseTransaction] [<CommonParameters>]

    Get-Content [-Path] <string[]> [-Credential <PSCredential>] [-Exclude <string[]>] [-Filter <string>] [-Force] [-Include <string[]>] [-ReadCount <Int64>] [-TotalCount <Int64>] [-UseTransaction] [<CommonParameters>]


説明
    Get-Content コマンドレットは、ファイルに格納されているテキストなど、パスで指定された場所にある項目の内容を取得します。一度に 1 行の内容を読み込み、それぞれの行のオブジェクトを返します。


関連するリンク
    Online version: http://go.microsoft.com/fwlink/?LinkID=113310
    about_Providers
    Add-Content
    Set-Content
    Clear-Content

注釈
    例を参照するには、次のように入力してください: "get-help Get-Content -examples".
    詳細を参照するには、次のように入力してください: "get-help Get-Content -detailed".
    技術情報を参照するには、次のように入力してください: "get-help Get-Content -full".
```

- `help <コマンドレット名>` で、指定したコマンドレットのヘルプを確認できる。

# スクリプトを実行する
PowerShell で実行する処理をテキストファイルにまとめておき、スクリプトファイルとして実行することができる。

## スクリプトファイルを作成する
```ps1:
> echo "echo ""Hello Script!!""" > hello.ps1

> cat .\hello.ps1
echo "Hello Script!!"
```

- `.ps1` という拡張子でファイルを保存する。

## スクリプトを実行できるようにポリシーを変更する
管理者権限で PowerShell を起動し、以下のコマンドを実行する。

```ps1:
> Set-ExecutionPolicy RemoteSigned
実行ポリシーは、信頼されていないスクリプトからの保護に役立ちます。実行ポリシーを変更すると、about_Execution_Policies
のヘルプ トピックで説明されているセキュリティ上の危険にさらされる可能性があります。実行ポリシーを変更しますか?
[Y] はい(Y)  [N] いいえ(N)  [S] 中断(S)  [?] ヘルプ (既定値は "Y"): y
```

Windows Server 2012 R2 以外の OS では、デフォルトでスクリプトファイルの実行ができないようにポリシーが設定されている。

ポリシーを変更するには、 `Set-ExecutionPolicy` というコマンドレットを使用する。
`RemoteSigned` を指定した場合、ローカルで作成されたスクリプトは無条件で実行できるが、ネットワークから入手したスクリプトは署名が無ければ実行できない（Windows Server 2012 R2 はこの設定がデフォルト）。

## 実行する
```ps1:
> .\hello.ps1
Hello Script!!
```

ポリシーを変更したら、後は普通にファイルを指定することで実行できる。

# 開発環境 ISE を使用する
PowerShell には、開発環境として ISE （あいす）というエディタが標準で用意されている。

入力補完やデバッガ機能が存在するので、 PowerShell のスクリプトを書くときは、基本 ISE を使用することになると思う。

Windows 7 なら「アクセサリ」に、 Windows 8 なら「管理ツール」にそれぞれショートカットが存在する。

ISE も PowerShell のバージョンに合わせて異なるバージョンのものがインストールされている。
バージョンが上がるにつれ、使いやすさ向上のため機能が追加されていっている。

自分の環境は Windows7 のデフォルト状態なので、 ver 2 の ISE の使い方をメモする。

## フォントサイズを調整する
`Ctrl + '+'` で拡大、 `Ctrl + '-'` で縮小。

## 入力補完
コマンドラインの場合と同様で、 `Tab` でコマンドや変数の補完ができる。

## スクリプトを実行する
`F5` で、現在編集中のスクリプトファイルを実行できる。

## 検索＋置換
`Ctrl + F` で検索、 `Ctrl + H` で置換。

## 指定行に移動
`Ctrl + G`

## デバッグ
### ブレークポイントを設定する
ブレークポイントを設定する行にカーソルを置き、 `F9`。

### デバッグを実行する
ブレークポイントを設定してから `F5` で実行すれば、ブレークポイントで処理が停止してくれる。

### デバッグ時の操作
|       操作       | ショートカット |
|------------------|----------------|
| ステップオーバー | `F10`          |
| ステップイン     | `F11`          |
| 続行             | `F5`           |
| 処理中断         | `Shift + F5`   |

# 文字を出力する
```ps1:
echo Hello
echo Hello World!!
echo "Hello World!!"
```

```text:実行結果
Hello
Hello
World!!
Hello World!!
```

- `echo` で文字列を出力できる。
- 文字列の間にスペースがある場合は、全体をダブルクォーテーションで括る。
- `echo` は `Write-Output` のエイリアスで、他に `write` というエイリアスが存在する。

## リダイレクト
```ps1:
echo Hello > hoge.log
echo World >> hoge.log

cat hoge.log
```

```text:実行結果
Hello
World
```

- `>` または `>>` でリダイレクトできる。
- 上書き、追記はコマンドプロンプトと同じ。
- エラー出力も `2>&1` でいける。

# コメント
```ps1:
# 単一行コメント

<#
複数行
コメントも可能
#>
```

- `#` で始まる行は、単一行コメントになる。
- `<#` と `#>` で囲まれた部分は、複数行コメントになる。

# 処理を終了させる
```ps1:
echo "hoge"
exit 10
echo "fuga"
```

```text:実行結果
hoge
```

- `exit` で処理を中断できる。
- 引数に終了コードを渡せる。

# 変数の定義
## 基本
```ps1:
$hoge = "Hoge"

echo $hoge
```

```text:実行結果
Hoge
```

- 変数名は、先頭を `$` にしなければならない。
- ２文字目は、任意の英数字またはアンダーバー `_` を指定できる。
- 大文字・小文字は区別されない。

## 任意の文字列を使用する
```ps1:
${日本語　変数} = "ほげ"

echo ${日本語　変数}
```

```text:実行結果
ほげ
```

- `${...}` という感じで波括弧でくくると、スペースや日本語など、任意の文字を変数名に使用できる。

## 変数の型を明示する
```ps1:
[string] $str = "hoge"
[int] $num = 100

echo $str.GetType()
echo $num.GetType()
```

```text:実行結果
IsPublic IsSerial Name                                     BaseType            
-------- -------- ----                                     --------            
True     True     String                                   System.Object       
True     True     Int32                                    System.ValueType
```

- `[型名]` を変数名の前に書くことで、その変数の型を明示できる。
- 型を明示すれば、メソッドの補完とかが働くようになる。
- 型には、 .NET で使用できる任意の型が指定できる。
- 特によく利用する型については、以下のようにエイリアスが定義されている。

|    エイリアス     |  .NET での形名   |
|-------------------|------------------|
| `byte`            | `System.Byte`    |
| `int`             | `System.Int32`   |
| `long`            | `System.Int64`   |
| `single`, `float` | `System.Single`  |
| `double`          | `System.Double`  |
| `decimal`         | `System.Decimal` |
| `char`            | `System.Char`    |
| `bool`            | `System.Boolean` |
| `string`          | `System.String`  |
| `array`           | `System.Array`   |

## メソッドを使用する
```ps1:
[string] $str = "hoge"

echo $str.ToUpper()
```

```text:実行結果
HOGE
```

- 変数などは全て .NET のオブジェクトなので、普通に .NET の API が使用できる（PowerShell の強みの１つ）。

# 四則演算
```ps1:
echo (1 + 1)
echo (10 - 1)
echo (3 * 2)
echo (6 / 3)
echo (5 % 2)
```

```text:実行結果
2
9
6
2
1
```

- 普通に四則演算ができる。

# 文字列リテラル
```ps1:
$a = "aaa"

echo "hoge $a"
echo 'fuga $a'
```

```text:実行結果
hoge aaa
fuga $a
```

- 文字列リテラルは、ダブルクォーテーションまたはシングルクォーテーションで定義する。
- ダブルクォーテーションの場合は、変数の展開が行われる。シングルクォーテーションの場合は展開が行われない。

## 変数名を展開した直後にアンダーバーを入れたい場合の注意
```ps1:
$hoge = "HOGE"

echo "$hoge_FUGA PIYO"
```

```text:実行結果
 PIYO
```

- `$hoge_FUGA` が１つの変数と判断されているらしく、空文字が出力されてしまう。
- こういう場合は、 `${hoge}_FUGA` のように変数名を波括弧で括る。

## エスケープシーケンス
```ps1:
echo "hoge`tfuga`r`n`"piyo`""
```

```text:実行結果
hoge	fuga
"piyo"
```

- PowerShell は、エスケープシーケンスとしてバッククォート（`` ` ``）を使用する。
- `` `t `` は、タブ。 `` `r `` はキャリッジリターン。 `` `n `` は改行。 `` `" `` はダブルクォーテーション。

## 文字列連結
```ps1:
echo ("hoge" + "fuga")
```

```text:実行結果
hogefuga
```

- `+` で文字列連結が可能。

## ヒアドキュメント
```ps1:
$fuga = "FUGA"

echo @"
hoge
  $fuga
piyo
"@
```

```text:実行結果
hoge
  FUGA
piyo
```

- ダブルクォーテーションまたはシングルクォーテーションを `@` で括ることで、ヒアドキュメントを記述できる。
- ダブルクォーテーションの場合は、変数の展開も可能。
- 開始・終了の `@` の前後には、それぞれ改行が必要。

# 配列を使用する
## 基本
```ps1:
$array = "hoge", "fuga", "piyo"

echo $array.GetType().Name
echo ($array[0] + ", " + $array[1] + ", " + $array[2])
```

```text:実行結果
Object[]
hoge, fuga, piyo
```

- カンマ `,` 区切りで変数に値を代入することで、配列を定義できる。
- 配列は、 0 始まりのインデックス指定で要素にアクセスできる。
- この配列は、固定長。

## 空の配列を定義する
```ps1:
$array = @()

echo $array.Length
```

```text:実行結果
0
```

- `@()` で空の配列を定義できる。

## 連続した数値の入った配列を簡潔に定義する
```ps1:
$array = 1..10

echo ('$array = ' + $array)
```

```text:実行結果
$array = 1 2 3 4 5 6 7 8 9 10
```

- `開始値..終了値` で、連続した数値が格納された配列を定義できる。
- `@(開始値..終了値)` でもOK。

## ループを回す
```ps1:
foreach ($i in @(1, 2, 3, 4)) {
    echo $i
}
```

```text:実行結果
1
2
3
4
```

- `foreach (変数 in 配列)` で、配列を順次処理できる。

# 連想配列を使用する
```ps1:
$map = @{hoge="HOGE"; fuga="FUGA"; piyo="PIYO"}

echo $map.GetType().Name
echo ("hoge = " + $map["hoge"])
echo ("fuga = " + $map.Item("fuga"))
```

```text:実行結果
Hashtable
hoge = HOGE
fuga = FUGA
```

- `@{key=value; ...}` という形式で、連想配列（マップ）を定義できる。
- `変数[キー]` または `変数.Item(キー)` で、要素にアクセスできる。

# .NET Framework のクラスを使用する
PowerShell はただ .NET 上で動いるだけでなく、 .NET Framework が提供するクラス郡を利用することができる。

## List を使用する
```ps1:
$list = New-Object System.Collections.ArrayList

$list.Add("hoge")
$list.Add("fuga") > $null
[void]$list.Add("piyo")

echo ($list[0] + ", " + $list[1] + ", " + $list[2])
```

```text:
0
hoge, fuga, piyo
```

- `New-Object` コマンドレットを使用することで、 .NET Framework が提供する任意のクラスのインスタンスを生成できる。
- `Add()` メソッドのように戻り値が存在するメソッドを実行した場合、 PowerShell から実行すると `return` された値が標準出力に出力されてしまう。
- これを抑制するには、 `$null` にリダイレクトするか、 `[void]` をメソッド実行の前に追加する。

# for 文
```ps1:
for ($i = 0; $i -lt 3; $i++) {
    echo $i
}
```

```text:実行結果
0
1
2
```

- `for (初期化; 終了条件; 増加式) {}` で for 文を書ける。

# while 文
```ps1:
$i = 0

while ($i -lt 3) {
    echo $i
    $i++
}
```

```text:実行結果
0
1
2
```

- `while (継続条件) {}` で while 文を書ける。

# if 文
## 基本
```ps1:
if ($true) {
    echo "hoge"
}

if ($false) {
    echo "fuga"
}
```

```text:実行結果
hoge
```

- `if (条件) {処理}` で if 文を書ける。
- 処理のブロックは必須。
- `$true` と `$false` は、暗黙で定義されている定数。

## True と判断されるもの
```ps1:
if (1) {
    echo "aaa"
}
if (0) {
    echo "bbb"
}
if ("") {
    echo "ccc"
}
if ("x") {
    echo "ddd"
}
if ($null) {
    echo "eee"
}
```

```text:実行結果
aaa
ddd
```

- その型の変数を宣言したとき、デフォルトで設定される値は `False` と判断される。
- それ以外の値は `True` と判断される。

## else 文
```ps1:
if ($true) {
    #...
} elseif ($false) {
    #...
} else {
    #...
}
```

- `elseif (条件)` で、異なる条件を追加できる。
- `else` で、どの条件にも当てはまらなかった場合の処理を定義できる。

## 比較演算
### 数値
```ps1:
if ("hoge" -eq "hoge") {
    echo "HOGE"
}

if (10 -gt 10) {
    echo "FUGA"
}

if (10 -ge 10) {
    echo "PIYO"
}
```

```text:実行結果
HOGE
PIYO
```

- `-eq` などの記法で比較演算ができる。

| 演算子 | Java で言うと |
|--------|---------------|
| `-eq`  | `==`          |
| `-ne`  | `!=`          |
| `-gt`  | `>`           |
| `-ge`  | `>=`          |
| `-lt`  | `<`           |
| `-le`  | `<=`          |

### 文字
```ps1:
if ("hoge" -like "h*") {
    echo "HOGE"
}

if ("fuga" -like "??ge") {
    echo "FUGA"
}

if ("piyo" -match "[a-z]+") {
    echo "PIYO"
}
```

```text:実行結果
HOGE
PIYO
```

- `-like` で、ワイルドカードを使った文字列比較。
- `-match` で、正規表現を使った文字列比較ができる。
- それぞれ、前に `not` が追加された否定形がある（`-notlike`, `-notmatch`）。

## 論理演算
```ps1:
if ($true -and $true -and ($true -or $false) -and -not $false -and -! $false) {
    echo "HOGE"
}
```

```text:実行結果
HOGE
```

|    演算子    | Java で言うと |
|--------------|---------------|
| `-and`       | `&&`          |
| `-or`        | <code>&#124;&#124;</code>          |
| `-not`, `-!` | `!`           |

# switch 文
## 基本
```ps1:
$num = 5

switch ($num) {
    1 {
        echo "hoge"
    }
    5 {
        echo "fuga"
    }
    10 {
        echo "piyo"
    }
}
```

```text:実行結果
fuga
```

- switch 文も書ける。
- `break` を書かなくて良いので、 if 文よりも綺麗に書けそう。

## 条件に文字列を使用する
```ps1:
$str = "hoge"

switch ($str) {
    "hoge" {
        echo "HOGE"
    }
    "fuga" {
        echo "FUGA"
    }
    "hoge" {
        echo "Hoge"
    }
}
```

```text:実行結果
HOGE
Hoge
```

- 文字列も OK。
- 条件に一致するブロックが複数存在する場合は、全てのブロックが実行される。

## 条件に変数を使用する
```ps1:
$value = 1
$c1 = 1
$c2 = 2
$c3 = 3

switch ($value) {
    $c1 {
        echo "one"
    }
    $c2 {
        echo "two"
    }
    $c3 {
        echo "three"
    }
}
```

```text:実行結果
one
```

- 条件に変数を使うこともできる。

## 条件にワイルドカードや正規表現を使用する
```ps1:
switch -wildcard ("hoge") {
    "h*" {
        echo "Hoge"
    }
    "f*" {
        echo "Fuga"
    }
}
```

```text:実行結果
Hoge
```

- `switch` の後ろに `-wildcard` と指定することで、条件のマッチングにワイルドカードを使用できる。
- `-regex` と指定すれば、正規表現でマッチングできる。

# コマンドライン引数を取得する
## 基本
```ps1:script.ps1
Param([string] $one, $two)

echo ($one + ", " + $two)
```

```ps1:
> .\script.ps1 Hoge 100
Hoge, 100
```

- `Param()` スクリプトレットを使用することで、コマンドラインから入力された引数を取得できる。
- 引数の型を宣言することもできる。

## 全ての引数を取得する
```ps1:script.ps1
echo ("Type : " + $args.GetType().Name)
echo ("Length : " + $args.Length)
echo ('$args : ' + $args)
```

```ps1:
> .\script.ps1 hoge fuga piyo
Type : Object[]
Length : 3
$args : hoge fuga piyo
```

- `$args` という配列が格納された変数が暗黙で定義されており、これにコマンドラインから渡された引数が全て格納されている。

# 関数の定義
## 基本
```ps1:
function Hoge {
    echo "hoge"
}

Hoge
```

```text:実行結果
hoge
```

- `function 関数名 {}` で関数を定義できる。
- 関数は、実行する場所より前で定義されている必要がある。

## 引数を定義する
```ps1:
function Hoge($a, $b) {
    echo ('$a = ' + $a + ", $b = " + $b)
}

Hoge 'aaa' 'bbb'
```

```text:実行結果
$a = aaa, bbb = bbb
```

- 関数に引数を定義する場合は、関数名の後に丸括弧で宣言する。
- 関数を実行する際は、半角スペース区切りで引数を渡す。
- <span id="comma_args_is_array">半角スペースでなくカンマ区切りにしてしまうと、配列になってしまうので注意。</span>

```ps1:半角スペースではなくカンマ区切りにした場合
function Hoge($a, $b) {
    echo ('$a = ' + $a)
    echo ('$a.GetType().Name = ' + $a.GetType().Name)
    echo ('$b = ' + $b)
}

Hoge 'aaa', 'bbb'
```

```text:実行結果
$a = aaa bbb
$a.GetType().Name = Object[]
$b = 
```


## 全ての引数を取得する
```ps1:
function Hoge {
    echo ('$args = ' + $args)
}

Hoge 'aaa' 'bbb' 'ccc'
```

```text:実行結果
$args = aaa bbb ccc
```

- `$args` を参照することで、全ての引数を取得できる。

## 引数のデフォルト値を設定する
```ps1:
function Hoge($a, $b=10) {
    echo ('$a = ' + $a + ', $b = ' + $b)
}

Hoge
Hoge "aaa"
Hoge "aaa" "bbb"
```

```text:実行結果
$a = , $b = 10
$a = aaa, $b = 10
$a = aaa, $b = bbb
```

- `引数 = 初期値` と宣言することで、引数が省略された場合の初期値を設定できる。

## 名前指定で引数を渡す
```ps1:
function Hoge($a, $b) {
    echo ('$a = ' + $a + ', $b = ' + $b)
}

Hoge -a "hoge" -b "fuga"
```

```text:実行結果
$a = hoge, $b = fuga
```

- `-変数名 値` という形で、名前指定で引数を渡せる。
- 名前指定の場合、引数の順序はバラバラでも構わない。

## 戻り値
```ps1:
function Hoge {
    echo "HOGE"
    return "hoge"
}

$result = Hoge

echo "=================="
echo $result
echo "=================="
```

```text:実行結果
==================
HOGE
hoge
==================
```

- 関数内で出力された内容は、全て呼び出し元に戻り値として返される。
    - 関数内の `echo` は、画面には出力されず、戻り値として返されている。
- `return` を使って、値を返すこともできる。
- `return` を使った場合、その時点で関数の実行は停止され、その関数内の後続処理は実行されなくなる。

## 変数のスコープ
```ps1:
$a = "aaa"

function Hoge($b = "bbb") {
    echo ('$a = ' + $a + ' @ Hoge')
    $a = "aaaaa"
    $c = "ccc"
    echo ('$a = ' + $a + ' @ Hoge')
}


Hoge

echo ('$a = ' + $a)
echo ('$b = ' + $b)
echo ('$c = ' + $c)
```

```text:実行結果
$a = aaa @ Hoge
$a = aaaaa @ Hoge
$a = aaa
$b = 
$c = 
```

- 関数内で宣言した変数の値は、外部からは参照できない。
- 関数内部では、関数外部で宣言した変数を参照できる。
- 関数外部で宣言されているものと同名の変数を関数内部で上書きしても、関数外部からは関数内部で代入した値を参照できない。
    - おそらく、変数が隠蔽されている。

# 定数を宣言する
```ps1:
set CONSTANT_VALUE "hoge" -option constant

echo $CONSTANT_VALUE

$CONSTANT_VALUE = "fuga"
```

```text:実行結果
hoge
変数 CONSTANT_VALUE は読み取り専用または定数であるため、上書きできません。
発生場所 （省略）\script.ps1:5 文字:16
+ $CONSTANT_VALUE <<<<  = "fuga"
    + CategoryInfo          : WriteError: (CONSTANT_VALUE:String) []、SessionStateUnauthorizedAccessEx 
   ception
    + FullyQualifiedErrorId : VariableNotWritable
```

- `set 定数名 値 -option constant` で、上書き不可な定数を宣言できる。

# エラーハンドリング
## コマンドの終了コードを取得する
```bat:other.bat
@echo off
exit /b 15
```

```ps1:
.\other.bat
echo $lastexitcode
```

```text:実行結果
15
```

- `$lastexitcode` に、最後に実行したコマンドの終了コードが格納されている。

## コマンドレットのエラーハンドリング
### 基本
```ps1:
Get-Content "not exists"

echo "hoge"
```

```text:実行結果
パス '（省略）\not exists' が存在しないため検出できません。
発生場所 （省略）:1 文字:12
+ Get-Content <<<<  "not exists"
    + CategoryInfo          : ObjectNotFound: (（省略）\not exists:String) [Get-Co
ntent]、ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand

hoge
```

- デフォルトの場合、コマンドレットの実行でエラーが発生しても、処理は続行される。

### コマンドレットでエラーがあった場合、処理を中断させる
```ps1:
$ErrorActionPreference = "stop"

Get-Content "not exists"

echo "hoge"
```

```text:実行結果
パス '（省略）\not exists' が存在しないため検出できません。
発生場所 （省略）:1 文字:12
+ Get-Content <<<<  "not exists"
    + CategoryInfo          : ObjectNotFound: (（省略）\not exists:String) [Get-Co
ntent]、ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
```

- `$ErrorActionPreference` という変数に値を設定することで、コマンドレットの実行でエラーが発生したときの挙動を指定できる。
- `"stop"` を指定した場合、エラーの発生とともに処理が中断される。
- デフォルトは `"continue"` で、処理が続行される設定になっている。

### try-catch
```ps1:
$ErrorActionPreference = "stop"

try {
    Get-Content "not exists"
} catch {
    echo "catch!!"
}

echo "hoge"
```

```text:実行結果
catch!!
hoge
```

- `try-catch` を使って、コマンドレットの実行でエラーが発生した場合の処理を実装できる。
- 例外を catch できるようにするためには、 `$ErrorActionPreference` に `"stop"` を設定しておく必要がある。

### trap
```ps1:
$ErrorActionPreference = "stop"

Get-Content "not exists"

echo "hoge"

trap {
    echo "trap!!"
}
```

```text:実行結果
trap!!
パス '（省略）\not exists' が存在しないため検出できません。
発生場所 （省略）:1 文字:12
+ Get-Content <<<<  "not exists"
    + CategoryInfo          : ObjectNotFound: (（省略）\not exists:String) [Get-Co
ntent]、ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
```

- `trap` 文を使うと、コマンドレットの実行でエラーが発生し、スクリプトの実行が中断されるときに処理を挟むことができる。

### 関数内で trap
```ps1:
function Hoge {
    throw "test error"
    
    trap {
        echo "Hoge trap"
    }
}

Hoge
echo "hoge"

trap {
    echo "trap"
}
```

```text:実行結果
Hoge trap
test error
発生場所 （省略）:2 文字:10
+     throw <<<<  "test error"
    + CategoryInfo          : OperationStopped: (test error:String) []、RuntimeException
    + FullyQualifiedErrorId : test error
 
hoge
```

- `trap` はブロック単位で記述できるので、関数内に専用の `trap` を定義できる。

### エラーオブジェクトを参照する
```ps1:
$ErrorActionPreference = "stop"

try {
    Get-Content "not exists"
} catch {
    echo '$error[0] = ' + $error[0]
}
```

```text:実行結果
$error[0] = 
+
Get-Content : パス '（省略）\not exists' が存在しないため検出できません。
発生場所 （省略）:4 文字:16
+     Get-Content <<<<  "not exists"
    + CategoryInfo          : ObjectNotFound: (（省略）:String) [Get-Content]、ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
```

- `$error` という暗黙の変数に、今まで発生した例外の情報が格納されている。
- `$error` は配列で、 `0` 番目の要素に最新のエラー情報が格納されている。

## 例外をスローする
```ps1:
try {
    throw "hoge"
} catch {
    echo $error[0]
}
```

```text:実行結果
hoge
発生場所 （省略）:2 文字:10
+     throw <<<<  "hoge"
    + CategoryInfo          : OperationStopped: (hoge:String) []、RuntimeException
    + FullyQualifiedErrorId : hoge
```

- `throw エラー情報` で、例外をスローできる。
- スローできるオブジェクトは、「文字列」「.NET Framework の例外クラス」「`ErrorRecord` クラス（コマンドレットが投げた例外とか）」の３種類。

# パイプ
PowerShell を使うことの最大のアドバンテージは、このパイプだと勝手に思っている。

## オブジェクトをパイプする
bash などの CLI では、コマンドをパイプでつなぐとテキストデータが次のコマンドに送られる。
しかし PowerShell では、テキストデータではなく配列やオブジェクトが送られる。

パイプでつないだ後は配列を順次処理したり、オブジェクトのプロパティでフィルタを掛けたりすることができ、まるで Java の Stream API を使っているかのような感じ（むしろそれ以上に簡潔な記述）で処理を書くことができる。

## 順次処理
```ps1:
@(1..5) | %{echo $_}
```

```text:実行結果
1
2
3
4
5
```

- `|` で処理を連結（パイプ）することができる。
- `%` は `ForEach-Object` のエイリアスで、渡された配列を順次処理する。
    - `{echo %_}` は、 `ForEach-Object` の第一引数（`-Process`）で、スクリプトブロックと呼ばれる。
    - 詳しくは、 `help ForEach-Object` を参照。
- `$_` は、パイプで受け取ったオブジェクトを参照するための暗黙の変数。

## 各要素を別の値に変換する
```ps1:
@(1..3) | % {"<<$_>>"} | % {echo $_}
```

```text:実行結果
<<1>>
<<2>>
<<3>>
```

- `ForEach-Object` を利用すれば、 Java の Stream API における `map()` 的なことができる。

## 絞り込み
```ps1:
@(1..5) | ?{$_ % 2 -eq 0} | %{echo $_}
```

```text:実行結果
2
4
```

- `?` は、 `Where-Object` のエイリアス。条件に一致する要素だけを抽出する。

## ソート
```ps1:
@(1..5) | sort -Descending
```

```text:実行結果
5
4
3
2
1
```

- `sort` は、 `Sort-Object` のエイリアスで、入力をソートする。
- `-Descending` は降順ソート

### 重複を取り除く
```ps1:
@(4, 2, 1, 2, 4, 5, 3) | sort -Unique
```

```text:実行結果
1
2
3
4
5
```

- `-Unique` で、重複を取り除いてソートする。

### ソートに使用するプロパティを指定する
```ps1:
@("one", "two", "three", "four", "five") | sort -Property {$_.Length, $_}
```

```text:実行結果
one
two
five
four
three
```

- `-Property` で、ソートに使用するプロパティを指定できる。
- 上記の場合、文字列の長さでソート・文字列そのもの、の順でソートしている。

## 入力されたオブジェクトのメンバーを参照する
```ps1:
@(1..3) | Get-Member
```

```text:実行結果
   TypeName: System.Int32

Name        MemberType Definition                                                                     
----        ---------- ----------                                                                     
CompareTo   Method     int CompareTo(System.Object value), int CompareTo(int value)                   
Equals      Method     bool Equals(System.Object obj), bool Equals(int obj)                           
GetHashCode Method     int GetHashCode()                                                              
GetType     Method     type GetType()                                                                 
GetTypeCode Method     System.TypeCode GetTypeCode()                                                  
ToString    Method     string ToString(), string ToString(string format), string ToString(System.IF...
```

- `Get-Member` にパイプで繋ぐことで、渡したオブジェクトのメンバー（プロパティとメソッド）を参照できる。

## 自作関数をパイプ処理に対応させる
```ps1:
function My-Function([Parameter(ValueFromPipeline=$true)] $param) {
    process {
        return $param + 100;
    }
}

1..3 | My-Function | % {echo "[$_]"}
```

```text:実行結果
[101]
[102]
[103]
```

- 関数内に `process` というブロックを作ると、パイプラインで呼び出されたときに入力ごとに実行される。
- パイプラインに渡されるパラメータは、宣言のところで `[Parameter(ValueFromPipeline=$true)]` を指定する。
- 関数が返した値が、次のパイプに渡される。

# ファイル操作
## ファイルを読み込む
```text:sample.txt
one
two
three
```

```ps1:
$content = (cat sample.txt)

echo $content
```

```text:実行結果
one
two
three
```

- `cat` は、 `Get-Content` のエイリアス。
- `cat` した結果を、 `$content` 変数に代入している。

## ファイルを１行ずつ処理する
```ps1:
cat sample.txt | % {echo "<li>$_</li>"}
```

```text:実行結果
<li>one</li>
<li>two</li>
<li>three</li>
```

- `cat` した結果を `ForEach-Object` にパイプすることで、１行ずつ処理できる。

## CSV ファイルを読み込む
```text:sample.csv
ID,NAME,AGE
1,Sato,18
2,Suzuki,22
3,Tanaka,19
```

```ps1:
Import-Csv sample.csv
```

```text:実行結果
ID  NAME    AGE
--  ----    ---
1   Sato    18 
2   Suzuki  22 
3   Tanaka  19
```

- `Import-Csv` で、 CSV ファイルを読み込むことができる。
- デフォルトでは、１行目をヘッダー行として処理する。

### 読み込み結果を変数に代入する
```ps1:
$csv = Import-Csv sample.csv

echo $csv[1]
```

```text:実行結果
ID  NAME    AGE
--  ----    ---
2   Suzuki  22
```

- `Import-Csv` を読み込んだ結果を変数に代入すると、配列のようにインデックス指定で任意の行にアクセスできる。

### 読込結果をパイプで処理する

```ps1:
Import-Csv sample.csv | ? {$_.AGE -as [int] -gt 18}
```

```text:実行結果
ID  NAME    AGE
--  ----    ---
2   Suzuki  22 
3   Tanaka  19  
```

- デフォルトだと、各要素は文字列として読み込まれている。
- 数値として処理したい場合は、 `-as` を使ってキャストする。

## CSV を出力する
```ps1:
$array = @(
    @{ID=1; NAME="Sato"; AGE=18},
    @{ID=2; NAME="Suzuki"; AGE=22},
    @{ID=3; NAME="Tanaka"; AGE=19}
)

$array | % {New-Object PSObject -Property $_} | Export-Csv out.csv
```

```text:out.csv
#TYPE System.Management.Automation.PSCustomObject
"ID","NAME","AGE"
"1","Sato","18"
"2","Suzuki","22"
"3","Tanaka","19"
```

- `Export-Csv` で、オブジェクトを CSV に変換して出力できる。
- デフォルトでは、オブジェクトの型情報が１行目に出力される。
    - 出力させたくない場合は `-NoTypeInformation` オプションを指定する。
- `@{}` で生成した連想配列（`Hashtable`）だとうまく出力できないので、 `PSObject` に変換してから出力する。

# XML を取り扱う
## 基本
```ps1:
$xmlString = @'
<hoge>
  <fuga>Hello XML!!</fuga>
</hoge>
'@

$xml = [xml]$xmlString

echo $xml.hoge.fuga
```

```text:実行結果
Hello XML!!
```

- XML 形式の文字列を `[xml]` でキャストすると、オブジェクトとして扱えるようになる。
- 各要素は、ドット (`.`) 区切りで参照できる。
- ファイルから取り込むときは、 `Get-Content` （`cat`）を使って読み込んで、それを `[xml]` でキャストすればいい。

## 属性の参照
```ps1:
$xmlString = @'
<hoge>
  <fuga message="Hello XML Attribute!!">
    <message>Hello XML!!</message>
  </fuga>
</hoge>
'@

[xml] $xml = [xml]$xmlString

echo $xml.hoge.fuga.message
```

```text:実行結果
Hello XML Attribute!!
Hello XML!!
```

- 属性もドット (`.`) で参照できる。
- ただし、同名のタグが子供に存在する場合は、そちらもとれてしまう。

## XML の出力
```ps1:
$xml = New-Object System.Xml.XmlDocument

$hoge = $xml.CreateElement('hoge')
$fuga = $xml.CreateElement('fuga')
$fuga.SetAttribute('message', 'Hello XML!!')

$hoge.AppendChild($fuga)
$xml.AppendChild($hoge)

$xml.Save('.\out.xml')
```

```xml:out.xml
<hoge>
  <fuga message="Hello XML!!" />
</hoge>
```

- `XmlDocument` のインスタンスを生成する。
- `CreateElement()` で、タグを作成し、 `AppendChild()` で作成したタグを任意のタグの子供として追加する。
- `SetAttribute()` で、属性を設定する。
- `Save()` メソッドで、指定したファイルに結果を出力する。

# 日付を取得する
```ps1:
$date = Get-Date

echo $date.ToString("yyyy/MM/dd")
echo $date.AddDays(30).ToString("yyyy/MM/dd")
```

```text:実行結果
2015/07/10
2015/08/09
```

- `Get-Date` で現在日付を取得できる。
- `ToString()` メソッドを使うことで、引数で指定した書式でフォーマットした文字列を取得できる。
- `AddDays()` などのメソッドで、日付を操作できる。

# 別のスクリプトファイルを実行する
```ps1:other.ps1
echo "other script"

$global:other = "Hello Other!!";

function global:Other-Function {
    echo "Other Function!!"
}
```

```ps1:one.ps1
.\other.ps1

echo "one script"

echo ('$other=' + $other)

Other-Function
```

```text:実行結果
other script
one script
$other=Hello Other!!
Other Function!!
```

- パス指定すれば、普通に別のスクリプトファイルを実行できる。
- 別ファイルで定義された変数や関数は、デフォルトではスコープ外なので参照できない。
- 参照できるようにするには、 `global:` を変数や関数の名前の前に追加して、グローバルスコープで定義する。


# その他
※細かい使い方とか、よく利用しそうなコマンドレットの使い方とかを随時メモしていく、と思う。

# よく使いそうな API ドキュメント
- [String メンバ (System)](https://msdn.microsoft.com/ja-jp/library/system.string_members(v=vs.80).aspx)
- [ArrayList クラス (System.Collections)](https://msdn.microsoft.com/ja-jp/library/system.collections.arraylist(v=vs.110).aspx)

# 参考
- [Windows PowerShell - Wikipedia](https://ja.wikipedia.org/wiki/Windows_PowerShell)
- [Tech TIPS：WindowsでPowerShellスクリプトの実行セキュリティポリシーを変更する - ＠IT](http://www.atmarkit.co.jp/ait/articles/0805/16/news139.html)
- [Windows PowerShellコマンド＆スクリプティング入門：PowerShellスクリプティングの第一歩（後編） (2/5) - ＠IT](http://www.atmarkit.co.jp/ait/articles/0709/20/news125_2.html)
- [.net - Prevent ArrayList.Add() from returning the index - Stack Overflow](http://stackoverflow.com/questions/2149159/prevent-arraylist-add-from-returning-the-index)
- [PowerShell: ◆Trueの判定](http://mtgpowershell.blogspot.jp/2010/11/true.html)
- [Windows PowerShell 入門（4）－変数と演算子 （2/3）：CodeZine](http://codezine.jp/article/detail/2337?p=2)
- [powershell 入門 定数宣言 | 技術的なこと、あれこれ](http://www.sakutyuu.com/technology/?p=109)
- [PowerShell: ◆終了コードを取得する](http://mtgpowershell.blogspot.jp/2011/03/blog-post.html)
- [PowerShell の コマンドレット例外を取得する - tech.guitarrapc.cóm](http://tech.guitarrapc.com/entry/2013/07/22/000738)
- [例外処理 - Windows PowerShell | ++C++; // 未確認飛行 C](http://ufcpp.net/study/powershell/exception.html#error)
- [【Windows PowerShell】スクリプトの途中でスクリプトを強制終了する - Muni Bus](http://munibus.hatenablog.com/entry/2014/01/22/053159)
- [PowerShell的システム管理入門：第2回　PowerShellの基礎 (1/3) - ＠IT](http://www.atmarkit.co.jp/ait/articles/1004/15/news106.html)
- [PowerShell で 関数実行結果の型をパイプラインの先に伝搬する - tech.guitarrapc.cóm](http://tech.guitarrapc.com/entry/2015/01/20/034358)
- [[PS1] Powershell の基本 (3:パイプライン処理) | オールトの雲](http://ooltcloud.expressweb.jp/201410/article_16230635.html)
- [(PowerShell) パイプラインはProcessで - Code Aquarium](http://mnzk.hatenablog.com/entry/2013/11/19/221531)
- [How to export data to CSV in PowerShell? - Stack Overflow](http://stackoverflow.com/questions/21047185/how-to-export-data-to-csv-in-powershell)
- [Windows PowerShell コマンドレット](https://technet.microsoft.com/ja-jp/scriptcenter/dd772285.aspx)
- [PowerShellらしい関数の書き方 [PS Advent Calendar '12] - PowerShell Scripting Weblog](http://winscript.jp/powershell/253)
- [Windows PowerShell 活用編（7）　XMLデータの基本操作1 （1/4）：CodeZine](http://codezine.jp/article/detail/3681)