PowerShell で Shell Script と同じシェルを実装してみる

前のポストで書いた簡単なプログラムをPowerShellでも書くというコマンドが来たのでさっと実装してみた。Shell はガチで勉強し始めてやっているので、PowerShell のほうまでは手が回らないので、必要なところだけ意味を理解するスタイルだけど、調べたことは書いてみたい。

# 実装に必要な要素

下記の要素があれば書けるだろう。

* 引数
* 制御構文
* スリープ
* タイムスタンプ
* RESTAPI呼び出し
* フォーマッティング
* ヘルプ

# 引数

パラメータは簡単。次のスタイル

 ```powershell
Param(
    [string] [Parameter(Mandatory=$true)] $Uri,
    [boolean] [Parameter(Mandatory=$false)] $hasUri
    )
```

# 制御構文 

これも、ほとんどC#のよう。違いは、Boolean の扱い。`true` は `$true` で表す。

 ```powershell
while($true) {
   :
}

if ($hasUri) {
}
```

# スリープ

`Start-Sleep` を使う。秒とミリセカンドのオプションあり。

```powershell
Start-Sleep -Seconds 3
```

* [Start-Sleep](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/start-sleep?view=powershell-6)

# タイムスタンプ

タイムスタンプは`Get-Date`で取得できる。

* [Get-Date](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-date?view=powershell-6)

# REST-API 呼び出し

