PowerShellおぼえがき

# はじめに

PowerShellスクリプトを書いているときにハマったところを覚え書きにして残します。

## 環境編

### PowerShell ISEの起動

PowerShell及びPowerShell ISEは、デフォルトでスタートメニューに入っている。

`スタートメニュー → 「Windows PowerShell」`

ちなみにPowerShell本体及びISEは、
`C:\Windows\System32\WindowsPowerShell\v1.0` (64bit)
`C:\Windows\SysWOW64\WindowsPowerShell\v1.0` (32bit)
に格納されている。

### PowerShell ISEでデバッグできない！

PowerShell ISEは、保存されていないスクリプトの場合、画面下部のコンソールに
編集中のスクリプトをそのまま流し込むことで実行する。
この場合、スクリプトファイルの実行ポリシーの影響を受けないので問題なくデバッグは
できるが、一旦ファイルとして保存したスクリプトに関しては先述の実行ポリシーが
適用されるため、実行することができない。
この場合、下記のような手順で実行ポリシーを変更することで
スクリプトファイルについてもデバッグ実行ができるようになる。

PowerShell ISEを起動し、画面下部のプロンプトで以下のコマンドを入力しEnter。

```powershell
> Get-ExecutionPolicy
```

結果が `Restricted` になっている場合は（というかそうなってるはず）、
下記のコマンドを入力しEnter、「実行ポリシーの変更」メッセージが表示されるので、
「はい」を押す。
（実行ポリシーの内容は「スクリプトの実行ポリシー一覧」を参照)

```powershell
> Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
```

`Set-ExecutionPolicy` のデフォルトのスコープは `LocalMachine` となっているため
コマンド実行に管理者権限が必要となってしまうが、
`-Scope CurrentUser` を追加することで現ユーザープロファイルにスコープが
変更されるため、管理者権限で実行しなくても実行ポリシーを変更できる。
なおこの設定は永続的な設定(レジストリに記録される)になるため注意が必要。
（実行ポリシーのスコープは「実行ポリシーのスコープ一覧」を参照)

>
※恒久的な設定になってしまうのが嫌な場合
`Set-ExecutionPolicy` の `-Scope` で `Process` を指定すると、
現在開いているプロセスのみを対象に実行ポリシーを設定できる。
ただし設定されているウィンドウ(またはISE)を閉じるとその設定は失われるので
開く都度に再設定は必要となる。
>

```powershell
> Set-ExecutionPolicy -Scope Process RemoteSigned
```

### 実行ポリシーのスコープ一覧

| スコープ名 | 説明 |
| :-- | :-- |
| MachinePolicy | すべてのユーザーグループポリシー |
| UserPolicy | 現在適用されているユーザーグループポリシー |
| Process | 現在のPowerShellセッションのみ。<br>設定はレジストリではなく環境変数 `$env:PSExecutionPolicyPreference` に保存される。<br>PowerShellセッションが閉じられると、変数と値が削除される。 |
| CurrentUser | 現在のユーザー。<br>設定はレジストリの `HKEY_CURRENT_USER` に保存される。
| LocalMachine | 現在のコンピューター上のすべてのユーザー。<br>設定はレジストリの `HKEY_LOCAL_MACHINE` に保存される。

### スクリプトの実行ポリシー一覧

| 実行ポリシー | 署名付き | 署名なし／ローカル | 署名なし／非ローカル | 概要 |
| --- | :---: | :---: | :---: | --- |
| Restricted | × | × | × | 全てのスクリプトが実行禁止。<br>(PowerShellまたはWindows OSインストール直後のデフォルト)
| AllSigned | ○ | × | × | 署名されているスクリプトのみが実行可能。<br>署名されていないスクリプトは実行禁止
| RemoteSigned | ○ | ○ | × | ローカルに保存されているスクリプトは実行可能。<br>インターネットからダウンロードしたスクリプト（非ローカルのスクリプト）は、<br>署名されているもののみが実行可能。 |
| Unrestricted | ○ | ○ | △ | 全てのスクリプトが実行可能。<br>ただしインターネットからダウンロードしたスクリプトは、<br>実行するかどうかが確認される。 |
| Bypass | ○ | ○ | ○ | 警告やユーザーへの確認なしに、全てのスクリプトが実行可能 |

### スクリプト起動

Windowsのデフォルト設定では `.ps1` ファイルを直接実行できない。

`powershell.exe` 起動時に、 `-ExecutionPolicy` オプションで実行ポリシーを一時的に変更可能。

```
> powershell.exe -ExecutionPolicy Bypass -File <スクリプトのパス>
```

### Formアプリケーション起動中プロンプト画面が邪魔！

