AzureAD PowerShell から Microsoft Graph PowerShell へ移行すると、同じつもりで書いたワンライナーでも「常に False になる」「値が空になる」といった現象が起きがちです。特に PasswordNeverExpires(=PasswordPolicies に DisablePasswordExpiration が含まれるか)の判定はつまずきポイント。本稿では原因と対処、実運用に耐えるコマンド例、落とし穴、テスト方法までまとめて解説します。
背景と結論の先取り
AzureAD モジュールのコードを Microsoft Graph PowerShell に置き換えると、次のような疑問が生まれます。
# AzureAD(正しい結果が出る)
Get-AzureADUser -ObjectId <UPN> |
Select UserPrincipalName, @{N='PasswordNeverExpires';E={$_.PasswordPolicies -contains 'DisablePasswordExpiration'}}
# Graph(常に False…)
Get-MgUser -UserId |
Select UserPrincipalName, @{N='PasswordNeverExpires';E={$_.PasswordPolicies -contains 'DisablePasswordExpiration'}}
この「常に False」の主原因は Graph は既定(デフォルト)で必要なプロパティを返さない ことにあります。PasswordPolicies は既定のプロパティ セットに含まれないため、要求しなければ空($null)のまま評価され、結果的に -contains は常に $false になります。対策はシンプルで、-Property PasswordPolicies を明示するか、検証用途に限って beta プロファイルを使います。
なぜ Graph だと常に False になるのか
Microsoft Graph PowerShell の Get-MgUser は、REST の $select に相当する -Property を指定しない場合、返却項目が最小化されます。PasswordPolicies のような拡張的な文字列プロパティは既定で返らないため、PowerShell 上では $null と見なされます。ここに対して -contains 'DisablePasswordExpiration' を実行しても、配列演算ではないため当然 $false です。
| 観点 | AzureAD PowerShell | Microsoft Graph PowerShell(v1.0) | 要点 |
|---|---|---|---|
| 返却プロパティ | 比較的多い(レガシー) | 既定は最小限 | -Property で明示選択が必要 |
| 判定対象 | PasswordPolicies をそのまま評価 | PasswordPolicies を要求しないと $null | 要求しない=常に $false |
| Beta プロファイル | 対象外 | 既定で多くの項目が返る傾向 | 検証向け。運用は v1.0+-Property |
最小の対処:-Property を指定する
単一ユーザーの判定は次のワンライナーで解決します。
Get-MgUser -UserId <UPN> -Property PasswordPolicies |
Select UserPrincipalName,
@{N='PasswordNeverExpires';E={($_.PasswordPolicies -split '[,\s]+' ) -contains 'DisablePasswordExpiration'}}
-split を挟むのは実践的な防御策です。環境によって PasswordPolicies が "DisablePasswordExpiration" の単一文字列だったり、"DisablePasswordExpiration, DisableStrongPassword" のような複合表記だったりします。-split して配列化すれば、-contains でブレなく判定できます。
「値の中身だけ見たい」場合の最短コマンド
Get-MgUser -UserId <UPN> -Property PasswordPolicies |
Select -ExpandProperty PasswordPolicies
値の直観チェックに有効です。DisablePasswordExpiration が含まれていれば「期限切れなし」設定です。
準備(インストールと接続)
# 1) モジュールのインストール(管理者権限で一度だけ)
Install-Module Microsoft.Graph -Scope AllUsers
# 2) サインイン(必要なスコープを明示)
# 単一ユーザーの参照:User.Read(自分のみ)/User.Read.All(全ユーザー)
# 組織全体の一覧取得:Directory.Read.All の付与が実用的
Connect-MgGraph -Scopes "User.Read.All","Directory.Read.All"
# 3) エンドポイントの確認(既定は v1.0)
Select-MgProfile -Name "v1.0"
検証向けには Select-MgProfile beta も使えますが、本番スクリプトは v1.0 + -Property 指定で安定運用しましょう。
複数ユーザーを一括確認(All テナント)
Get-MgUser -All -Property UserPrincipalName,PasswordPolicies |
Select UserPrincipalName,
@{N='PasswordNeverExpires';E={($_.PasswordPolicies -split '[,\s]+' ) -contains 'DisablePasswordExpiration'}}
-Property には必要最小限の項目だけを列挙すると API 呼び出し回数・転送量を節約できます。
CSV にエクスポートして監査証跡にする
$result = Get-MgUser -All -Property UserPrincipalName,PasswordPolicies |
Select UserPrincipalName,
@{N='PasswordNeverExpires';E={($_.PasswordPolicies -split '[,\s]+' ) -contains 'DisablePasswordExpiration'}}
$result | Export-Csv -NoTypeInformation -Encoding UTF8 `
-Path (Join-Path $PWD "PasswordNeverExpires-audit.csv")
対象ユーザーを CSV で与えてピンポイント確認
# users.csv はヘッダーに UPN 列を持つとする
# UPN
# [email protected]
# [email protected]
Import-Csv .\users.csv | ForEach-Object {
$u = Get-MgUser -UserId $_.UPN -Property UserPrincipalName,PasswordPolicies
[pscustomobject]@{
UserPrincipalName = $u.UserPrincipalName
PasswordNeverExpires = (($u.PasswordPolicies -split '[,\s]+') -contains 'DisablePasswordExpiration')
}
} | Export-Csv -NoTypeInformation -Encoding UTF8 .\PasswordNeverExpires-selected.csv
beta プロファイルを使う場合の例(検証用途)
Select-MgProfile beta
# -Property を省略しても多くのプロパティが返ることがある
Get-MgUser -UserId |
Select UserPrincipalName,
@{N='PasswordNeverExpires';E={($_.PasswordPolicies -split '[,\s]+' ) -contains 'DisablePasswordExpiration'}}
注意: beta は予告なくスキーマや挙動が変わる可能性があります。運用スクリプトでは v1.0 を前提に -Property を指定してください。
AzureAD から Graph への読み替え要点
| 意図 | AzureAD PowerShell | Microsoft Graph PowerShell | 備考 |
|---|---|---|---|
| ログイン | Connect-AzureAD | Connect-MgGraph -Scopes ... | 必要スコープを明示 |
| ユーザー取得 | Get-AzureADUser -ObjectId | Get-MgUser -UserId | 基本は等価 |
| 期限切れなしの判定 | $_.PasswordPolicies -contains 'DisablePasswordExpiration' | ($_.PasswordPolicies -split '[,\s]+') -contains 'DisablePasswordExpiration' | Graph は配列化してから判定が安全 |
| プロパティの明示 | 原則不要 | -Property PasswordPolicies | これが最大のハマりどころ |
実務で役立つ関数化(再利用とテスト容易性)
function Test-PasswordNeverExpires {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$UserId
)
$u = Get-MgUser -UserId $UserId -Property UserPrincipalName,PasswordPolicies -ErrorAction Stop
$flags = ($u.PasswordPolicies -split '[,\s]+')
[pscustomobject]@{
UserPrincipalName = $u.UserPrincipalName
PasswordNeverExpires = $flags -contains 'DisablePasswordExpiration'
RawPasswordPolicies = $u.PasswordPolicies
}
}
ユニットテストや CI でのチェックも容易になります。
一括是正の準備(変更は慎重に)
本稿は「確認方法」の解説ですが、運用では「誤って期限切れなしになっているアカウント」を迅速にリストアップし、ガバナンスに則って是正する必要があります。是正フローの例を示します(実際の更新コマンドは組織ルールに従って判断してください)。
- 一覧取得:
Get-MgUser -All -Property UserPrincipalName,PasswordPolicies - 判定:
DisablePasswordExpirationを含むユーザーのみ抽出 - レビュー:サービスアカウントや緊急時アカウントなど、正当な例外をホワイトリスト化
- 是正:パスワード有効期限ポリシーの見直し/クラウド運用ルールへ統一
出力の目視確認例
UserPrincipalName PasswordNeverExpires
----------------- --------------------
[email protected] True
[email protected] False
[email protected] True
よくある質問(FAQ)
判定がすべて False になる/値が空に見える
- 原因:
-Property PasswordPoliciesを指定していない。 - 対処: 必ず
-Property引数にPasswordPoliciesを含める。
例:Get-MgUser -UserId <UPN> -Property UserPrincipalName,PasswordPolicies
-contains と -match のどちらを使うべき?
- 値が単一文字列かつ完全一致のみを見たいなら
-eq。 - 複合表記(カンマ区切りやスペース区切り)に備えるなら
-split→ 配列化 →-containsが堅実。 - 正規表現で柔軟に判定したい場合は
-match(例:$_.PasswordPolicies -match '(^|,)\s*DisablePasswordExpiration(\s*,|$)')。
ゲスト(B2B)やフェデレーション ユーザーは?
- 来訪者(ゲスト)アカウントは
PasswordPoliciesが空の場合があります。空=False と決め打ちせず、アカウント種別も併せて判断してください。 - ハイブリッド(オンプレ同期)ユーザーでは、パスワード期限の実態はオンプレ側で管理されます。クラウド側の
PasswordPoliciesは運用方針により未設定のことがあります。
一覧取得が遅い/タイムアウトする
-Propertyで返却項目を絞る(UserPrincipalName,PasswordPoliciesのみ)。-Allに頼らず、-Filter(例:startswith(displayName,'Svc'))で対象をサーバー側フィルター。- 定期バッチは差分(新規・更新のみ)に絞る設計にする。
セキュリティ運用のヒント
- 例外は最小化: サービスアカウントや非常用アカウント等、やむを得ないケースに限定し、根拠を台帳で管理。
- 多要素認証(MFA)と併用: 期限切れなし設定は MFA で最低限のリスク低減を。
- ロール分離: 判定スクリプトの実行者と設定変更者のロールを分け、四眼原則を適用。
完全版スクリプト(監査+レポート)
# 監査レポートを作る包括スクリプトの例
# 実行前に Connect-MgGraph -Scopes "User.Read.All","Directory.Read.All" を済ませること
$now = Get-Date -Format "yyyyMMdd-HHmmss"
$out = Join-Path $PWD ("PasswordNeverExpires-audit-{0}.csv" -f $now)
$users = Get-MgUser -All -Property Id,UserPrincipalName,DisplayName,UserType,PasswordPolicies
$report = foreach ($u in $users) {
$flags = ($u.PasswordPolicies -split '[,\s]+' | Where-Object { $_ })
[pscustomobject]@{
UPN = $u.UserPrincipalName
DisplayName = $u.DisplayName
UserType = $u.UserType
PasswordPoliciesRaw = $u.PasswordPolicies
PasswordNeverExpires = $flags -contains 'DisablePasswordExpiration'
HasDisableStrongPwd = $flags -contains 'DisableStrongPassword'
}
}
$report | Sort-Object UserType,UPN | Export-Csv -NoTypeInformation -Encoding UTF8 $out
Write-Host "Exported: $out"
トラブルシューティング・チェックリスト
- 接続は v1.0 プロファイルか(
Select-MgProfile -Name v1.0)。 - 必要スコープを付けているか(
User.Read.AllまたはDirectory.Read.All)。 -Property PasswordPoliciesを指定しているか。- 判定の前に
-splitで配列化しているか。 - 対象がゲスト/ハイブリッド等、例外的なユーザー種別でないか。
まとめ
- 原因: Graph は既定で
PasswordPoliciesを返さないため、値が空になり判定が常にFalseになる。 - 解決:
Get-MgUser -Property PasswordPoliciesを明示し、判定時は-split+-containsを用いる。検証ではSelect-MgProfile betaも可。 - 運用: 本番は v1.0 +
-Property指定で安定化。-Allと最小プロパティ選択、CSV 出力で監査証跡を整備。 - 移行ポイント: AzureAD 時代のワンライナーは「ログイン方法」と「プロパティ取得」の 2 点を主に書き換えれば置き換え可能。
付録:表示に使えるカラム例
| 列名 | 説明 | サンプル |
|---|---|---|
| UserPrincipalName | 一意のサインイン ID | [email protected] |
| DisplayName | 表示名 | Alice Tanaka |
| UserType | Member / Guest など | Member |
| PasswordPoliciesRaw | 返却された生の文字列 | DisablePasswordExpiration, DisableStrongPassword |
| PasswordNeverExpires | DisablePasswordExpiration を含むかの真偽 | True / False |
もう一歩踏み込む:応答最小化と堅牢化
- プロパティ最小化:
-Property UserPrincipalName,PasswordPoliciesとして不要な列を省く。 - エラーハンドリング:
-ErrorAction Stopとtry/catchで権限や存在しない UPN を検知。 - ロギング:
Start-Transcriptや独自ログで実行者・日時・対象数を記録。
ここまでの要点を 1 行に凝縮
Get-MgUser -UserId <UPN> -Property PasswordPolicies |
Select UserPrincipalName,
@{N='PasswordNeverExpires';E={($_.PasswordPolicies -split '[,\s]+') -contains 'DisablePasswordExpiration'}}
この 1 行をベースに、組織の監査や定期チェックへ発展させてください。

コメント