Hyper-V 上の Windows 11 で WSL2 を有効化する

docs.microsoft.com

いつの間にか WSL のインストール方法が簡単になっていた。

PowerShell を管理者として起動して以下のコマンドを実行する。既定では Ubuntu がインストールされる。インストールが完了したら再起動。

wsl --install

再起動後、Ubuntu が自動的に起動してユーザー名とパスワードの設定が求められるので、設定する。

起動したらアップデートしておきましょう。

sudo apt update
sudo apt upgrade

めちゃくちゃ簡単ですね。素晴らしい。

ちなみに初期化も簡単にできます。

atmarkit.itmedia.co.jp

Hyper-V 上の Windows 11 で Hyper-V を有効化する

VM を停止した状態で、Hyper-V ホスト側で PowerShell を管理者として起動して以下のコマンドを実行する。

$vmName = "Win11" # 仮想マシンの名前
Set-VMProcessor -VMName $vmName -ExposeVirtualizationExtensions $true

VM を起動して、VM 上で PowerShell を管理者として起動して以下のコマンドを実行する。

実行後、再起動を促されるので再起動する。

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All

ここによると何かしらネットワーク設定が必要なように見えるけど Windows 11 を普通にインストールしたら使えた(Default Switch を指定)。 docs.microsoft.com

Bot Framework SDK で開発したボットを Teams で利用するために必要な構成

ボットを Teams で利用するために登録する方法は2つある。

Azure Bot は Azure のリソースとして登録する。作成すると Azure AD にアプリケーションとして登録される。

Bot Framework PortalGUI からボットを登録しようとすると Azure Portal に誘導される。Bot Framework Portal でボットを作成するためには作成用の URL に直接アクセスする必要がある。

docs.microsoft.com

Bot Framework Portal から作成した場合も Azure AD にアプリケーションとして登録される。

Teams App StudioTeams Developer Portal からボットを作成した場合は Bot Framework Portal にボットとして登録される。

Azure Bot も無料で利用できるので Azure サブスクリプションが使えるのであれば Azure Bot を使った方が良さそう、というか Microsoft としては使ってほしそう。

ただ Azure Bot のためだけに Azure サブスクリプションを用意するのは…という場合は Bot Framework Portal にボットを登録すればよい。

...という感じだろうか。最近のドキュメントは Azure Bot 前提で記載してあるのがほとんどで Bot Framework Portal についての記述がほとんどなくて混乱した。

こちらも参照。

www.neilwithdata.com

Azure Bot が最近?マネージド ID に対応したので Azure 使っている場合は Azure App Service でボットをホストしてマネージド ID を使うのがベストプラクティスっぽい。

zenn.dev

[随時更新] Windows 11 にインストールするアプリケーション一覧

管理者としてターミナルを実行してインストールを実施する。(Windows キー + x)

# 既定でインストールされているターミナルを最新化
winget upgrade --id Microsoft.WindowsTerminal

# スコープはすべてシステム。--interactive を外すと完全自動インストールになるが設定を既定値以外にしたいので対話型インストールにしている。
winget install --scope machine --id Microsoft.PowerShell --interactive
winget install --scope machine --id Microsoft.VisualStudioCode --interactive
winget install --scope machine --id Git.Git --interactive
winget install --scope machine --id Google.Chrome --interactive
winget install --scope machine --id Mozilla.Firefox --interactive
winget install --scope machine --id 7zip.7zip --interactive
winget install --scope machine --id NickeManarin.ScreenToGif --interactive
winget install --scope machine --id Adobe.Acrobat.Reader.64-bit --interactive
winget install --scope machine --id Microsoft.PowerToys --interactive

# --scope machine を入れると失敗する。入れなくても Program Files 配下にインストールされる
winget install --id Microsoft.VisualStudio.2022.Community --interactive

Bot Framework SDK で開発したボットを Teams で動かすところまで

プロジェクトの作成

f:id:tamtamyarn:20211230105351p:plain

f:id:tamtamyarn:20211230105454p:plain

f:id:tamtamyarn:20211230105514p:plain

Bot Framework Emulator で動作確認

Visual Studioデバッグ実行しておく。

Bot Framework Emulator を起動して [File] -[Open Bot] を開く。

[Bot URL] に http://localhost:3978/api/messages と入力して [Connect] をクリックする。

f:id:tamtamyarn:20211230105950p:plain

動作確認。

f:id:tamtamyarn:20211230110252p:plain

Bot を Azure にデプロイする

Visual Studio から発行する。

f:id:tamtamyarn:20211230110457p:plain

f:id:tamtamyarn:20211230110525p:plain

f:id:tamtamyarn:20211230112730p:plain

プロファイルを作成したら発行する。

Bot Framework Emulator で動作確認

Bot Framework Emulator で ngrok のパスを指定する。

f:id:tamtamyarn:20211230111305p:plain

Bot URL に http://{Azure Web App の URL}/api/messages と入力して [Connect] をクリックする。

