簡単なスクリプトを作ってPowerShellを学習しよう


PowerShellの初めの一歩

## 目的
PowerShellを使用した簡単なスクリプトを作成して理解を深める。
一通りPowerShellの基本的な動作を確認できるスクリプトを作成する。


## 環境
Windows11
PSVersion 7.4.2

## どんな処理ができるスクリプトを作成するか
次の処理ができるスクリプトを作成する。
1. コンソールで日本語の文字列を1行ずつ入力できる
2.  改行コードが含まれていた場合スクリプトを強制終了させる
3.  一行ずつ入力して'exit'と入力されたとき入力モードを終了する
4.  終了後スクリプトが配置された階層にフォルダを生成する
5.  生成したフォルダに入力値が記録されたテキストファイルを出力する

## コンソールから文字列を受け取る
```powershell:sample1.ps1
$demoText = Read-Host "Please enter a string"
Write-Host $demoText
```
### 処理内容
1. コンソールに入力された文字列を受け取る
2. コンソールに受け取った文字列を出力している
### 補足
予約語などで例外となる場合もあるが基本的に$がついているものは変数として扱う。
動詞-名詞で構成されたPowershell特有のコマンドをコマンドレットと呼んでいる。従来のコマンド プロンプトでは複雑となるような処理を短いコマンドで実装することが可能となる。
- Read-Host
 コンソールからの入力読み込み時に使用
- Write-Host
 コンソール出力時に使用

## テキストファイルを出力する
```powershell:sample2.ps1
$demoText = Read-Host "Please enter a string"
$demoText | Out-File -FilePath .\Results.txt
```
### 処理内容
1. コンソールに入力された文字列を受け取る
2. スクリプトと同階層にResults.txtファイルを生成する。ファイルの内容は1で入力された文字列である
### 補足
- Out-File ファイル出力時に使用
- -FilePath
 コマンドレットの後に-をつけてコマンドレットごとに登録されたパラメータを指定する。コマンドレットの処理を操作することができる。今回では`-FilePath`を使用してファイルの出力先を指定している。
 どういったパラメータを指定できるかは公式ドキュメント等でも確認できる。
 参考:https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/out-file?view=powershell-5.1

## PowerShellにおける配列
```powershell:sample3.ps1
$demoTexts = @()
$demoTexts += "hoge"
$demoTexts += "fuga"
$demoTexts += "piyo"
Write-Host $demoTexts 
```
### 処理内容
1. 空の配列を生成する
2. 配列に格納する要素を追加する。(実際には要素を追加した新規の配列を生成している。)
3. 配列をコンソールに出力する
### 補足
`@()`と宣言することで空の配列を生成できる。
基本的に配列は要素数が固定である場合に使用する。そのためサンプルのソースのように配列を扱うと処理速度は遅い。項目数の増減があるような処理はコレクションを利用したほうが処理速度は速くなるためコレクションを利用する方が実用的である。
今回配列を利用しているのはコレクションについての説明を避け、なるべく簡潔に実装とその実装の説明を済ませたいため。

https://learn.microsoft.com/ja-jp/powershell/scripting/learn/deep-dives/everything-about-arrays?view=powershell-7.4

## whileを使用したループ処理
```powershell:sample4.ps1
$continue = $true
$demoTexts = @()

while ($continue) {
    $inputText = Read-Host "Please enter texts (or 'exit' to finish)"
    if ($inputText -ceq "exit") {
        $continue = $false
    }
    else {
        $demoTexts += $inputText
    }
}
Write-Host $demoTexts
```
### 処理内容
1. \\$continue,\\$demoTextsと名前を付けた変数を宣言する。\\$continueはブール値である
2. \\$continueが\\$trueの間3~5のWhile内の処理を繰り返し実行する。\\$falseの場合while内の処理から抜け、6の処理を実行する
3. \\$inputTextに入力値を格納する
4. \\$inputTextの文字列が`exit`の場合、$continueにブール値falseを設定する。`exit`以外の場合5の処理を実行する
5. \\$demoTextsに\\$inputTextを追加する
6. \\$demoTextsをコンソールに出力する
### 補足
- ブール値の扱い
PowerShellでブール値を扱う場合\\$true,\\$falseと表記する。
- 文字列比較
`-ceq`は大文字小文字を区別したうえで文字列の比較を行い、一致する場合trueを返す。`-eq`を使用している場合は大文字、小文字を区別せずに比較を行う。
:::note Info
-ceqのc
大文字、小文字を区別する事をcase-sensitiveというため
:::

## foreachを使用したループ処理
```powershell:sample5.ps1
$demoTexts = @("hoge", "fuga" , "piyo")

foreach ($item in $demoTexts) {
    Write-Host $item
}
```

