Teamsから自分に割り当てられたWindows Virtual Desktopを起動・停止する

こんにちはパーソルプロセス&テクノロジーの内田です。
今回は、Windows Virtual Desktopのシングルセッション構成での電源管理を紹介します。具体的には次の画像のように、ユーザーがMicrosoft Teams経由で自身に割り当てられたAzure Virtual Machineを起動・停止できる仕組みを作っていきたいと思います。

シングルセッション構成での電源管理

内容に入っていく前に、まずはWVDの電源管理について考えていくことにします。
WVDはホストプール単位で、シングルセッション、マルチセッションの設定を行います。

  • マルチセッション環境では、セッションホストのリソース状況に応じた縮退運転となるので、実装はかなり難しいものとなります。
  • 一方、シングルセッション環境では「1ユーザーに対して1VM」が割り当てられているので、使いたいときにユーザーが適宜起動させればよいわけです。

とはいっても、
・従業員一人一人がAzure Portalにサインインして
・その中から自分に割り当てられた仮想マシンを探し
・起動・停止の操作を行う
というのは、現実的ではありません。

ユーザーにとってAzure Portalでの作業というのは、敷居が高いものです。
また、RBACで権限管理できるとはいえ、万が一漏れがあった場合には業務サーバーをユーザーが停止させてしまうリスクは背負いたくありませんよね。

安全にかつ簡単にユーザーに電源ON/OFFさせたい!