f:id:tamtamyarn:20211230111409p:plain

動作確認。

f:id:tamtamyarn:20211230112946p:plain

Azure Bot の作成

f:id:tamtamyarn:20211230113422p:plain

Key Vault にクライアントシークレットが格納されているので見に行く(本当は Web App からマネージド ID で見に行くのがいいのだろうな)

Web App の Application 設定に MicrosoftAppId / MicrosoftAppPassword / MicrosoftAppTenantId を設定する。

Azure Bot のメッセージングエンドポイントに http://{Azure Web App の URL}/api/messages を設定する。

「Web チャットでテスト」で動作確認。

f:id:tamtamyarn:20211231083418p:plain

Teams チャネル追加

f:id:tamtamyarn:20211231083544p:plain

「Open in Teams」をクリック。

f:id:tamtamyarn:20211231084051p:plain

アプリパッケージ作成

manifest.json

idbotId にアプリケーション ID を入力。

{
  "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.11/MicrosoftTeams.schema.json",
  "manifestVersion": "1.11",
  "version": "1.0.0",
  "id": "Micrsoft_APP_ID",
  "packageName": "com.example.mysamplebot",
  "developer": {
    "name": "The Developer",
    "websiteUrl": "https://example.com/",
    "privacyUrl": "https://example.com/privacy",
    "termsOfUseUrl": "https://example.com/app-tos"
  },
  "name": {
    "short": "EchoBot",
    "full": "Echo-Bot"
  },
  "description": {
    "short": "Echo Bot.",
    "full": "This is the Echo Bot."
  },
  "icons": {
    "outline": "icon32x32.png",
    "color": "icon192x192.png"
  },
  "accentColor": "#ff0000",
  "bots": [
    {
      "botId": "Microsft_APP_ID",
      "needsChannelSelector": false,
      "isNotificationOnly": false,
      "scopes": [
        "personal",
        "team",
        "groupchat"
      ],
      "supportsFiles": false,
      "commandLists": []
    }
  ]
}

アイコン

192 x 192 と 32 x 32 の PNG ファイルを用意する。manifest.json でファイル名を指定する。

圧縮

マニフェストファイルとアイコン2つを ZIP で固める。

アプリアップロード(サイドローディング)

Teams 管理センター の「Teams のアプリ」-「セットアップポリシー」から「カスタムアプリをアップロード」をオンにする。

f:id:tamtamyarn:20211231135353p:plain

Teams クライアントを開いて「アプリ」-「アプリを管理」-「カスタムアプリをアップロード」をクリック。アプリパッケージの ZIP ファイルを選択して追加する。

f:id:tamtamyarn:20211231135537p:plain

アプリアップロード(組織のアプリカタログ)

Teams クライアントを開いて「アプリ」-「アプリを管理」-「組織のアプリカタログにアプリをアップロードします」をクリック。アプリパッケージの ZIP ファイルを選択して追加する。

「アプリ」-「組織向けに開発」にアプリが表示される。

f:id:tamtamyarn:20211231140020p:plain

"Build Bot Framework bots with Microsoft Graph" を試した

docs.microsoft.com

おそらく仕様が変わっていて、何か所か悩むところがあったので忘れないように残す。

Create a Bot Channels registration

タイトルが Bot Channels Registration になっているけど手順は Azure Bot

Azure Bot 作成時にシングルテナント、マルチテナント、マネージド ID の選択肢が出てくる。 手順通りに進めるならマルチテナント。シングルテナントとマネージド ID は前は選べなかったっぽい。

手順 13 以降が Bot Channel Registration の設定になっているけど、この手順は不要。

Add Microsoft identity platform authentication

appsettings.json が若干違った。

{
  "MicrosoftAppType": "",
  "MicrosoftAppId": "YOUR_BOT_APP_ID_HERE",
  "MicrosoftAppPassword": "YOUR_BOT_CLIENT_SECRET_HERE",
  "MicrosoftAppTenantId":  "YOUR_TENANT_ID_HERE",
  "ConnectionName": "GraphBotAuth"
}

コードをそのまま使うとログアウトに失敗する。(BotFrameworkAdapter にキャストできない。)

以下 2 行を

var botAdapter = (BotFrameworkAdapter)innerDc.Context.Adapter;
await botAdapter.SignOutUserAsync(innerDc.Context, ConnectionName, null, cancellationToken);

以下のように修正する。

var userTokenClient = innerDc.Context.TurnState.Get<UserTokenClient>();
await userTokenClient.SignOutUserAsync(innerDc.Context.Activity.From.Id, ConnectionName, innerDc.Context.Activity.ChannelId, cancellationToken).ConfigureAwait(false);

docs.microsoft.com

Test authentication

ここで指定する Microsoft App ID は Graph Calendar Bot Auth ではなく Azure Bot 作成時に作られたアプリの方の Application ID。