スクリプト起動オプションに `-WindowStyle Hidden` を追加することで
Formアプリケーション起動時にプロンプト画面が消えるようになる。

### それでも起動時に一瞬プロンプト画面が表示されるんですけど！

ショートカットを作って「実行時の大きさ」を「最小化」にすることで抑止できる。

### ODBCドライバ

スクリプトが動作しているPowerShellと同じアーキテクチャのODBCドライバしか使用できない。
（64bit版のPowerShellでスクリプトを動作させるならなら64bit版のODBCドライバが必要)

## おまじない編

### Add-Type

.NETクラスを使うときに必要なおまじない。
ISE上でこのおまじないを実行すると.NETオブジェクトがロードされ、
おまけに.NETのインテリセンスが使えるようになる。

```powershell
Add-Type -AssemblyName <.NETクラス名称>
```

### using namespace

現行のWindows10環境であれば `using namespace` が使用できる。
C#みたいな感覚で.NETクラスを扱える。
ただし必ず行頭に書かなきゃならない。

## 変数・配列・ハッシュテーブル編

### グローバル変数

基本的に子スコープからは参照可能だが、値の変更は不可。
コマンドレットを使えば子スコープから値を更新することができるらしいが、
そこまですることもない気がする。
というか個人的にはクラス作っちゃってグローバル変数は
その入れ物にしておくというスタイルが便利。

### 型宣言

型名称を `[]` で囲む

```powershell
[string] $a
```

### 型の一覧(抜粋)

| 型定義 | 型名称 | 説明 |
| --- | --- | --- |
| [bool] | System.Boolean | $true,$falseのみ |
| [sbyte] | System.SByte | 8bit符号付整数 |
| [byte] | System.Byte | 8bit整数 |
| [int] | System.Int32 | 32bit符号付整数 |
| [int16] | System.Int16 | 16bit符号付整数 |
| [int32] | System.Int32 | 32bit符号付整数 |
| [int64] | System.Int64 | 64bit符号付整数 |
| [long] | System.Int64 | 64bit符号付整数 |
| [uint16] | System.UInt16 | 16bit整数 |
| [uint32] | System.UInt32 | 32bit整数 |
| [uint64] | System.UInt64 | 64bit整数 |
| [single] | System.Single | 単精度浮動小数点 |
| [float] | System.Single | 単精度浮動小数点 |
| [double] | System.Double | 倍精度浮動小数点 |
| [char] | System.Char | 文字 |
| [string] | System.String | 文字列 |
| [decimal] | System.Decimal | 10進整数 |
| [datetime] | System.DateTime | 日時 |
| [timespan] | System.TimeSpan | 時間 |
| [object] | System.Object | オブジェクト型 |

### 配列宣言

型名称の後ろに `[]` を書く。

```powershell
[string[]] $a
```

### 配列の初期化

`@(値1,値2,値3,...)` という構文で任意の要素数の配列に初期化できる。

```powershell
$a = @("hoge","fuga","hage")
```

ちなみに `@()`で要素数0の配列を作れる。

### 配列の拡張

配列変数に `+=` で配列を拡張しつつ値を入れることができる。
また文字列の拡張も同様の書き方でできる。

```powershell
$a += "piyo"
```

### ハッシュテーブルの宣言（というかDictionaryオブジェクトの生成）

```powershell
$a = [System.Collections.Generic.Dictionary[<型名称>,PSObject]]::new()
```

### 文字列の連結

`+` 演算子でつなげることができる。

```powershell
"hoge" + "fuga"
```

### ハッシュテーブルの初期化

`@{<キー> = <値>}` という構文で初期化できる。
複数指定する場合は `;` か改行で区切る。

```powershell
$a = @{"aaa" = "hoge";"bbb" = "fuga";"ccc" = "hage"}
$b = @{
    "aaa" = "hoge"
    "bbb" = "fuga"
    "ccc" = "hage"
}
```

ちなみに `@{}` で要素数0のハッシュテーブルを作れる。

### ハッシュテーブルへの追加と削除

`add(<キー>,<値>)` メソッドで要素を追加できる。

```powershell
$a.add("ddd","piyo")
```

要素の削除は `Remove(<キー>)` メソッドを使う。

```powershell
$a.Remove("aaa")
```

### ハッシュテーブル値の取得と変更

配列の添え字と同じ感覚で扱える。

```powershell
$a["aaa"]
$a["aaa"] = "piyo" #値の変更も可能
```

キーが見つからない時は `$null` が返る。

### ハッシュテーブルに含まれるキー、値の検索

