はじめに
Entra ID を利用している環境において、定期的なアカウント棚卸しで未利用者を洗い出した後、それらのアカウントを無効化したい場面があります。
よくある利用シーン:
- 退職予定者のアカウント停止
- 長期休暇者の一時的な無効化
- セキュリティポリシーに基づく未利用アカウントの整理
Microsoft 365管理センターのGUIでは、ユーザーの一括無効化機能が提供されていません。本記事では、PowerShellを使った安全で確実な一括無効化の方法を解説します。
関連記事: アカウント棚卸しの事前準備として、Entra IDの最終サインイン日時を一括で取得する方法もご参照ください。
無効化と削除の違い(重要)
スクリプトを実行する前に、無効化と削除の違いを理解しておくことが重要です。
無効化 (Disable)
- サインイン不可になるが、ユーザーオブジェクトは残ります
- メールボックス、OneDrive、ライセンスはそのまま保持されます
- いつでも再有効化が可能です
- 用途: 退職予定者、長期休暇者の一時的な停止
削除 (Delete)
- 論理削除され、30日間は復元可能です
- ライセンスは即座に解放されます
- 30日後に完全削除されます(データも消失)
- 用途: 完全な退職、アカウント整理
どちらを選ぶべきか
まず無効化 → 問題なければ削除、という段階的なアプローチが安全な運用です。
本記事では無効化の方法を解説します。削除については後述の補足をご確認ください。
この記事で実現できること
- CSVからユーザーを読み込んで一括無効化
- 無効化と同時にセッションを強制切断
- 処理結果をTSVログで記録
- エラーハンドリングと進捗表示
前提条件
- Microsoft.Graph PowerShellモジュール(v2.x以降)
- グローバル管理者またはユーザー管理者権限
モジュールの確認とインストール
# バージョン確認
Get-Module Microsoft.Graph -ListAvailable
# インストール(必要な場合)
Install-Module Microsoft.Graph -Scope CurrentUser
# v1.xからのアップデート
Update-Module Microsoft.Graph
実行前の確認事項
影響範囲の理解
- 無効化されたユーザーは即座にサインイン不可になります
- 共有メールボックスの代理アクセス権限なども失効します
- Microsoft Teams、SharePointでの活動も利用できなくなります
テスト実行の推奨
本番環境で実行する前に、以下を推奨します:
- テストユーザー1名でスクリプトを試す
-
$DoSignout = $falseで動作確認する - ログファイルの出力形式を確認する
準備:CSVファイルの作成
以下の形式でCSVファイルを作成します。
UserPrincipalName
user1@contoso.com
user2@contoso.com
重要:
- ヘッダー行は必ず
UserPrincipalNameとしてください - 文字コードはUTF-8を推奨します
- Excelで作成する場合、「CSV UTF-8 (コンマ区切り) (*.csv)」形式で保存してください
スクリプト全文
# 0) Graph に接続
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
Connect-MgGraph -Scopes "User.ReadWrite.All"
# 1) セッション無効化関数
function Revoke-Sessions {
param(
[Parameter(Mandatory=$true)][string]$UserId,
[Parameter(Mandatory=$true)][bool]$DoSignout
)
if (-not $DoSignout) { return }
try {
# Microsoft.Graph 2.x 以降の標準コマンド
if (Get-Command Revoke-MgUserSignInSession -ErrorAction SilentlyContinue) {
Revoke-MgUserSignInSession -UserId $UserId -ErrorAction Stop | Out-Null
Write-Host " → セッション無効化完了" -ForegroundColor Gray
} else {
Write-Host " → セッション無効化コマンドが見つかりません(スキップ)" -ForegroundColor Yellow
}
} catch {
Write-Host " → セッション無効化失敗: $($_.Exception.Message)" -ForegroundColor Yellow
}
}
# 2) 設定
$CsvPath = ".\disable_users.csv"
$DoSignout = $true
# 3) ログ準備
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$logPath = ".\disable_users_result_$timestamp.tsv"
"UserPrincipalName`tResult`tMessage" | Out-File -FilePath $logPath -Encoding UTF8
# 4) CSV存在確認・読込
if (-not (Test-Path $CsvPath)) {
Write-Host "CSV が見つかりません: $CsvPath" -ForegroundColor Red
exit 1
}
$users = Import-Csv -Path $CsvPath
if ($users.Count -eq 0) {
Write-Host "CSV にデータがありません" -ForegroundColor Red
exit 1
}
if (-not ($users[0].PSObject.Properties.Name -contains "UserPrincipalName")) {
Write-Host "CSV に 'UserPrincipalName' 列が見つかりません" -ForegroundColor Red
exit 1
}
# 5) 確認プロンプト
Write-Host "`n【警告】$($users.Count) 件のユーザーを無効化します" -ForegroundColor Yellow
$confirm = Read-Host "続行しますか? (y/n)"
if ($confirm -notmatch '^y(es)?$') {
Write-Host "キャンセルしました" -ForegroundColor Cyan
exit 0
}
# 6) 実行ループ
$total = $users.Count
$current = 0
$successCount = 0
$failCount = 0
foreach ($u in $users) {
$current++
Write-Progress -Activity "ユーザー無効化処理" -Status "$current / $total" -PercentComplete (($current/$total)*100)
$upn = $u.UserPrincipalName.Trim()
if ([string]::IsNullOrWhiteSpace($upn)) {
Write-Host "[$current/$total] スキップ:空の UPN 行" -ForegroundColor Yellow
"$upn`tSkip`tBlank UPN" | Out-File -FilePath $logPath -Append -Encoding UTF8
continue
}
try {
# ユーザー取得
$userObj = Get-MgUser -UserId $upn -ErrorAction Stop
# ゲストユーザーの警告表示(オプション)
if ($userObj.UserType -eq "Guest") {
Write-Host " ⚠ Warning: $upn はゲストユーザーです" -ForegroundColor Yellow
}
# 無効化
Update-MgUser -UserId $userObj.Id -AccountEnabled:$false -ErrorAction Stop
# セッション無効化
Revoke-Sessions -UserId $userObj.Id -DoSignout $DoSignout
Write-Host "[$current/$total] ✓ Disabled: $upn" -ForegroundColor Green
"$upn`tSuccess`tDisabled" | Out-File -FilePath $logPath -Append -Encoding UTF8
$successCount++
} catch {
$msg = ($_.Exception.Message -replace "`t"," " -replace "`r`n"," ").Substring(0, [Math]::Min(200, $_.Exception.Message.Length))
Write-Host "[$current/$total] ✗ Failed: $upn - $msg" -ForegroundColor Red
"$upn`tFailed`t$msg" | Out-File -FilePath $logPath -Append -Encoding UTF8
$failCount++
}
}
Write-Progress -Activity "ユーザー無効化処理" -Completed
# 7) サマリー表示
Write-Host "`n========== 処理完了 ==========" -ForegroundColor Cyan
Write-Host "成功: $successCount 件" -ForegroundColor Green
Write-Host "失敗: $failCount 件" -ForegroundColor Red
Write-Host "結果ログ: $logPath" -ForegroundColor Cyan
使い方
- CSVファイルを準備します
- スクリプトを実行します
- 確認プロンプトで "yes" を入力します
- 結果ログを確認します
- 接続解除 (オプション)
Disconnect-MgGraph
注意点
セッション無効化について
AccountEnabled:$false だけでは既存セッションが残り続けます。Revoke-MgUserSignInSession による強制ログアウトを推奨します。
理由:
- 無効化されたユーザーでも、既存のアクセストークンが有効期限内であれば一部のサービスにアクセス可能な場合があります
- セッション無効化により、すべてのアクティブなセッションが即座に切断されます
ログファイルについて
結果ログ(TSV形式)には以下の情報が記録されます:
- UserPrincipalName: 対象ユーザー
- Result: Success / Failed / Skip
- Message: 処理結果の詳細
トラブルシューティング
"Insufficient privileges" エラー
原因: 必要な権限が不足しています。
対処法: User.ReadWrite.All 権限でGraph APIに接続してください。
Connect-MgGraph -Scopes "User.ReadWrite.All"
セッション無効化がスキップされる
原因: Microsoft.Graph v2.x未満のバージョンを使用しています。
対処法: Microsoft.Graph v2.x以降に更新してください。
Update-Module Microsoft.Graph
CSV読み込みエラー
原因: CSVファイルの形式が正しくありません。
対処法:
- ヘッダー行が
UserPrincipalNameになっているか確認してください - 文字コードがUTF-8になっているか確認してください
- Excelで開いて余分な空行がないか確認してください
補足: ユーザーを一括削除する場合
無効化ではなく削除したい場合は、以下のように変更します:
# 無効化の代わりに削除
Remove-MgUser -UserId $userObj.Id -ErrorAction Stop
# セッション無効化は不要(削除時は自動的に切断される)
注意事項:
- 削除されたユーザーは30日後に完全削除されます
- ライセンスは即座に解放されます
- 慎重に実行してください
推奨: まず無効化で運用し、問題がなければ削除する段階的なアプローチをお勧めします。
まとめ
GUIでは実現できない一括無効化を、安全かつ監査可能な形で実装できました。
ポイント:
- CSVベースでの一括処理により、作業効率が大幅に向上します
- セッション無効化により、セキュリティリスクを最小化できます
- 結果ログ(TSV形式)により、監査証跡を残すことができます
アカウント棚卸しの事前準備として、Entra IDの最終サインイン日時を一括で取得する方法と組み合わせることで、より効率的な運用が可能になります。