そういった背景から、今回作成するツールの要件をざっくりまとめてみます。

  • ユーザー手動でVMの電源ON/OFFができること
  • 対象VMはユーザーに割り当てられたVMのみであること(Safety!
  • 業務で普段使うTeamsに統合すること(Easy!

※とりあえず上記の点だけ抑えるツールにします。(作りこむときりがないので)

ちなみにシステムの概要図は以下の通りです。

処理の流れをざっくり説明していきます。

  1. Logic appsにて毎日の業務開始時にTeams経由でユーザーへ通知を行います。
  2. ユーザーは通知を受け取った後、任意のタイミングで起動、停止のリクエストを送信します。
  3. リクエストをLogic appsにて受け取った後、RunbookのWebhookを叩きます。
  4. Runbookの処理の中で、ユーザーに割り当てられたVMを特定し、起動、停止のアクションを実施します。

実際に作っていく

1.Automation Accountの作成

今回ユーザーに割り当てられたVMの探索、起動、停止をAzure Automation AccountのRunbookにて行います。

リソースの作成からAutomationと検索し、上記の画面の作成をクリックします。Runbookの認証に実行アカウントを利用するので、「Azure実行アカウントの作成」では「はい」を選択してください。

2.Azure実行アカウントにRDS Reader権限を付与

どのセッションホストにユーザーが割り当てられているのかを確認するには、少なくともRDS Reader権限が必要ですので付与していきます。

まず作成されたAutomation Accountリソースに移動し、[アカウント設定]-[実行アカウント]-[Azure実行アカウント]を選択します。

そうするとAzure実行アカウントのプロパティ画面に遷移しますので、そこに記載されている「アプリケーションID」を控えます。

続いてPowershellを開いたら、次のコマンドを利用してAzure実行アカウントに権限を付与します。<ApplicationId>には、先ほど控えたIDを入力してください。

# サインイン
Add-RdsAccount -DeploymentUrl https://rdbroker.wvd.microsoft.com
# テナント情報取得
$tenant = Get-RdsTenant
# 権限付与
New-RdsRoleAssignment -RoleDefinitionName “RDS Reader” -ApplicationId <ApplicationId>-TenantName $tenant.TenantName

4.変数の追加、モジュールインストール、Runbookの作成

いくつかの値はAutomation Accountの共有リソースの変数に格納し利用することにします。次の画像に示す通りタイプは文字列で、以下の変数を追加してください。値は変数名の通りです。
AADTenantId, HostpoolName, RDBrokerURL, SubscriptionID, TenantName

次にRunbookのスクリプトで利用する次の3つのPowershellモジュールをAutomation Accountにインポートします。
・Az.Accounts
・Az.Compute
・Microsoft.RDInfra.RDPowershell
[共有リソース]-[共有モジュールギャラリー]をクリックして、上記モジュールを検索の上インポートを実施してください。ちなみに、Az.ComputeはAz.Accountsの依存関係がありますので、先にAz.Accountsモジュールをインポートしてください。

スクリプトの実態となるRunbookの種類はPowershellを選択ください。
尚スクリプトはサンプルコードとして公開します。ページ下部に記載するので、それをコピーしてRunbookを作成ください。
Runbookの作成が完了したら、公開をクリックして発行してください。

5.RunbookにWebhookの追加

続いてRunbookにWebhookを追加していきます。
[リソース]-[Webhook]-[Webhookの追加]をクリックします。

作成時には忘れずに「URL」を控えてください。後程使うのですが、このURLはセキュリティの観点から後から確認することができません。
尚、パラメータと実行設定の「WEBHOOKDATA」は未記入で問題ありません。

6.Logic appsにてフローを作成する

Flowの簡略化のため、今回は特定の1人のユーザーを指定するものとします。
まず最初に画像のようにしてトリガーを作成します。

これに続くアクションとして「アダプティブ カードをTeamsユーザーに投稿して応答を待機(プレビュー)」を指定します。

メッセージは次のように入力しましょう。

{
“$schema”: “http://adaptivecards.io/schemas/adaptive-card.json”,
“type”: “AdaptiveCard”,
“version”: “1.0”,
“body”: [
{
“type”: “TextBlock”,
“text”: “起動/停止を押してください”,
“size”: “Medium”,
“weight”: “Bolder”
}
],
“actions”: [
{
“type”: “Action.Submit”,
“title”: “起動”,
“data”: {
“statusId”: 0,
“statusName”: “Start”
},
“style”: “positive”
},
{
“type”: “Action.Submit”,
“title”: “停止”,
“data”: {
“statusId”: 1,
“statusName”: “Stop”
},
“style”: “destructive”
}
]
}

そして「受信者」に今回対象となるユーザーを指定します。

最後のアクションとしてHTTPを選択し、先ほど控えておいたWebhookのURLを指定します。

本文(body)は画像のように入力してください。

動作確認

以上で設定は終了です。動作確認をするために、Logic Appsを保存して「実行」をクリックしてください。

指定したユーザーのTeamsに上記のようなアダプティブ カードが出現し、ボタン押下後にそのユーザーに割り当てられているセッションホストが起動/停止することが確認できれば成功です!


最後に

今回はLogic appsとRunbookを組み合わせて、使い慣れたTeams上からユーザー自身でVMを起動、停止する仕組みをご紹介いたしました。VMの稼働時間は月額料金に響くものですので、このような仕組みを使ってAzureをお得にご利用いただければと思います。

ちなみに今回ご紹介したツールは業務開始時にのみトリガーされるツールとなりますが、Teams Botを作成すればユーザーの任意のタイミングでトリガーさせることも可能です。
次の画像のようなイメージで、Botに話しかけるとトリガーされます。

※上記の画像は弊社のフリーアドレスを実現する座席管理ツールのTeams Botです。

尚、弊社ではAzure仮想マシンの電源管理を一括で管理するサービスもご提供しております。こちらはWVDにも対応しておりますので、ご興味がございましたらこちらよりお気軽にお問い合わせください。

おまけ:Runbookのサンプルスクリプト

param(
[Parameter(mandatory = $false)]
[object]$WebHookData
)
# If runbook was called from Webhook, WebhookData will not be null.
if ($WebHookData) {

# Collect properties of WebhookData
$WebhookName = $WebHookData.WebhookName
$WebhookHeaders = $WebHookData.RequestHeader
$WebhookBody = $WebHookData.RequestBody
# debug print
Write-Output (“WebhookName Variable: ” + $WebhookName)
Write-Output (“WebhookHeaders Variable: ” + $WebhookHeaders)
Write-Output (“WebhookBody Variable: ” + $WebhookBody)

# Collect individual headers. Input converted from JSON.
$From = $WebhookHeaders.From
$Input = (ConvertFrom-Json -InputObject $WebhookBody)
}
else
{
Write-Error -Message ‘Runbook was not started from Webhook’ -ErrorAction stop
}
# Initialize
$UserPrincipalName=$Input.UserPrincipalName
$Action=$Input.Action
$AADTenantId=Get-AutomationVariable -Name ‘AADTenantId’
$SubscriptionID=Get-AutomationVariable -Name ‘SubscriptionID’
$RDBrokerURL=Get-AutomationVariable -Name ‘RDBrokerURL’
$TenantName=Get-AutomationVariable -Name ‘TenantName’
$HostpoolName=Get-AutomationVariable -Name ‘HostpoolName’

Set-ExecutionPolicy -ExecutionPolicy Undefined -Scope Process -Force -Confirm:$false
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope LocalMachine -Force -Confirm:$false
# Setting ErrorActionPreference to stop script execution when error occurs
$ErrorActionPreference = “Stop”
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

### MAIN ###
# Collect the credentials from Azure Automation Account Assets
$Connection = Get-AutomationConnection -Name ‘AzureRunAsConnection’

# Authenticating to Azure
Clear-AzContext -Force
$AZAuthentication = Connect-AzAccount -ApplicationId $Connection.ApplicationId -TenantId $AADTenantId -CertificateThumbprint $Connection.CertificateThumbprint -ServicePrincipal
if ($AZAuthentication -eq $null) {
Write-Output “Failed to authenticate Azure: $($_.exception.message)”
exit
} else {
$AzObj = $AZAuthentication | Out-String
Write-Output “Authenticating as service principal for Azure. Result: `n$AzObj”
}
# Set the Azure context with Subscription
$AzContext = Set-AzContext -SubscriptionId $SubscriptionID
if ($AzContext -eq $null) {
Write-Error “Please provide a valid subscription”
exit
} else {
$AzSubObj = $AzContext | Out-String
Write-Output “Sets the Azure subscription. Result: `n$AzSubObj”
}

# Authenticating to WVD
try {
$WVDAuthentication = Add-RdsAccount -DeploymentUrl $RDBrokerURL -ApplicationId $Connection.ApplicationId -CertificateThumbprint $Connection.CertificateThumbprint -AADTenantId $AadTenantId
}
catch {
Write-Output “Failed to authenticate WVD: $($_.exception.message)”
exit
}
$WVDObj = $WVDAuthentication | Out-String
Write-Output “Authenticating as service principal for WVD. Result: `n$WVDObj”

# Function to Start the assigned VM
function Start-VM
{
param(
[string]$SessionHostName
)
$vm = Get-AzVM -Name $SessionHostName
$StartRtn = $vm | Start-AzVM -ErrorAction Continue
# debug Pring
Write-Output (“vm Variable” + $vm)
Write-Output (“StartRtn Variable” + $StartRtn)

if ($StartRtn.Status -ne ‘Succeeded’)
{
# The VM failed to start, so send notice
Write-Output ($vm.Name + ” failed to start”)
Write-Error ($vm.Name + ” failed to start. Error was:”) -ErrorAction Continue
Write-Error (ConvertTo-Json $StartRtn.Error) -ErrorAction Continue
}
else
{
# The VM stopped, so send notice
Write-Output ($vm.Name + ” has been started”)
}
}

# Function to Stop the assigned VM
function Stop-VM
{
param(
[string]$SessionHostName
)
$vm = Get-AzVM -Name $SessionHostName
$StopRtn = $vm | Stop-AzVM -Force -ErrorAction Continue
# debug Pring
Write-Output (“vm Variable” + $vm)
Write-Output (“StartRtn Variable” + $StartRtn)

if ($StopRtn.Status -ne ‘Succeeded’)
{
# The VM failed to stop, so send notice
Write-Output ($VM.Name + ” failed to stop”)
Write-Error ($VM.Name + ” failed to stop. Error was:”) -ErrorAction Continue
Write-Error (ConvertTo-Json $StopRtn.Error) -ErrorAction Continue
}
else
{
# The VM stopped, so send notice
Write-Output ($VM.Name + ” has been stopped”)
}
}

# Get Assigned VM Info
$VMInfo = Get-RdsSessionHost -TenantName $TenantName -HostPoolName $HostPoolName | Where-Object{ $_.AssignedUser -eq $UserPrincipalName}
$SessionHostName = $VMInfo.SessionHostName -replace ‘^([^\.]+).*’,’$1′
# debug print
Write-Output (“VMInfo Variable: ” + $VMInfo)
Write-Output (“SessionHostName Variable: ” + $SessionHostName)
Write-Output (“Action Variable: ” + $Action)
# Start or Stop Assigned VM
switch($Action)
{
“Start” {Start-VM -SessionHostName $SessionHostName}
“Stop” {Stop-VM -SessionHostName $SessionHostName}
}

いいね (この記事が参考になった人の数:9)
(↑参考になった場合はハートマークを押して評価お願いします)
読み込み中...

注意事項・免責事項

※技術情報につきましては投稿日時点の情報となります。投稿日以降に仕様等が変更されていることがありますのでご了承ください。

※公式な技術情報の紹介の他、当社による検証結果および経験に基づく独自の見解が含まれている場合がございます。

※これらの技術情報によって被ったいかなる損害についても、当社は一切責任を負わないものといたします。十分な確認・検証の上、ご活用お願いたします。

※当サイトはマイクロソフト社によるサポートページではございません。パーソルプロセス&テクノロジー株式会社が運営しているサイトのため、マイクロソフト社によるサポートを希望される方は適切な問い合わせ先にご確認ください。
 【重要】マイクロソフト社のサポートをお求めの方は、問い合わせ窓口をご確認ください