👌

Azure Bastion で session 一覧を出す PowerShell cmdlet

2023/04/20に公開

Azure Bastion で session 一覧を出す PowerShell cmdlet

Azure Bastion には session の一覧画面があります。

bastion session list
bastion session list

ただ、比較的新しいのか、PowerShell にはこの一覧を出す cmdlet がありませんでした。
該当の API は Get Active Sessions です。
API があるということはあとは叩けばいいだけなので早速作っていきましょう。

ChatGPT と GitHub Copilot の力をいっぱい借りながら、こんな感じのものができました。

function Get-MyAzBastionSession {
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true, ParameterSetName = 'ByName')]
        [string]$ResourceGroupName,
        [parameter(Mandatory = $true, ParameterSetName = 'ByName')]
        [string]$Name,

        [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName, ParameterSetName = 'ById')]
        [string]$Id
    )

    switch ($PSCmdlet.ParameterSetName) {
        'ByName' {
            $SubscriptionId = (Get-AzContext).Subscription.Id
            $Path = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.Network/bastionHosts/$Name/getActiveSessions?api-version=2022-09-01"
        }
        'ById' {
            $Path = "$Id/getActiveSessions?api-version=2022-09-01"
        }
    }

    $restResponse = Invoke-AzRestMethod -Method POST -Path $Path      

    $sessionResponse = $null
    do {
        $sessionResponse = Invoke-AzRestMethod -Method GET -Uri $restResponse.Headers.Location.AbsoluteUri
        if ($sessionResponse.StatusCode -ne 200) {
            Write-Debug "$(Get-Date -Format "h:MM:ss tt") - Received status code $($sessionResponse.StatusCode). Retrying in 5 seconds..."
            Start-Sleep -Seconds 5
        }
    } while ($sessionResponse.StatusCode -ne 200)
    
    return $sessionResponse.Content | ConvertFrom-Json
}

使い方はいたって簡単で、Resrouce group と Bastion 自体の名前を指定するパターンと、Bastion の Resource ID を指定するパターンがあります。

Get-MyAzBastionSession -ResourceGroupName files-blob-archive -Name bast-hub00 -Debug
Get-MyAzBastionSession -Id "/subscriptions/9e4d6321-d80d-4e43-8916-6f3b482d001d/resourceGroups/files-blob-archive/providers/Microsoft.Network/bastionHosts/bast-hub00" -Debug

また、こだわりのポイントとしては | (パイプ) での実行に対応しました。
注意点として、Get-AzBastion の結果で Bastion が複数返ってきた際に、以下のような利用方法だと 1 つめの Bastion に対してのみ実行されます。

Get-AzBastion | Get-MyAzBastionSession -Debug

Get-AzBasion-ResourceGroupName option を使うなどして 1 つの Bastion が返ってくるように絞るか、ForEach-Object を使って順次実行させる必要があります。

Get-AzBastion -ResourceGroupName files-blob-archive | Get-MyAzBastionSession -Debug
Get-AzBastion | ForEach-Object { Get-MyAzBastionSession -Id $_.id}

PowerShell 的ポイント

ほとんど ChatGPT と GitHub Copilot に書いてもらったのですが、以下の点が勉強になりました。

  • parameter が ResourceGroupName と Name、または Id があることを必須とする

ParameterSetName というのを使うことで、parameter のかたまりを作り、ちょっと複雑な必須条件を書けるようです。

    param (
        [parameter(Mandatory = $true, ParameterSetName = 'ByName')]
        [string]$ResourceGroupName,
        [parameter(Mandatory = $true, ParameterSetName = 'ByName')]
        [string]$Name,

        [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName, ParameterSetName = 'ById')]
        [string]$Id
    )

このかたまりの名前は $PSCmdlet.ParameterSetName に入っているようなので、そのまま switch で利用しています。
前者が ResourceGroupName と Name を指定した場合であり、後者が Id を指定した場合です。

    switch ($PSCmdlet.ParameterSetName) {
        'ByName' {
            $SubscriptionId = (Get-AzContext).Subscription.Id
            $Path = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.Network/bastionHosts/$Name/getActiveSessions?api-version=2022-09-01"
        }
        'ById' {
            $Path = "$Id/getActiveSessions?api-version=2022-09-01"
        }
    }
  • パイプでつなげられている場合に、パイプ元の出力名と引数名を一致させて引数に一気に値を突っ込む

ValueFromPipeline = $true がパイプから引数が渡されることを示しています。
加えて、ValueFromPipelineByPropertyName を指定することで、Get-AzBastion の結果に含まれる Id を引数の Id と直接紐づけています。
これにより、わざわざ $Id = $inputObject.Id というようなコードを書く必要がなくなります。

        [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName, ParameterSetName = 'ById')]
        [string]$Id
  • HTTP response が 200 OK ではないときに、sleep して再度リクエストする

単なる idiom ではあるのですが、つい忘れちゃうのでこれも ChatGPT にやってもらいました。
当初は Invoke-AzRestMethod だけがあったのですが、リトライ入れてー、ってお願いしたらこれです、すばらしい。

    $sessionResponse = $null
    do {
        $sessionResponse = Invoke-AzRestMethod -Method GET -Uri $restResponse.Headers.Location.AbsoluteUri
        if ($sessionResponse.StatusCode -ne 200) {
            Write-Debug "$(Get-Date -Format "h:MM:ss tt") - Received status code $($sessionResponse.StatusCode). Retrying in 5 seconds..."
            Start-Sleep -Seconds 5
        }
    } while ($sessionResponse.StatusCode -ne 200)

まとめ

実際にはこの cmdlet 自体が必要なわけではなく、これを使って、使っていない Bastion を定期的に削除するものを作りたいのでした。
まだ定期実行までは実装していないのですが、使ってなさそうな Azure Bastion を削除するという記事をこちらに書いています。

https://zenn.dev/skmkzyk/articles/remove-unused-bastion

Microsoft (有志)

Discussion