MSOnline / AzureAD モジュールの廃止により、「MsolServicePrincipal」系スクリプトがそのままでは動かなくなり、現場では移行対応が急務になっています。本記事では、サービス プリンシパルの資格情報一覧やクライアント シークレット発行を中心に、旧コマンドから Microsoft Graph PowerShell SDK への“実務レベルの置き換え方”を、エラー対処を含めて詳しく解説します。
MSOnline / AzureAD 廃止で何が変わるのか
これまで Azure AD 管理の自動化といえば、MSOnline モジュールや AzureAD モジュールが定番でした。しかしこれらはすでに非推奨となっており、今後の更新・新機能は Microsoft Graph API / Microsoft Graph PowerShell SDK 側に集約されていきます。
特に影響が大きいのが、以下のような MsolServicePrincipal 系コマンドを使ったスクリプトです。
Connect-AzureAD/Connect-MsolServiceGet-MsolServicePrincipalGet-MsolServicePrincipalCredentialNew-MsolServicePrincipalCredential
これらは Microsoft Graph PowerShell ではそのまま利用できないため、等価な処理に書き換える必要があります。本記事ではとくに次のニーズにフォーカスします。
- サービス プリンシパルの資格情報(シークレット/証明書)を一覧出力したい
- 有効期限を見ながら、必要に応じて新しいクライアント シークレットを発行したい
Add-MgApplicationPassword実行時に発生する 404 (NotFound) を解消したい
単なるコマンド対応表だけではなく、「なぜ 404 になるのか」「何を ID に渡すべきか」といった実務でつまずきやすいポイントまで掘り下げて解説します。
MsolServicePrincipal 系コマンドの読み替え方
旧コマンドと Graph PowerShell の対応表
まずは旧モジュールの代表的なコマンドと、Microsoft Graph PowerShell SDK における対応関係を整理しておきます。
| 旧 MSOnline / AzureAD | 代替(Microsoft Graph PowerShell) | 補足 |
|---|---|---|
Connect-MsolService / Connect-AzureAD | Connect-MgGraph | -TenantId 指定で誤テナント接続を防止 |
Get-MsolServicePrincipal | Get-MgServicePrincipal | フィルタリングは PowerShell 側で実施すると柔軟 |
Get-MsolServicePrincipalCredential | PasswordCredentials / KeyCredentials プロパティ | -Property / -ExpandProperty で展開 |
New-MsolServicePrincipalCredential(パスワード) | Add-MgApplicationPassword | 対象は アプリ登録(Application) が基本 |
New-MsolServicePrincipalCredential(証明書) | Add-MgApplicationKey | X.509 証明書などの追加に対応 |
削除系(Remove-MsolServicePrincipalCredential など) | Remove-MgApplicationPassword / Remove-MgApplicationKey | KeyId を指定して削除 |
よくある混乱ポイントとして、Graph PowerShell には Add-MgServicePrincipalPassword / Add-MgServicePrincipalKey も存在します。しかし多くのベストプラクティスや公式ドキュメントでは、資格情報はアプリ登録(Application オブジェクト)側で管理する運用が推奨されています。
そのため本記事では、原則として以下のスタイルで統一します。
- 「シークレット・証明書の追加」は アプリ登録(Application) に対して実施
- サービス プリンシパル(ServicePrincipal)は「権限付与の単位」として扱う
Application と ServicePrincipal の違いを整理
404 や権限エラーでハマる大きな原因の一つが、Application と ServicePrincipal を混同してしまうことです。簡単に整理しておきます。
| オブジェクト | 役割 | 代表的な ID | Graph PowerShell での取得 |
|---|---|---|---|
| Application(アプリ登録) | アプリの設計図。クライアント ID やリダイレクト URL などを定義 | appId(クライアント ID) id(ObjectId) | Get-MgApplication |
| ServicePrincipal(サービス プリンシパル) | 特定テナントに展開されたインスタンス。実際のアクセス権を持つ主体 | appId(元 Application のクライアント ID) id(ServicePrincipal の ObjectId) | Get-MgServicePrincipal |
つまり、同じアプリでも、
- クライアント ID(appId)…すべてのテナントで共通
- ObjectId(id)…テナントごとに異なる
という構造になっています。Add-MgApplicationPassword は「Application の ObjectId(id)」を引数に取るのがポイントです。ここをクライアント ID(appId)のつもりで渡してしまうと、404(NotFound)の原因になります。
Microsoft Graph PowerShell SDK のセットアップと接続
モジュールのインストールと読み込み
まずは Microsoft Graph PowerShell SDK をインストールし、モジュールを読み込みます。
# 初回のみ(未導入なら)
Install-Module Microsoft.Graph -Scope CurrentUser
# モジュール読み込み
Import-Module Microsoft.Graph
環境によっては Microsoft.Graph のサブモジュール(Microsoft.Graph.Applications など)を個別にインストールするパターンもありますが、基本的には上記でまとめて導入しておけば問題ありません。
テナントを明示した接続
もっとも重要なのは、「正しいテナントに接続しているか」を常に意識することです。旧モジュールでもありがちでしたが、Graph PowerShell でも誤テナント接続はそのまま 404 の原因になります。
代表的な接続方法は次のとおりです(委任権限での対話接続)。
# 必要なスコープを指定して接続
Connect-MgGraph -Scopes "Application.Read.All","Application.ReadWrite.All"
# テナントを固定したい場合
Connect-MgGraph -TenantId "<your-tenant-id>" `
-Scopes "Application.Read.All","Application.ReadWrite.All"
接続後は必ずコンテキストを確認し、テナントやスコープをチェックしておきましょう。
Get-MgContext | Format-List TenantId, Account, Scopes
ここで TenantId が意図したものと違っていれば、その時点で誤接続が判明します。トラブルシュートの観点では、1 行追加するだけで大きな差が出るチェックです。
自動化用(アプリ権限)接続のイメージ
ジョブ化・タスクスケジューラ・Azure Automation などで非対話実行したい場合は、アプリ権限での接続も検討します。
Connect-MgGraph -ClientId "<appId-of-automation-app>" `
-TenantId "<tenant-id>" `
-CertificateThumbprint "<cert-thumbprint>"
この場合、Azure ポータル側で「アプリ登録」に対して Application.Read.All / Application.ReadWrite.All などのアプリケーション許可を与え、管理者同意を済ませておく必要があります。
サービス プリンシパル資格情報の一覧取得
旧スクリプトの典型パターン
MSOnline 時代の典型的なスクリプトは、次のような流れだったと思います。
Get-MsolServicePrincipalでサービス プリンシパル一覧を取得- 表示名や SPN(ServicePrincipalNames)で不要なものを除外
Get-MsolServicePrincipalCredentialでシークレット・証明書を取得- CSV などに書き出し、有効期限の監視や棚卸に利用
これを Graph PowerShell で実現するには、
Get-MgServicePrincipalでサービス プリンシパルを取得- 各 SP の
PasswordCredentials/KeyCredentialsを展開
という構成になります。
Graph PowerShell での一覧取得サンプル
以下のスクリプトは、
- 表示名に
"*Microsoft*"を含むもの - 表示名が
"autohost*"で始まるもの - ServicePrincipalNames に
"*localhost*"を含むもの
を除外しつつ、PasswordCredentials(シークレット)と KeyCredentials(証明書)を テキストファイル形式で出力する例です。
$path = "C:\temp\sp-credentials.txt"
# 出力ディレクトリが無ければ作成
if (-not (Test-Path (Split-Path $path))) {
New-Item -ItemType Directory -Path (Split-Path $path) | Out-Null
}
# 既存ファイルを削除
Remove-Item $path -ErrorAction SilentlyContinue
# サービス プリンシパルを取得してフィルタリング
$spList = Get-MgServicePrincipal -All | Where-Object {
$_.DisplayName -notlike "*Microsoft*" -and
$_.DisplayName -notlike "autohost*" -and
($_.ServicePrincipalNames -notlike "*localhost*")
}
foreach ($sp in $spList) {
# 必要なプロパティを明示して再取得
$spFull = Get-MgServicePrincipal -ServicePrincipalId $sp.Id `
-Property "Id,AppId,DisplayName,PasswordCredentials,KeyCredentials" `
-ExpandProperty "PasswordCredentials,KeyCredentials"
# シークレット(Password)を出力
foreach ($cred in $spFull.PasswordCredentials) {
"$($spFull.DisplayName);$($spFull.AppId);Password;$($cred.KeyId);$($cred.StartDateTime);$($cred.EndDateTime)" `
| Out-File -FilePath $path -Append -Encoding UTF8
}
# 証明書(Key)を出力
foreach ($key in $spFull.KeyCredentials) {
"$($spFull.DisplayName);$($spFull.AppId);Key;$($key.KeyId);$($key.StartDateTime);$($key.EndDateTime)" `
| Out-File -FilePath $path -Append -Encoding UTF8
}
}
1 行あたりが 「サービス プリンシパル名; appId; 種別; KeyId; 開始日時; 終了日時」 という構造になります。Excel に取り込む場合は、区切り文字を ; としてインポートすればよいでしょう。
| 項目 | 内容 | 利用例 |
|---|---|---|
| DisplayName | サービス プリンシパルの表示名 | どのアプリなのか人間が識別するため |
| AppId | クライアント ID(appId) | ポータル上の「アプリケーション(クライアント)ID」と照合 |
| 種別 | Password / Key | シークレットか証明書かを区別 |
| KeyId | 資格情報ごとの一意な ID | 削除・更新時の指定に利用 |
| StartDateTime | 有効開始日時 | 棚卸し時の参考(古いかどうか) |
| EndDateTime | 有効期限 | 期限切れ・期限間近の判定 |
CSV 出力や期限 30 日前の絞り込み例
より実務に近づけるなら、最初から CSV 形式で出力しつつ、「期限まで 30 日以内」のものだけを抽出するのもよくあるパターンです。
$limitDays = 30
$csvPath = "C:\temp\sp-credentials-expiring.csv"
$now = Get-Date
$results = @()
foreach ($sp in $spList) {
$spFull = Get-MgServicePrincipal -ServicePrincipalId $sp.Id `
-Property "Id,AppId,DisplayName,PasswordCredentials,KeyCredentials" `
-ExpandProperty "PasswordCredentials,KeyCredentials"
foreach ($cred in $spFull.PasswordCredentials) {
if ($cred.EndDateTime -and ($cred.EndDateTime - $now).TotalDays -le $limitDays) {
$results += [pscustomobject]@{
DisplayName = $spFull.DisplayName
AppId = $spFull.AppId
Type = "Password"
KeyId = $cred.KeyId
Start = $cred.StartDateTime
End = $cred.EndDateTime
DaysLeft = [math]::Floor(($cred.EndDateTime - $now).TotalDays)
}
}
}
foreach ($key in $spFull.KeyCredentials) {
if ($key.EndDateTime -and ($key.EndDateTime - $now).TotalDays -le $limitDays) {
$results += [pscustomobject]@{
DisplayName = $spFull.DisplayName
AppId = $spFull.AppId
Type = "Key"
KeyId = $key.KeyId
Start = $key.StartDateTime
End = $key.EndDateTime
DaysLeft = [math]::Floor(($key.EndDateTime - $now).TotalDays)
}
}
}
}
$results | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
あとはこの CSV を監視基盤やメール通知スクリプトと連携させれば、「期限 30 日前の資格情報だけアラート」といった運用に発展させることができます。
クライアント シークレットの新規発行(推奨パターン)
なぜ Add-MgApplicationPassword なのか
旧 New-MsolServicePrincipalCredential でパスワードを追加していた処理は、Graph では主に Add-MgApplicationPassword に相当します。繰り返しになりますが、ここで意識すべきなのは「アプリ登録(Application)に対して追加する」という点です。
つまり、
- クライアント ID(appId)から目的の Application を特定
- その Application の ObjectId(id) を取得
Add-MgApplicationPassword -ApplicationId <ObjectId>を実行
という 3 ステップを踏むことになります。
クライアント ID から ObjectId を取得してシークレット発行
以下は、クライアント ID から ObjectId を取得し、1 年有効のシークレットを新規発行するサンプルです。
# 1) クライアント ID(appId)を指定
$clientId = "12345asd-avf5-hy78-asd5-9a71fa0a4024" # Azure ポータルに表示される「アプリケーション(クライアント)ID」
# 2) appId からアプリ登録(Application)を検索
$app = Get-MgApplication -Filter "appId eq '$clientId'"
if (-not $app) {
throw "指定の appId のアプリ登録が見つかりません。接続テナントや ID を確認してください。"
}
# 3) シークレットのメタ情報を定義
$pc = @{
DisplayName = "Auto-generated secret"
StartDateTime = (Get-Date).ToString("o")
EndDateTime = (Get-Date).AddYears(1).ToString("o")
}
# 4) Application の ObjectId($app.Id)を指定してシークレット追加
$result = Add-MgApplicationPassword -ApplicationId $app.Id -PasswordCredential $pc
# 5) 生成されたシークレットの「値」は SecretText にのみ一度だけ返ってくる
$secretValue = $result.SecretText
$secretValue
重要なポイントは次の 3 つです。
-ApplicationIdには$app.Id(ObjectId)を渡すこと- シークレットの値(SecretText)は この時しか取得できないこと
- 値を取得したらすぐに Key Vault やパスワードマネージャーに安全に保管すること
特に 2 点目は見落とされがちなポイントです。スクリプトを再実行しても、同じ SecretText を再取得することはできません。運用上は「発行直後に安全な保管先へ登録する」までを 1 つのプロセスとして設計しておくと安心です。
任意の値を指定したい場合の注意点
旧モジュールでは、自分で生成したランダム文字列をそのままパスワードとして登録することができました。Graph でもバージョンやポリシーによっては同様のことが可能ですが、テナントのセキュリティ ポリシーにより拒否される場合があります。
近年は「パスワードはシステム側で十分な長さ・複雑さを持つ値を自動生成し、人間が覚えなくてよい世界にする」という方向性にシフトしているため、基本的には Graph 側で安全な値を生成させる方式を採用するのが無難です。
404(NotFound)/権限エラーの切り分け
Add-MgApplicationPassword で 404 が出る典型パターン
Add-MgApplicationPassword 実行時の 404(NotFound)は、多くの場合パラメータやテナントが期待とずれていることが原因です。典型例をまとめると次のとおりです。
- 原因 1:
-ApplicationIdにクライアント ID(appId)を渡している - 原因 2: 別テナントに接続している(同じ appId のアプリが存在しない)
- 原因 3: そもそも対象のアプリ登録が削除済み
この 3 つを切り分けるためのチェックリストは以下の通りです。
Get-MgContextで TenantId・Account を確認し、想定テナントかを確認Get-MgApplication -Filter "appId eq '<clientId>'"の結果を確認$appが null → そのテナントに該当アプリが存在しない- 1 件以上返る →
$app.Idが ObjectId なので、それを-ApplicationIdに使用
- Azure ポータルの「アプリ登録」画面からも同じ appId のアプリが存在するか確認
Graph のエラー メッセージは一見わかりづらいですが、上記のように「テナント」「ObjectId」「appId」の 3 つを切り分けて確認していくと原因にたどり着きやすくなります。
403 / Authorization エラー時の確認ポイント
404 と並んでよく遭遇するのが、権限不足による 403 エラーです。特に次のようなメッセージが出る場合があります。
- Insufficient privileges to complete the operation
- Authorization_RequestDenied
この場合のチェックポイントは以下です。
- 委任権限接続の場合:
Connect-MgGraphの-Scopesに Application.Read.All / Application.ReadWrite.All を含めているか- 初回接続時に表示される「同意」画面で、要求された権限を承諾したか
- 実行アカウントがテナント管理者でない場合、管理者に「管理者の同意」を依頼したか
- アプリ権限接続の場合:
- Azure ポータルの「API のアクセス許可」で、上記スコープをアプリケーション許可として付与しているか
- 「管理者の同意が必要」のラベルが付いているものは、管理者が同意ボタンを押したか
- 接続に使用しているアプリ登録(ClientId)が正しいものか
403 の場合は「ID の指定は正しいのに権限が足りない」状態なので、404 の切り分けと合わせて、「404 なら ID・テナント」「403 なら権限」と覚えておくと整理しやすくなります。
証明書認証(Add-MgApplicationKey)の活用
セキュリティの観点では、クライアント シークレットよりも 証明書認証(Add-MgApplicationKey) を利用することが推奨されるケースが増えています。証明書認証のメリットとしては、
- 秘密鍵ファイルを安全に管理する前提であれば、盗聴に強い
- シークレットよりも長い有効期限を設定しやすい(とはいえ適切なローテーションは必須)
- HSM / Key Vault と組み合わせた高度な運用が可能
Graph で証明書を登録する際は、Add-MgApplicationKey を利用します。詳細な構文は割愛しますが、基本的には「証明書のバイナリ」「タイプ」「用途(用途に応じた usage / keyType)」などを指定する形になります。
既存スクリプトが「シークレット前提」になっている場合でも、移行を機に「証明書優先で、やむを得ないケースのみシークレット」というポリシーに見直すと、長期運用の安定性・安全性が高まります。
旧スクリプトからの移行ステップ例
ここまでの内容を踏まえて、実際に MsolServicePrincipal 系スクリプトを Graph ベースに移行する手順例をまとめます。
- 現状スクリプトの棚卸し
Get-MsolServicePrincipal/Get-MsolServicePrincipalCredential/New-MsolServicePrincipalCredentialを使っている箇所を洗い出す- どのサービス プリンシパルに対して、どのような頻度・条件で資格情報を操作しているか整理する
- Graph 版コマンドへのマッピング
- 一覧取得部分 →
Get-MgServicePrincipal+PasswordCredentials/KeyCredentials - シークレット追加部分 →
Add-MgApplicationPassword(Application の ObjectId を利用) - 削除部分 →
Remove-MgApplicationPasswordなどに置き換え
- 一覧取得部分 →
- Graph 接続・権限の準備
- 検証用テナントやテスト環境で
Connect-MgGraphの動作確認 - 必要スコープの付与と管理者同意の取得
- 検証用テナントやテスト環境で
- 一覧取得スクリプトの先行移行
- 既存の棚卸しレポート生成処理を Graph 版に置き換える
- MSOnline 版と Graph 版で出力を比較し、差異を確認
- シークレット発行処理の移行
- 404 / 403 を潰しながら
Add-MgApplicationPasswordを安定稼働させる - Key Vault などへの安全な保管処理も合わせて実装
- 404 / 403 を潰しながら
- ジョブ化・自動化とモニタリング
- 期限 30 日前アラートなどをジョブ化し、実際の運用に組み込む
- 想定外のエラー(例:特定テナントだけ 403 になるなど)に備えてログを残す
- MSOnline / AzureAD スクリプトの閉塞
- Graph 版の安定稼働を確認したら、旧モジュール依存のスクリプトを順次停止・削除
- 運用マニュアルや手順書も Graph 前提の内容に更新する
ポイントは、「いきなり全部を Graph に変えるのではなく、一覧取得 → シークレット発行 → 自動化の順で少しずつ置き換える」ことです。特に資格情報周りは障害が出ると影響範囲が大きいため、段階的な移行が安心です。
実務で意識したいベストプラクティス
最後に、本記事で扱った内容を踏まえて、資格情報管理と Graph 移行におけるベストプラクティスを整理します。
- 証明書認証を第一候補にする
- 新規構築・再設計が可能なシナリオでは、可能な限り
Add-MgApplicationKeyによる証明書認証を採用 - シークレットは「どうしても証明書が使えない場合の代替手段」という位置づけにする
- 新規構築・再設計が可能なシナリオでは、可能な限り
- シークレットは短寿命+自動ローテーション前提
- 人間が手で管理する前提を捨て、1 年や 6 か月など短めの期限で設計
- 期限 30 日前を起点とした自動通知・ローテーションジョブを組む
- 値は即座に安全なストアへ保管
Add-MgApplicationPasswordの戻り値(SecretText)は一度しか取得できない- Azure Key Vault やパスワードマネージャー、Secret Manager などに即時登録するプロセスを用意する
- テナントと ID の取り違えを防ぐ
- スクリプト先頭で
Get-MgContextを必ず出力して、ログに残す appId(クライアント ID)とid(ObjectId)の両方を正しく理解し、コメントに明記する
- スクリプト先頭で
- エラーハンドリングとメッセージの整備
- 404 / 403 など代表的なエラーコードごとに「想定される原因」と「確認手順」をメッセージ化
- 運用担当者がログだけで原因を推測できるようにしておく
- MSOnline / AzureAD に依存しない新規スクリプト設計
- 新規でスクリプトを書く場合は、最初から Microsoft Graph PowerShell SDK を前提にする
- 将来の API 変更に備えて、クエリやフィルタ条件はなるべく分かりやすく記述する
MSOnline / AzureAD モジュールの廃止は、一見すると負担に感じられるかもしれません。しかし、これを機に資格情報管理の棚卸しや自動化を進めることで、結果的には安全でメンテナンスしやすい環境に近づけるチャンスでもあります。本記事で紹介した手順やサンプルをベースに、ぜひ自社スクリプトの Graph 対応と運用の見直しを進めてみてください。

コメント