いまさらPowerShell

# はじめに
* 今まではbash/ksh(UNIX系)を使うことが多かった
* コマンドプロンプトで特段困ってなかったので、避けていた
* 担当プロジェクトで使うことになった
* 勉強したら、まあまあ便利かも　←いまここ

# Powershellとは？
* コマンドプロンプトと同じくWindowsのCLIツール
* パイプ処理でオブジェクトを扱える
* Windows Server 2008 / Windows 7から標準機能として搭載
* 今はOSS化してWindows以外でも動く(PowerShell Core)

# 抑えておきたい概念
## コマンドレット
* Powershellランタイムで動くコマンド
* [動詞-名詞]の命名規則
* `Get-Command`：コマンドレットの一覧を取得するコマンド
    * 詳細は、[MSリファレンス(Get-Command)](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/get-command)参照
* `Get-Help`：コマンド詳細調べる
    * 詳細は、[MSリファレンス(Get-Help)](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/get-help)参照
* エイリアス(別名)で呼び出せる(Set-Location→cd 等..)
    * `Get-Alias`：エイリアス一覧取得するコマンド
    * 詳細は、[MSリファレンス(Get-Alias)](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/get-alias)参照
* コマンド入力中にTABキーで補完が効く
* コマンドレットは戻りとして「.NET Frameworkのオブジェクト」を返す
* パイプ処理(|)でコマンドを組み合わせできる
例）1MBを超えるサイズのpngファイル名とサイズを表示
```powershell:
Get-ChildItem -Path *.png |
  Where-Object {$_.length -gt 1MB} |
    Sort-Object -Property length |
      Format-Table -Property name, length
```