Endpoint URL が https になっているが正しくは http。

謎のエラーが出たが Bot Framework Emulator と ngrok を最新化すると解消。

認証用のコードを入力する画面は表示されない。

Get the logged on user

ユーザーに写真が設定されていないとユーザー取得に失敗する。

「ASP.NET Core Blazor WebAssembly でホストされるアプリを Azure Active Directory でセキュリティ保護する」のアプリ登録をコマンドで。

docs.microsoft.com

サーバー API アプリの登録。

# Create Application Object
$app = az ad app create --display-name "Blazor Server AAD"

# Get Application ID
$appId = ($app | ConvertFrom-Json).appId

# Create Service Principal
az ad sp create --id $appId

# 既定のスコープを削除
$oauth2Permissions = ($app | ConvertFrom-Json).oauth2Permissions
$oauth2Permissions[0].isEnabled = $false
$oauth2Permissions = ConvertTo-Json -InputObject @($oauth2Permissions)
$oauth2Permissions | Out-File -FilePath .\oauth2Permissions.json
az ad app update --id $appId --set oauth2Permissions=`@oauth2Permissions.json
az ad app update --id $appId --set oauth2Permissions="[]"
Remove-Item -Path .\oauth2Permissions.json

# アプリケーションIDのURIを追加
az ad app update --id $appId --identifier-uris "api://$appId"

# スコープを追加
$oauth2Permissions = @{
    adminConsentDescription = "Allows the app to access server app API"
    adminConsentDisplayName = "Access API"
    id = (New-Guid).Guid
    isEnabled = $true
    type = "Admin"
    userConsentDescription = ""
    userConsentDisplayName = ""
    value = "API.Access"
}
$oauth2Permissions = ConvertTo-Json -InputObject @($oauth2Permissions)
$oauth2Permissions | Out-File -FilePath .\oauth2Permissions.json
az ad app update --id $appId --set oauth2Permissions=`@oauth2Permissions.json
Remove-Item -Path .\oauth2Permissions.json

クライアントアプリの登録。

# Create Application Object
$app = az ad app create --display-name "Blazor Client AAD"

# Get Application ID & Object ID
$appId = ($app | ConvertFrom-Json).appId
$objectId = ($app | ConvertFrom-Json).objectId

# Create Service Principal
az ad sp create --id $appId

# 既定のスコープを削除
$oauth2Permissions = ($app | ConvertFrom-Json).oauth2Permissions
$oauth2Permissions[0].isEnabled = $false
$oauth2Permissions = ConvertTo-Json -InputObject @($oauth2Permissions)
$oauth2Permissions | Out-File -FilePath .\oauth2Permissions.json
az ad app update --id $appId --set oauth2Permissions=`@oauth2Permissions.json
az ad app update --id $appId --set oauth2Permissions="[]"
Remove-Item -Path .\oauth2Permissions.json

# アクセストークンを取得する
$response = az account get-access-token --resource-type ms-graph | ConvertFrom-Json
$accessToken = $response.accesstoken
$headers = @{"Authorization" = "Bearer $accessToken"; "Content-Type" = "application/json" }

# SPA のリダイレクトURLを設定する
$body = @{
    spa = @{
        redirectUris = @(
            "https://localhost:5001/authentication/login-callback"
        )
    }
} | ConvertTo-Json
Invoke-RestMethod -Method Patch -Uri https://graph.microsoft.com/v1.0/applications/$objectId -Headers $headers -Body $body

# 暗黙的な許可を無効にする
$body = @{
    web = @{
        implicitGrantSettings = @{
            enableAccessTokenIssuance = $false
            enableIdTokenIssuance     = $false
        }
    }
} | ConvertTo-Json
Invoke-RestMethod -Method Patch -Uri https://graph.microsoft.com/v1.0/applications/$objectId -Headers $headers -Body $body

# アクセス許可を付与する
$apiName = "Microsoft Graph"
$permissionType = "Scope"
$permissionName = "User.Read"
$apiServicePrincipal = az ad sp list --filter "displayname eq '$apiName'" | ConvertFrom-Json
$apiPermission = $apiServicePrincipal.oauth2Permissions | Where-Object { $_.value -eq $permissionName }
az ad app permission add --id $appId --api $apiServicePrincipal.appId --api-permissions "$($apiPermission.id)=$permissionType"

# アクセス許可を付与する
$apiName = "Blazor Server AAD"
$permissionType = "Scope"
$permissionName = "API.Access"
$apiServicePrincipal = az ad sp list --filter "displayname eq '$apiName'" | ConvertFrom-Json
$apiPermission = $apiServicePrincipal.oauth2Permissions | Where-Object { $_.value -eq $permissionName }
az ad app permission add --id $appId --api $apiServicePrincipal.appId --api-permissions "$($apiPermission.id)=$permissionType"

# 管理者の同意を与える
az ad app permission grant --id $appId --api $apiServicePrincipal.appId --scope $permissionName