Azure CLI で Azure AD アプリケーションを作成して既定のスコープを削除する
Azure CLI でアプリ登録すると既定で user_impersonation っていうスコープが作成されるけど使わないので消したい。
# Azure にログイン az login # アプリの新規登録 $app = az ad app create --display-name "DeleteScopeApp" # アプリケーションIDを取得 $appId = ($app | ConvertFrom-Json).appId # スコープを取得 $oauth2Permissions = ($app | ConvertFrom-Json).oauth2Permissions # 既定のスコープを無効に設定する(有効だと削除できないため) $oauth2Permissions[0].isEnabled = $false # 変更したオブジェクトを JSON 形式に変換する $oauth2Permissions = ConvertTo-Json -InputObject @($oauth2Permissions) # JSON をファイルに出力する $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
何故かは良くわからないのだけど設定用の JSON はファイルに書き出さないとうまくいかなかった。公式には以下の記載があるが Windows だとうまくいかない?
###Remove api permissions: disable default exposed scope first default_scope=$(az ad app show --id $clientid | jq '.oauth2Permissions[0].isEnabled = false' | jq -r '.oauth2Permissions') az ad app update --id $clientid --set oauth2Permissions="$default_scope" az ad app update --id $clientid --set oauth2Permissions="[]"
こちらのサイトを参考にしました。
これに関しては Azure AD モジュールを使った方がよいのかも。
Azure AD でデバイスコードフローを試す
Azure AD にアプリを登録してパブリッククライアントフローを有効化する。
# Azure CLI az login # Create Application $app = az ad app create --display-name "Device Code Flow App" # Get Application ID $appId = ($app | ConvertFrom-Json).appId # Create Service Principal az ad sp create --id $appId # Enable Public Client Flow az ad app update --id $appId --set publicClient=true
# Get Tenant ID $tenantId = (az account show | ConvertFrom-Json ).tenantId # Create HTTP Body for Getting Device Code $body = @{ client_id = $appId scope = "User.Read" } # Request Device Code $response = Invoke-RestMethod -Method Post -Uri https://login.microsoftonline.com/$tenantId/oauth2/v2.0/devicecode -Body $body -ContentType "application/x-www-form-urlencoded" # Copy User Code to Clipboard $response.user_code | clip.exe # Open Verification Url in Browser, Paste User Code and Authenticate Start-Process $response.verification_uri # Create HTTP Body for Getting Access Token $body = @{ grant_type = "urn:ietf:params:oauth:grant-type:device_code" client_id = $appId device_code = $response.device_code } # Request Access Token $response = Invoke-RestMethod -Method Post -Uri https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token -Body $body -ContentType "application/x-www-form-urlencoded" # Get Access Token $accessToken = $response.access_token
アクセストークンを使って Microsoft Graph API を呼び出す。
$headers = @{"Authorization" = "Bearer $accessToken"} Invoke-RestMethod -Uri https://graph.microsoft.com/v1.0/me -Headers $headers
クライアントシークレットを環境変数から取得する
まず環境変数にクライアントシークレットを登録する。(値はダミー)
$clinetSecret = "bXw7Q~Pn0fv8NHXBZdWzgkRm2gzFd-.fsZx~O" [System.Environment]::SetEnvironmentVariable("ClientSecret", $clinetSecret, "User")
登録後 PowerShell を再起動しないと環境変数を読み取れないので注意。
呼出し側。
$config = Get-Content -Path $PSScriptRoot\appsettings.json | ConvertFrom-Json $applicationId = $config.applicationId $directoryId = $config.directoryId $scope = "https://graph.microsoft.com/.default" $clientSecret = $env:ClientSecret # 環境変数から取得 $response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$directoryId/oauth2/v2.0/token" -Method Post -Body @{client_id=$applicationId; scope=$scope; client_secret=$clientSecret; grant_type="client_credentials"} $accessToken = $response.access_token $headers = @{"Authorization" = "Bearer " + $accessToken} $users = Invoke-WebRequest -Uri https://graph.microsoft.com/v1.0/users -Headers $headers | ConvertFrom-Json $users.value | Select-Object UserPrincipalName
[PowerShell] appsettings.json から情報を取り出す
appsettings.json
(値はダミー)
{ "applicationId": "56c0ac2c-c858-4781-ad48-749312fb2fdd", "clientSecret": "bXw7Q~Pn0fv8NHXBZdWzgkRm2gzFd-.fsZx~O", "directoryId": "a9fc19ab-08cf-44a9-b2dc-1fd0542d6b4f" }
呼出し側。appsettings.json と同じ階層に。
$config = Get-Content -Path $PSScriptRoot\appsettings.json | ConvertFrom-Json $applicationId = $config.applicationId $clientSecret = $config.clientSecret $directoryId = $config.directoryId $scope = "https://graph.microsoft.com/.default" $response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$directoryId/oauth2/v2.0/token" -Method Post -Body @{client_id=$applicationId; scope=$scope; client_secret=$clientSecret; grant_type="client_credentials"} $accessToken = $response.access_token $headers = @{"Authorization" = "Bearer " + $accessToken} $users = Invoke-WebRequest -Uri https://graph.microsoft.com/v1.0/users -Headers $headers | ConvertFrom-Json $users.value | Select-Object UserPrincipalName
PowerShell で JSON ファイルをインポートする
Get-Content
でファイルを読み込んで ConvertFrom-Json
でオブジェクトに変換する。
$config = Get-Content -Path .\appsettings.json | ConvertFrom-Json
PowerShell モジュールのバージョン管理
psm1 ファイルではバージョンを指定する方法がないのでインポートするとバージョンが 0.0 になります。
> Import-Module .\Modules\Greeting\Greeting.psm1 > Get-Module Greeting ModuleType Version Name ---------- ------- ---- Script 0.0 Greeting
バージョンを管理するためにはマニフェストファイル(psd1)を用意する必要があります。マニフェストファイルのテンプレートは New-ModuleManifest
で作成することができます。psd1 は psm1 と同じ階層に出力します。
New-ModuleManifest -Path .\Modules\Greeting\Greeting.psd1 -Author tamtamyarn -RootModule Greeting.psm1 -ModuleVersion 1.0.0
出力されたテンプレートは以下のようになります。
# # モジュール 'Greeting' のモジュール マニフェスト # # 生成者: tamtamyarn # # 生成日: 2021/12/19 # @{ # このマニフェストに関連付けられているスクリプト モジュール ファイルまたはバイナリ モジュール ファイル。 RootModule = 'Greeting.psm1' # このモジュールのバージョン番号です。 ModuleVersion = '1.0.0' # サポートされている PSEditions # CompatiblePSEditions = @() # このモジュールを一意に識別するために使用される ID GUID = '586ac6fe-8f39-465c-bb0d-0bec9cac6ca7' # このモジュールの作成者 Author = 'tamtamyarn' # このモジュールの会社またはベンダー CompanyName = '不明' # このモジュールの著作権情報 Copyright = '(c) 2021 tamtamyarn. All rights reserved.' # このモジュールの機能の説明 # Description = '' # このモジュールに必要な Windows PowerShell エンジンの最小バージョン # PowerShellVersion = '' # このモジュールに必要な Windows PowerShell ホストの名前 # PowerShellHostName = '' # このモジュールに必要な Windows PowerShell ホストの最小バージョン # PowerShellHostVersion = '' # このモジュールに必要な Microsoft .NET Framework の最小バージョン。 この前提条件は、PowerShell Desktop エディションについてのみ有効です。 # DotNetFrameworkVersion = '' # このモジュールに必要な共通言語ランタイム (CLR) の最小バージョン。 この前提条件は、PowerShell Desktop エディションについてのみ有効です。 # CLRVersion = '' # このモジュールに必要なプロセッサ アーキテクチャ (なし、X86、Amd64) # ProcessorArchitecture = '' # このモジュールをインポートする前にグローバル環境にインポートされている必要があるモジュール # RequiredModules = @() # このモジュールをインポートする前に読み込まれている必要があるアセンブリ # RequiredAssemblies = @() # このモジュールをインポートする前に呼び出し元の環境で実行されるスクリプト ファイル (.ps1)。 # ScriptsToProcess = @() # このモジュールをインポートするときに読み込まれる型ファイル (.ps1xml) # TypesToProcess = @() # このモジュールをインポートするときに読み込まれる書式ファイル (.ps1xml) # FormatsToProcess = @() # RootModule/ModuleToProcess に指定されているモジュールの入れ子になったモジュールとしてインポートするモジュール # NestedModules = @() # このモジュールからエクスポートする関数です。最適なパフォーマンスを得るには、ワイルドカードを使用せず、エクスポートする関数がない場合は、エントリを削除しないで空の配列を使用してください。 FunctionsToExport = '*' # このモジュールからエクスポートするコマンドレットです。最適なパフォーマンスを得るには、ワイルドカードを使用せず、エクスポートするコマンドレットがない場合は、エントリを削除しないで空の配列を使用してください。 CmdletsToExport = '*' # このモジュールからエクスポートする変数 VariablesToExport = '*' # このモジュールからエクスポートするエイリアスです。最適なパフォーマンスを得るには、ワイルドカードを使用せず、エクスポートするエイリアスがない場合は、エントリを削除しないで空の配列を使用してください。 AliasesToExport = '*' # このモジュールからエクスポートする DSC リソース # DscResourcesToExport = @() # このモジュールに同梱されているすべてのモジュールのリスト # ModuleList = @() # このモジュールに同梱されているすべてのファイルのリスト # FileList = @() # RootModule/ModuleToProcess に指定されているモジュールに渡すプライベート データ。これには、PowerShell で使用される追加のモジュール メタデータを含む PSData ハッシュテーブルが含まれる場合もあります。 PrivateData = @{ PSData = @{ # このモジュールに適用されているタグ。オンライン ギャラリーでモジュールを検出する際に役立ちます。 # Tags = @() # このモジュールのライセンスの URL。 # LicenseUri = '' # このプロジェクトのメイン Web サイトの URL。 # ProjectUri = '' # このモジュールを表すアイコンの URL。 # IconUri = '' # このモジュールの ReleaseNotes # ReleaseNotes = '' } # PSData ハッシュテーブル終了 } # PrivateData ハッシュテーブル終了 # このモジュールの HelpInfo URI # HelpInfoURI = '' # このモジュールからエクスポートされたコマンドの既定のプレフィックス。既定のプレフィックスをオーバーライドする場合は、Import-Module -Prefix を使用します。 # DefaultCommandPrefix = '' }
項目が多くてごちゃごちゃしているので最低限の要素以外は削除します。
@{ # このマニフェストに関連付けられているスクリプト モジュール ファイルまたはバイナリ モジュール ファイル。 RootModule = 'Greeting.psm1' # このモジュールのバージョン番号です。 ModuleVersion = '1.0.0' # このモジュールを一意に識別するために使用される ID GUID = '586ac6fe-8f39-465c-bb0d-0bec9cac6ca7' # このモジュールからエクスポートする関数です。最適なパフォーマンスを得るには、ワイルドカードを使用せず、エクスポートする関数がない場合は、エントリを削除しないで空の配列を使用してください。 FunctionsToExport = '*' # このモジュールからエクスポートするコマンドレットです。最適なパフォーマンスを得るには、ワイルドカードを使用せず、エクスポートするコマンドレットがない場合は、エントリを削除しないで空の配列を使用してください。 CmdletsToExport = '*' # このモジュールからエクスポートする変数 VariablesToExport = '*' # このモジュールからエクスポートするエイリアスです。最適なパフォーマンスを得るには、ワイルドカードを使用せず、エクスポートするエイリアスがない場合は、エントリを削除しないで空の配列を使用してください。 AliasesToExport = '*' }
これでモジュールをインポートすると指定したバージョンで読み込まれます。
> Import-Module .\Modules\Greeting > Get-Module Greeting ModuleType Version Name ---------- ------- ---- Script 1.0.0 Greeting
Import-Module
実行時にバージョン指定することで特定のバージョンのモジュールをインポートすることができます。まずバージョンごとにフォルダを分けます。
│ Main.ps1 │ └───Modules └───Greeting ├───1.0.0 │ Greeting.psd1 │ Greeting.psm1 │ └───2.0.0 Greeting.psd1 Greeting.psm1
psd1 ファイル内の ModuleVersion
をフォルダー名のバージョンと合わせておきます。RequiredVersion
で指定したバージョンを読み込むことができます。指定しない場合は最新バージョンが読み込まれます。
> Remove-Module Greeting > Import-Module .\Modules\Greeting -RequiredVersion 1.0.0 > Get-Module Greeting ModuleType Version Name ---------- ------- ---- Script 1.0.0 Greeting > Remove-Module Greeting > Import-Module .\Modules\Greeting -RequiredVersion 2.0.0 > Get-Module Greeting ModuleType Version Name ---------- ------- ---- Script 2.0.0 Greeting > Remove-Module Greeting > Import-Module .\Modules\Greeting > Get-Module Greeting ModuleType Version Name ---------- ------- ---- Script 2.0.0 Greeting
PowerShell スクリプトモジュールを試す
PowerShell でモジュールを作ったことがなかったので試してみました。PowerShell のモジュールとしては C# で書くバイナリモジュールとPowerShell で書くスクリプトモジュールがありますが、今回の対象はスクリプトモジュールです。
とりあえずこんな感じで Main.ps1
と Greeting.psm1
を配置しました。
ModuleSample │ Main.ps1 │ └───Modules └───Greeting Greeting.psm1
Greeting.psm1
がスクリプトモジュールの本体です。とりあえず 1 個関数を書いておきます。
function Get-Hello { Write-Output "Hello World!" }
呼出し側 Main.ps1
では Import-Module
でモジュールを読み込みます。
Import-Module .\Modules\Greeting Get-Hello
実行結果です。
> .\Main.ps1 Hello World!
Import-Module
の代わりに using module
を使ってモジュールを読み込むこともできます。
using module .\Modules\Greeting Get-Hello
フォルダーを指定してモジュールを読み込む場合はフォルダー名(Greeting)とファイル名(Greeting.psm1)が同じである必要があります。フォルダー名と異なる場合でもファイル名を直接指定して読み込むことはできます。
using module .\Modules\Greeting\Greeting.psm1 Get-Hello
Export-ModuleMember
を使うことで指定した関数だけをエクスポートすることができます。
function Get-Hello { Write-Output "Hello World!" } function Get-GoodMorning { Write-Output "Good Morning World!" } function Get-GoodAfternoon { Write-Output "Good Afternoon World!" } function Get-GoodEvening { Write-Output "Good Evening World!" } function Get-GoodNight { Write-Output "Good Night World!" } $functionsToExport = @( "Get-Hello" "Get-GoodMorning" "Get-GoodAfternoon" "Get-GoodEvening" ) Export-ModuleMember -Function $functionsToExport
Main.ps1
を以下のように書き換えておきます。
using module .\Modules\Greeting Get-Hello Get-GoodMorning Get-GoodAfternoon Get-GoodEvening Get-GoodNight
モジュールを読み込みなおすために Remove-Module
で読み込んだモジュールをいったん削除してから実行すると、エクスポートしなかった関数だけ実行に失敗します。
> Remove-Module Greeting > .\Main.ps1 Hello World! Good Morning World! Good Afternoon World! Good Evening World! Get-GoodNight : 用語 'Get-GoodNight' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前として認識されません。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認してから、再試行してください。
間違った挨拶をしないように、時間に応じた挨拶を返す Get-Greeting
を定義して他の関数は非公開にしてみました。
function Get-Hello { Write-Output "Hello World!" } function Get-GoodMorning { Write-Output "Good Morning World!" } function Get-GoodAfternoon { Write-Output "Good Afternoon World!" } function Get-GoodEvening { Write-Output "Good Evening World!" } function Get-GoodNight { Write-Output "Good Night World!" } function Get-Greeting { param ( [DateTime]$DateTime = (Get-Date) ) if ($DateTime.Hour -lt 5) { Get-GoodNight return } if ($DateTime.Hour -lt 12) { Get-GoodMorning return } if ($DateTime.Hour -lt 18) { Get-GoodAfternoon return } if ($DateTime.Hour -lt 24) { Get-GoodEvening return } } $functionsToExport = @( "Get-Hello" "Get-Greeting" ) Export-ModuleMember -Function $functionsToExport