## ドライブ
* PowerShell上のコマンドレットからアクセス可能な「データの保管場所」のこと
* Cドライブなどに加え、「レジストリ」や「証明書ストア」もドライブとして使用できる
* `Get-PSDrive`：現在のセッションで利用可能なドライブを一覧表示
    * 詳細は、[MSリファレンス(Get-PSDrive)](https://learn.microsoft.com/ja-jp/PowerShell/module/microsoft.powershell.management/get-psdrive)参照
* [プロバイダ](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_providers)はドライブの実装でドライバの機能を実際に提供している(NET Frameworkベースのクラス・ライブラリ)

# 実行方法
* スクリプトファイルは「.ps1」の拡張子となる
  * ただし、標準ではダブルクリックでPowershellが実行される訳ではない
* コマンドラインでの実行例
```
PS > .\Sample.ps1

PS > C:\work\Sample.ps1

※「.ps1」の部分は省略できる
```
* 実行後リターンコードは、`echo $LASTEXITCODE`で取得できる
 
:::note warn
初回実行
スクリプト実行ポリシーを設定する必要あり
```
PS > Set-ExecutionPolicy RemoteSigned
```
:::

| 実行ポリシー | 概要 |
|:--|:--|
|Restricted|すべてのスクリプトを実行不可|
|AllSigned|すべてのスクリプトに証明書を要求|
|RemoteSigned|インターネット経由でダウンロードしたスクリプトのみ証明書を要求|
|Unrestricted|すべてのスクリプト実行を許可（ただしインターネット経由でダウンロードしたコードは実行確認のみあり）|

# 変数
* 先頭文字は「$」で始まること
* 2文字目以降は任意の英数字、またはアンダースコア（_）であること
* 「\\$\{Windows PowerShell + .NET Framework}」のような\\${～}では任意の文字が使える

## スコープ
* 変数/関数/エイリアス/ドライブがスコープを持つ

| 種類 | 意味 |変数の指定
|:--|:--|:--|
|グローバル・スコープ|スクリプトの外からでもアクセス可|$global:x|
|スクリプト・スコープ|スクリプト内であればどこからでもアクセス可|$script:x|
|ローカル・スコープ|宣言されたブロック内かその子ブロック内からアクセス可|$local:x|
|プライベート・スコープ|宣言されたブロック内からのみアクセス可|$private:x|

[こちらの記事](https://tech.blog.aerie.jp/entry/powershell-advent-calendar-2018-18)には、コードによる解説と詳細の説明があり、大変参考になります。

## 変数の型
* 型は動的に解釈されるため、以下のような不具合の可能性がある(前者は文字列連結、後者は整数の加算として処理される)
```powershell:
PS > $x = "1"
PS > $y = 2
PS > $z = $x + $y
PS > Write-Output $z
12

PS > $x = 1
PS > $y = "2"
PS > $z = $x + $y
PS > Write-Output $z
3
```

* 明示的に型指定(キャスト構文)もできる
```powershell:
PS > [System.Int32] $x = 10
```

* 型名のエイリアス

| エイリアス | .NET Frameworkの型名 |
|:--|:--|
|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|
|xml|System.Xml.XmlDocument|
|type|System.Type

## 文字列
* 4種類ある
1. シングルクォートで囲まれた文字列
1. ダブルクォートで囲まれた文字列
1. シングルクォートで囲まれたヒア・ドキュメント
1. ダブルクォートで囲まれたヒア・ドキュメント

* 変数の展開については、2.(ダブルクォート)を使用すること ※1.(シングルクォート)は変数が展開されない
```powershell:
PS > $name="鈴木"
PS > Write-Output "私の名前は $name です。"
私の名前は 鈴木 です。
PS > Write-Output '私の名前は $name です。'
私の名前は $name です。
```

* 特殊文字のエスケープ文字は「`」(バッククォート)※「\」はパス区切りなので使われない

| エスケープ・シーケンス | 概要 |
|:--|:--|
|`b|バックスペース|
|`n|改行|
|`r|キャリッジ・リターン|
|`t|タブ|

* ヒア・ドキュメント
「@"＜改行＞～＜改行＞"@」（または「@'＜改行＞～＜改行＞'@」）を文字列の区切りとする

```powershell:
PS > $body=@"
>> これは1行目
>> 次は2行目
>> "@
>> #ここでEnter押下
PS > Write-Output $body
これは1行目
次は2行目
```

## 配列・連想配列(ハッシュテーブル)
1. 配列の定義
   * $array=要素, 要素, 要素, ...
   * $array=@(要素, 要素, 要素, ...)
1. 連想配列の定義
   * $map=@{キー名=値; キー名=値; ...}

アクセス方法は以下の通り
1. 配列の定義
   * $array[インデックス番号]
1. 連想配列の定義
   * $map["キー名"]
   * $map.キー名

# .NET Frameworkライブラリの利用
* PowerShellは.NET Frameworkライブラリを使って、通常のコマンドレットより高度な操作ができる
* 現在ロードされているアセンブリの確認方法
```powershell:
PS > [Appdomain]::CurrentDomain.GetAssemblies() | %{$_.GetName().Name}
mscorlib
Microsoft.PowerShell.ConsoleHost
System
System.Core
System.Management.Automation
Anonymously Hosted DynamicMethods Assembly
Microsoft.PowerShell.Security
System.Transactions
Microsoft.PowerShell.PSReadline
Microsoft.PowerShell.Commands.Utility
System.Configuration.Install
```

* 例)System.Collections.ArrayListクラスを利用する
```powershell:
PS > $list=New-Object System.Collections.ArrayList
PS > $list.Add("a")
0
PS > $list.Add("b")
1
PS > $list.Add("c")
2
PS > $list
a
b
c
PS > $list.Count
3
```

* 静的メンバにアクセスする場合は「::」演算子を使う
```powershell:
PS > [Math]::Pow(2, 3) #2^3 = 8
8
```

# 構文

## 比較演算子
|演算子 |概要 |例 |
|:--|:--|:--|
|-eq|等しい（＝）|$_.Name -eq "hoge"|
|-ne|等しくない（≠）|$_.Name -ne "hoge"|
|-gt|より大きい（＞）|$_.Length -gt 1MB|
|-ge|以上（≧）|$_.Length -ge 1MB|
|-lt|未満（＜）|$_.Length -lt 1MB|
|-le|以下（≦）|$_.Length -le 1MB|
|-like|あいまい検索（ワイルドカード）|$_.Name -like "*.log"|
|-match|正規表現検索|\\$_.Name -match "^[0-9]{3}-[0-9]{4}\\$"

## if構文
```powershell:
$x = 10
if($x -ge 8){
  Write-Output '$xは8以上です。'
}elseif($x -ge 5){
  Write-Output '$xは5以上8未満です。'
}else{
  Write-Output '$xは5未満です。'
}
$xは8以上です。
```

## switch ～ case構文
```powershell:
$x = 1
switch($x){
  1 {
      Write-Output '$xは1です。'
      break
    }
  2 {
      Write-Output '$xは2です。'
      break
    }
  3 {
      Write-Output '$xは3です。'
      break
    }
  default {Write-Output '$xは1、2、3ではありません。'}
}
```

:::note info
Tips
-wildcardや-regexオプションで正規表現の判定ができる
```powershell:
$x = "北海道札幌市"
switch -wildcard ($x){
  "*札幌*"  {Write-Output '$xは"札幌"を含みます'}
  "*小樽*"  {Write-Output '$xは"小樽"を含みます'}
  "*函館*" {Write-Output '$xは"函館"を含みます'}
}
```
:::

## ループ構文

### for文
```powershell:
$ary=@(100,200,300)
for($i=0; $i -lt 3; $i++){
  $sum += $ary[$i];
}
Write-Output $sum
```
### foreach文
```powershell:
$ary=@(100,200,300)
foreach($dat in $ary){
  $sum += $dat;
}
Write-Output $sum
```
### while文
```powershell:
$i=0;
$sum=0;
$ary=@(100,200,300)
while($i -lt 3){
  $sum += $ary[$i];
  $i++;
}
Write-Output $sum
```
### do ～ while文
```powershell:
$i=0;
$sum=0;
$ary=@(100,200,300)
do{
  $sum += $ary[$i];
  $i++;
}while($i -lt 3)
Write-Output $sum
```
:::note warn
do ～ whileは後置判断となるので、必ず1回は処理が実行されることに注意してください。
:::

## 関数

### 構文規則
```powershell:
function 関数名 [(引数[=デフォルト値],...)] {
  関数の本体
  [return 戻り値]
}
```

例）三角形の面積を求める関数
```powershell:
function Triangle([double] $width=1, [double] $height=1) {
  return $width * $height / 2
}

Triangle 3 6
9
```
# まとめ
まずは、基礎的な概念や構文についてまとめてみた。

Microsoft社が開発したシェルのため、Windowsがやはりメインターゲットとなるが、.NET Frameworkベースのため拡張性や高機能性が魅力。
オブジェクト指向のパイプ処理を上手く使いこなすのが、初心者には第一関門になりそう。
一度使って慣れると、様々な業務の定型作業を自動化できそう。

# To be continued.
次回は、実用的で便利なスクリプトについて記事を挙げてみようと思う。
* ファイルの一括操作
* Excelやxmlの読み書き
* プログラム自動生成 
* DBとの連携
etc..

以上