```console:実行結果
hoge
fuga
piyo
```
### 処理内容
1. 配列を宣言する
2. 配列の要素を一つずつ取り出し、コンソールに出力する
### 補足
- forループを使用した場合
配列を単純に一つずつ取り出して処理を繰り返すだけなら可読性はいまいちだと思うが以下のように書ける。
```powershell:sample6.ps1
$demoTexts = @("hoge", "fuga" , "piyo")

for ($index = 0; $index -lt $demotexts.Count; $index++) {
    "{0}" -f $demoTexts[$index]
}
```
:::note Info
`-lt`,`-f`について
`-lt`は less than を表していて配列の要素数を超えないよう制限している。
`-f` はフォーマット演算子と呼ばれ`-f`の左側で指定した書式を右側の要素に設定することができる。
:::
https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.4#format-operator--f

## スクリプトの強制終了
```powershell:sample7.ps1
$continue = $true
$demoTexts = @()

while ($continue) {
    $inputText = Read-Host "Please enter texts (or 'exit' to finish)"
    if ($inputText -match "`n") { 
        throw "The script has finished executing because it encountered input newline characters."
    }
    if ($inputText -ceq "exit") {
        $continue = $false
    }
    else {
        $demoTexts += $inputText
    }
}
Write-Host $demoTexts
```


### 処理内容
1. \\$continue,\\$demoTextsと名前を付けた変数を宣言する。\\$continueはブール値である
2. \\$continueが\\$trueの間3~6のWhile内の処理を繰り返し実行する。\\$falseの場合while内の処理から抜け、7の処理を実行する
3. \\$inputTextに入力値を格納する
4. \\$inputTextの入力中に改行コードが入力された場合スクリプトを終了する
5. \\$inputTextの文字列が`exit`の場合、$continueにブール値falseを設定する。`exit`以外の場合6の処理を実行する
6. \\$demoTextsに\\$inputTextを追加する
7. \\$demoTextsをコンソールに出力する

### 補足
おそらく一番簡単な処理の中断方法。`throw` の後に文字列を書くことで任意の文字列を表示させてスクリプトを強制終了することができる。
実際に処理が中断した場合コンソールには次のように表示される。
```console
Line |
   7 |          throw "The script has finished executing because it encounter …
     |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | The script has finished executing because it encountered input newline characters.
```

## 完成スクリプト
```powershell:sample8.ps1
$continue = $true
$results = @()
$outputText = ""
$inputCount = 0

while ($continue) {
    $inputText = Read-Host "Please enter texts (or 'exit' to finish)"
    if ($inputText -match "`n") { 
        throw "The script has finished executing because it encountered input newline characters."
    }
    if ($inputText -eq "exit") {
        $continue = $false
    }
    else {
        $results += $inputText
    }
}
if ($results.Count -eq 0) {
    $outputText = "no input"
}
else {
    foreach ($val in $results) {
        $outputText += $val
        $inputCount += 1
        if ($inputCount -ne $results.Count) {
            $outputText += "`n"
        } 
    }
}
$outputText | Out-File -FilePath .\Results.txt -NoNewline
```
### 処理内容
1. 変数を宣言する
2. \\$continueが\\$trueの間3~6のWhile内の処理を繰り返し実行する。\\$falseの場合while内の処理から抜け、7の処理を実行する
3. \\$inputTextに入力値を格納する
4. \\$inputTextの入力中に改行コードが入力された場合スクリプトを終了する
5. \\$inputTextの文字列が`exit`の場合、$continueにブール値falseを設定する。`exit`以外の場合6の処理を実行する
6. \\$resultsに\\$inputTextを追加する
7. \\$resultsの要素数が0の場合、コンソールに`no input`と出力する。\\$resultsの要素数が0でない場合、8~9のforeach内の処理を要素数分繰り返し実行する
8. \\$resultsの要素を\\$outputTextに連結し、\\$countをインクリメントする
9. \\$countと\\$resultsの要素数が一致しない場合、\\$outputTextの文字列に改行コードを連結する
10. スクリプトと同階層にResults.txtファイルを生成する。ファイルの内容は$outputTextである

### 補足
最初の1の変数宣言時に`$outputText = ""`と明示的に文字列と宣言しているが`$outputText`と型を書かずに宣言してもPowerShell側で文字列として処理される。

10のファイル出力時デフォルトで最後に空行が挿入される。`-NoNewline`のパラメータを付与すると最後に空行を挿入せずにファイルを出力することができる。
   
:::note Info
Out-Fileの`-Encoding`パラメータ
実用的にスクリプトでファイルを置換等する場合、よく使用するパラメータになるかと思う。このパラメータを使用することで文字コードやBOMの有無を指定できる
:::

## まとめ
かなり基本的な内容で頻繁にスクリプトを書くわけではないのでよく忘れてしまう。私がスクリプトを作成する場合一から作るということはあまりなく、ChatGPTに投げて帰ってきたスクリプトのコードを手直しして使用することが多い。そういったときにざっとよく使う処理を確認できる内容になればと思い記事にまとめた。

実務ではExcelのブック内の情報を参照して参照結果を別のブックに出力したり、Thymeleafの一部のタグを置換したりなどでPowerShellを使用した。気が向いたらそのあたりも書けたら…