キーは `ContainsKey(<キー>)` メソッドで検索できる。
値は `ContainsValue(<値>)` メソッドで検索できる。

```powershell
if($a.ContainsKey("aaa") -eq $true){ echo "True" }
if($a.ContainsValue("hoge") -eq $true){ echo "True" }
```

存在すれば `$true` 、存在しなければ `$false`　が返る。

## 制御構文編

### IF文の書き方

うっかり以下のような書き方をしちゃうと、エラーも出ないし式も当然ながら正しく評価されない。

```powershell
if($a > 0){処理}
```

`$a`の中身が`0`というファイルにリダイレクトされるという動作になってしまう。
エラーにならないので案外気づきにくい。なのでシェルスクリプトよろしくこう書く必要がある。

```powershell
if($a -gt 0){処理}
```

比較演算子一覧

| 比較演算子 | C言語表記 | 意味 |
| :--- | :--- | :--- |
| -eq | == | 等しい |
| -ne | != | 等しくない |
| -gt | > | より大きい |
| -ge | >= | 以上 |
| -lt | < | より小さい |
| -le | <= | 以下 |
| -like | なし | ワイルドカードと等しい |
| -notlike | なし | ワイルドカードと等しくない |
| -match | なし | 正規表現と等しい |
| -notmatch | なし | 正規表現と等しくない |

### switch文の書き方

書き方がちょっと特殊なので丸暗記しよう。

```powershell
switch(式){
    値1{処理1}
    値2{処理2}
    値3{処理3}
    ...
}
```

## 関数編

### ユーザ定義関数の定義

以下のような構文で定義する。

```powershell
function <関数名>([型][変数],...){
    #処理
    [return <戻り値>]
}
```

デフォルト値を指定することで引数の省略も可能。

```powershell
function <関数名>([型]<変数>[ = 値]>){
```


### ユーザ定義関数の呼び出し

構文は以下のように、引数をスペースで区切る。（コマンドに引数を渡すイメージ）

```powershell
hoge [引数1] [引数2] [...]
```

ちなみにカンマで区切ると、引数リストとして渡される模様。（配列みたいなもん）
.NETクラスと呼び出し方が違うので混乱しやすい。

### ユーザ定義関数の引数

計算式を引数とする場合、またはユーザ定義関数の返り値を引数としたい場合はかっこで囲んでおくとよい。

```powershell
hoge (1 + 2) (fuga 1 2)
```

### 関数への参照渡し

参照渡しにしたい引数変数の前に `[ref]` をつける。

```powershell
function hoge([ref]$a){処理}
```

### 関数を引数に入れたい

引数としたい関数の前後を{}で囲む。
イベントハンドラ登録時などで多用します。

```powershell
hoge({fuga})
```

## クラス編

### クラスのコンストラクタ

クラス名と同名のメソッドを定義すればよい。

```powershell
Class MyClass{
    [int]$a
    MyClass(){
        $this.a = 0
    }
}
```

### クラスのプロパティ

クラスの変数がそのままプロパティになる。
ISEのインテリセンスに出したくない時は `hidden` 修飾子をつけるとよい。
※最新の仕様ではprivate修飾子があるようだが、現バージョンでは使えない模様。

### クラスのインスタンス化

2通りのやり方がある。お好きな方で。

```powershell
$c = [<クラス名>]::new()
$c = New-Object <クラス名>
```

### 自身のプロパティ、メソッドにアクセス

`$this.<メソッドorプロパティ>` という構文でアクセスする。

```powershell
class Hoge {
    [string] $s
    
    GetStr(){
        return $this.s #プロパティの参照
    }
    
    GetString(){
        return $this.GetStr() #メソッドを呼び出す
    }
}
```

### クラスの静的メソッドへのアクセス

クラスの静的メソッドにアクセスしたいときに使用します。
ちなみに自身に定義した静的メソッドへも同様の記述でアクセスします。
（特に.NETクラスの定数とかを取りたいときに多用します）

```powershell
[<クラス名称>]::<定数名>
```

### クラスの継承

`class <クラス名称> : <継承元クラス名称> {}` で継承できる。

```powershell
class Fuga : Hoge {
    #処理
}
```
### 継承元クラスメソッドのオーバーライド

継承したクラスで継承元クラスにあるメソッドと同名のメソッドを定義でき、
全く違うメソッドにすることもできるし継承元メソッドを呼び出すことで機能拡張もできる。

```powershell
class Fuga : Hoge {
    Hage(){
        ([Hoge]$this).Hage() #継承元クラスのメソッド
        #追加の処理とか
    }
}
```

## フォーム編

### フォーム作成の流れ

