MSOnline/AzureAD廃止後のMsolServicePrincipalスクリプト完全移行ガイド(Microsoft Graph PowerShell対応)

MSOnline / AzureAD モジュールの廃止により、「MsolServicePrincipal」系スクリプトがそのままでは動かなくなり、現場では移行対応が急務になっています。本記事では、サービス プリンシパルの資格情報一覧やクライアント シークレット発行を中心に、旧コマンドから Microsoft Graph PowerShell SDK への“実務レベルの置き換え方”を、エラー対処を含めて詳しく解説します。

目次

MSOnline / AzureAD 廃止で何が変わるのか

これまで Azure AD 管理の自動化といえば、MSOnline モジュールや AzureAD モジュールが定番でした。しかしこれらはすでに非推奨となっており、今後の更新・新機能は Microsoft Graph API / Microsoft Graph PowerShell SDK 側に集約されていきます。

特に影響が大きいのが、以下のような MsolServicePrincipal 系コマンドを使ったスクリプトです。

  • Connect-AzureAD / Connect-MsolService
  • Get-MsolServicePrincipal
  • Get-MsolServicePrincipalCredential
  • New-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-AzureADConnect-MgGraph-TenantId 指定で誤テナント接続を防止
Get-MsolServicePrincipalGet-MgServicePrincipalフィルタリングは PowerShell 側で実施すると柔軟
Get-MsolServicePrincipalCredentialPasswordCredentials / KeyCredentials プロパティ-Property / -ExpandProperty で展開
New-MsolServicePrincipalCredential(パスワード)Add-MgApplicationPassword対象は アプリ登録(Application) が基本
New-MsolServicePrincipalCredential(証明書)Add-MgApplicationKeyX.509 証明書などの追加に対応
削除系(Remove-MsolServicePrincipalCredential など)Remove-MgApplicationPassword / Remove-MgApplicationKeyKeyId を指定して削除

よくある混乱ポイントとして、Graph PowerShell には Add-MgServicePrincipalPassword / Add-MgServicePrincipalKey も存在します。しかし多くのベストプラクティスや公式ドキュメントでは、資格情報はアプリ登録(Application オブジェクト)側で管理する運用が推奨されています。

そのため本記事では、原則として以下のスタイルで統一します。

  • 「シークレット・証明書の追加」は アプリ登録(Application) に対して実施
  • サービス プリンシパル(ServicePrincipal)は「権限付与の単位」として扱う

Application と ServicePrincipal の違いを整理

404 や権限エラーでハマる大きな原因の一つが、Application と ServicePrincipal を混同してしまうことです。簡単に整理しておきます。

オブジェクト役割代表的な IDGraph 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 時代の典型的なスクリプトは、次のような流れだったと思います。

  1. Get-MsolServicePrincipal でサービス プリンシパル一覧を取得
  2. 表示名や SPN(ServicePrincipalNames)で不要なものを除外
  3. Get-MsolServicePrincipalCredential でシークレット・証明書を取得
  4. 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)に対して追加する」という点です。

つまり、

  1. クライアント ID(appId)から目的の Application を特定
  2. その Application の ObjectId(id) を取得
  3. 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 つを切り分けるためのチェックリストは以下の通りです。

  1. Get-MgContext で TenantId・Account を確認し、想定テナントかを確認
  2. Get-MgApplication -Filter "appId eq '<clientId>'" の結果を確認
    • $app が null → そのテナントに該当アプリが存在しない
    • 1 件以上返る → $app.Id が ObjectId なので、それを -ApplicationId に使用
  3. 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 ベースに移行する手順例をまとめます。

  1. 現状スクリプトの棚卸し
    • Get-MsolServicePrincipal / Get-MsolServicePrincipalCredential / New-MsolServicePrincipalCredential を使っている箇所を洗い出す
    • どのサービス プリンシパルに対して、どのような頻度・条件で資格情報を操作しているか整理する
  2. Graph 版コマンドへのマッピング
    • 一覧取得部分 → Get-MgServicePrincipal + PasswordCredentials / KeyCredentials
    • シークレット追加部分 → Add-MgApplicationPassword(Application の ObjectId を利用)
    • 削除部分 → Remove-MgApplicationPassword などに置き換え
  3. Graph 接続・権限の準備
    • 検証用テナントやテスト環境で Connect-MgGraph の動作確認
    • 必要スコープの付与と管理者同意の取得
  4. 一覧取得スクリプトの先行移行
    • 既存の棚卸しレポート生成処理を Graph 版に置き換える
    • MSOnline 版と Graph 版で出力を比較し、差異を確認
  5. シークレット発行処理の移行
    • 404 / 403 を潰しながら Add-MgApplicationPassword を安定稼働させる
    • Key Vault などへの安全な保管処理も合わせて実装
  6. ジョブ化・自動化とモニタリング
    • 期限 30 日前アラートなどをジョブ化し、実際の運用に組み込む
    • 想定外のエラー(例:特定テナントだけ 403 になるなど)に備えてログを残す
  7. 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 対応と運用の見直しを進めてみてください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次