ステータスチェックだけだが、[Invoke-WebRequest](ttps://docs.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Utility/Invoke-WebRequest?view=powershell-5.1) でよい。オブジェクトで取れるのでパース不要で楽だった。

```powershell
$R = Invoke-WebRequest -URI $Uri
```

ちなみに、パラメータなどを送りたい場合はエスケープが必要。

* [How to use HTTP GET in PowerShell? [duplicate]](https://stackoverflow.com/questions/13935218/how-to-use-http-get-in-powershell)

# フォーマッティング

文字列のフォーマッティング。こういう構文が使える。とても簡単だ。

```powershell
$output = '{0} | {1}' -f($timestamp, $R.StatusCode)
Write-Output $output
```

* [The Ultimate Guide to PowerShell String Formatting](https://hodgkins.io/the-ultimate-guide-to-powershell-string-formatting)

# ヘルプ

折角なのでヘルプも作っておこう。Get-Help  に対応できるようにする。最初は全く動かなかった。`.EXAMPLE`が小文字だったりしたが、そういう文法の崩れがあると表示されない模様。

```powershell
<#

.SYNOPSIS
    Healthcheck the Uri and display the result. 

.DESCRIPTION
    The polling.ps1 display the helthcheck result for each second. You will see the format
    [Timestamp] | [StatusCode] | [Uri] 

.PARAMETER Uri
    The target uri for checking the status

.PARAMETER hasUri
    If $true, it omits the output of the Uri

.EXAMPLE
PS > .\polling.ps1 -Uri https://github.com/Azure-Samples/openhack-devops-proctor -hasUri $true
.\polling.ps1 -Uri https://github.com/Azure-Samples/openhack-devops-proctor -hasUri $true
21/03/2019 14:21:17 | 200 | https://github.com/Azure-Samples/openhack-devops-proctor
21/03/2019 14:21:21 | 200 | https://github.com/Azure-Samples/openhack-devops-proctor

.EXAMPLE
PS > .\polling.ps1 -Uri https://github.com/Azure-Samples/openhack-devops-proctor 
21/03/2019 14:21:55 | 200
21/03/2019 14:21:58 | 200

#>
```

##　結果

```powershell
PS > Get-Help .\polling.ps1 -Detailed

NAME
    C:\Users\tsushi\Codes\OpenHack\devops\openhack-devops-proctor\monitoring\polling.ps1
    
SYNOPSIS
    Healthcheck the Uri and display the result.
    
    
SYNTAX
    C:\Users\tsushi\Codes\OpenHack\devops\openhack-devops-proctor\monitoring\polling.ps1 [-Uri] <String> [[-hasUri] <Boolean>] 
    [<CommonParameters>]
    
    
DESCRIPTION
    The polling.ps1 display the helthcheck result for each second. You will see the format
    [Timestamp] | [StatusCode] | [Uri]
    

PARAMETERS
    -Uri <String>
        The target uri for checking the status
        
    -hasUri <Boolean>
        If $true, it omits the output of the Uri
        
    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer, PipelineVariable, and OutVariable. For more information, see 
        about_CommonParameters (https:/go.microsoft.com/fwlink/?LinkID=113216). 
    
    -------------------------- EXAMPLE 1 --------------------------
    
    PS >.\polling.ps1 -Uri https://github.com/Azure-Samples/openhack-devops-proctor -hasUri $true
    
    .\polling.ps1 -Uri https://github.com/Azure-Samples/openhack-devops-proctor -hasUri $true
    21/03/2019 14:21:17 | 200 | https://github.com/Azure-Samples/openhack-devops-proctor
    21/03/2019 14:21:21 | 200 | https://github.com/Azure-Samples/openhack-devops-proctor
    
    
    
    
    -------------------------- EXAMPLE 2 --------------------------
    
    PS >.\polling.ps1 -Uri https://github.com/Azure-Samples/openhack-devops-proctor
    
    21/03/2019 14:21:55 | 200
    21/03/2019 14:21:58 | 200
    
    
    
    
REMARKS
    To see the examples, type: "get-help C:\Users\tsushi\Codes\OpenHack\devops\openhack-devops-proctor\monitoring\polling.ps1 
    -examples".
    For more information, type: "get-help C:\Users\tsushi\Codes\OpenHack\devops\openhack-devops-proctor\monitoring\polling.ps1 
    -detailed".
    For technical information, type: "get-help C:\Users\tsushi\Codes\OpenHack\devops\openhack-devops-proctor\monitoring\polling.ps1 
    -full".
```


ちなみに、ヒアドキュメントもサポートされているが、こっちのほうが絶対いいだろう。

* [Heredoc in PowerShell](http://tasteofpowershell.blogspot.com/2008/07/here-docs-in-powershell.html)

# 全体像

_polling.ps1_

```powershell
<#

.SYNOPSIS
    Healthcheck the Uri and display the result. 

.DESCRIPTION
    The polling.ps1 display the helthcheck result for each second. You will see the format
    [Timestamp] | [StatusCode] | [Uri] 

.PARAMETER Uri
    The target uri for checking the status

.PARAMETER hasUri
    If $true, it omits the output of the Uri

.EXAMPLE
PS > .\polling.ps1 -Uri https://github.com/Azure-Samples/openhack-devops-proctor -hasUri $true
.\polling.ps1 -Uri https://github.com/Azure-Samples/openhack-devops-proctor -hasUri $true
21/03/2019 14:21:17 | 200 | https://github.com/Azure-Samples/openhack-devops-proctor
21/03/2019 14:21:21 | 200 | https://github.com/Azure-Samples/openhack-devops-proctor

.EXAMPLE
PS > .\polling.ps1 -Uri https://github.com/Azure-Samples/openhack-devops-proctor 
21/03/2019 14:21:55 | 200
21/03/2019 14:21:58 | 200

#>

Param(
    [string] [Parameter(Mandatory=$true)] $Uri,
    [boolean] [Parameter(Mandatory=$false)] $hasUri
    )

while($true) {
  $R = Invoke-WebRequest -URI $Uri
  $timestamp = Get-Date 
  $output = ""
  if ($hasUri) {
    $output = $output = '{0} | {1} | {2}' -f($timestamp, $R.StatusCode, $Uri)
  } else {
    
    $output = '{0} | {1}' -f($timestamp, $R.StatusCode)
  }
  Write-Output $output
  Start-Sleep -Seconds 1
}
```

# まとめ

実際やってみると、PowerShell のほうがわかってないけど、このケースではめっちゃ早く書けた。オブジェクト指向と型があるのが効いているようすだ。真面目にやってないけど、学んだことのメモとして。