フォームに表示する各コントロールのオブジェクトを作成
↓
フォームオブジェクトを作成
↓
フォームに各コントロールオブジェクトを登録
↓
フォーム表示

### フォームの基本サンプル

```powershell
################################################################################
## おまじない
################################################################################
#.NETのFormsクラスを読み込む
Add-Type -AssemblyName System.Windows.Forms
#.NETのDrawingクラスを読み込む
Add-Type -AssemblyName System.Drawing

################################################################################
## 各種コントロール
################################################################################
## OKボタンの設定
#ボタンコントロールオブジェクトを作成
$OKButton = New-Object System.Windows.Forms.Button
#コントロールの表示位置を設定
$OKButton.Location = New-Object System.Drawing.Point(40,100)
#コントロールのサイズを設定
$OKButton.Size = New-Object System.Drawing.Size(75,30)
#ボタンに表示するテキストを設定
$OKButton.Text = "OK"
#ダイアログの返り値を設定
$OKButton.DialogResult = "OK"

## キャンセルボタンの設定
#ボタンコントロールオブジェクトを作成
$CancelButton = New-Object System.Windows.Forms.Button
#コントロールの表示位置を設定
$CancelButton.Location = New-Object System.Drawing.Point(130,100)
#コントロールのサイズを設定
$CancelButton.Size = New-Object System.Drawing.Size(75,30)
#ボタンに表示するテキストを設定
$CancelButton.Text = "Cancel"
#ダイアログの返り値を設定
$CancelButton.DialogResult = "Cancel"

## ラベルの設定
#ボタンコントロールオブジェクトを作成
$label = New-Object System.Windows.Forms.Label
#コントロールの表示位置を設定
$label.Location = New-Object System.Drawing.Point(10,30)
#コントロールのサイズを設定
$label.Size = New-Object System.Drawing.Size(250,20)
#ラベルのテキストを設定
$label.Text = "好きな言葉を入力してください"

## テキストボックスの設定
#テキストボックスコントロールオブジェクトを作成
$textBox = New-Object System.Windows.Forms.TextBox
#コントロールの表示位置を設定
$textBox.Location = New-Object System.Drawing.Point(10,70)
#コントロールのサイズを設定
$textBox.Size = New-Object System.Drawing.Size(225,50)

################################################################################
## フォーム
################################################################################
#フォームオブジェクトを作成
$form = New-Object System.Windows.Forms.Form
#フォームのキャプションを設定
$form.Text = "入力"
#フォームのサイズを設定
$form.Size = New-Object System.Drawing.Size(260,180)

#フォームの決定ボタンとしてOKボタンコントロールを登録
$form.AcceptButton = $OKButton
#フォームのキャンセルボタンとしてキャンセルボタンコントロールを登録
$form.CancelButton = $CancelButton

#フォームにOKボタンコントロールを追加
$form.Controls.Add($OKButton)
#フォームにキャンセルボタンコントロールを追加
$form.Controls.Add($CancelButton)
#フォームにラベルコントロールを追加
$form.Controls.Add($label)
#フォームにテキストボックスコントロールを追加
$form.Controls.Add($textBox)

#フォームを表示させ、その結果を受け取る
$result = $form.ShowDialog()

#フォームの結果による処理分岐
if ($result -eq "OK")
{
    $x = $textBox.Text
    $x
}
```

実行結果
![form_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298004/19b0bb63-de74-77bf-1a5d-6e35159edb91.png)

### イベントハンドラ

各コントロール及びフォームオブジェクトにイベントハンドラを登録するには
`$<オブジェクト>.Add_<イベント名>({<イベントハンドラ>})`
という構文で登録できる。

```powershell
################################################################################
## おまじない
################################################################################
#.NETのFormsクラスを読み込む
Add-Type -AssemblyName System.Windows.Forms
#.NETのDrawingクラスを読み込む
Add-Type -AssemblyName System.Drawing

################################################################################
## イベントハンドラ
################################################################################
#フォームが表示されたときのイベントハンドラ
function Form_Shown(){
    [System.Windows.MessageBox]::Show("Form_Shownが呼ばれました！")
}

################################################################################
## フォーム
################################################################################
#フォームオブジェクトを作成
$form = New-Object System.Windows.Forms.Form
#フォームのサイズを設定
$form.Size = New-Object System.Drawing.Size(260,180)
#フォームのキャプションを設定
$form.Text = "イベントテスト"

#フォームが表示された時のイベントハンドラを登録
$form.Add_Shown({Form_Shown})

#フォームを表示させる。
$form.ShowDialog()
```

実行結果
![event_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298004/bd0564cd-8947-725d-8a9d-31e6e65402a8.png)