みなさん、こんにちは! 業務ハックLabのようです。
本記事はMicrosoft 365 Advent Calendar 2025 12月21日担当分の記事です。
突然ですが、最近Microsoft 365の管理画面でポチポチ作業してませんか?
「このユーザー、MFA設定してるかな?」「ライセンス、誰が何使ってるんだっけ?」って確認するのに、管理画面を延々とクリックして回る日々…疲れますよね。
僕も以前までMicrosoft 365の管理をバリバリやってたので、この苦労、よ~~~くわかります。
数十人規模ならまだしも、数百人の組織になると、もうクリックだけで日が暮れちゃいます(マジで)。
そこで便利なのがPowerShell!!
とはいえあの黒い画面、見慣れない方には触るのはちょっと怖く感じちゃいますよね・・・
でも覚えると管理をするとき、メチャクチャ便利なんです。
・・・が、ここで大事なお知らせがあります。
実は、これまで使われていた古いPowerShellモジュール(MSOnlineとかAzureADとか)が、2025年前半に段階的に廃止されることが決まっているんです。
もう目の前に迫ってますね…!
参考情報:
Action required: MSOnline and AzureAD PowerShell retirement – 2025 info and resources
Important Update: AzureAD PowerShell retirement
というわけで今回は、これから主流になる Microsoft Graph PowerShell を使った、最先端かつ実用的なスクリプトを3つご紹介していきます!
コピペで使えるようにしてますので、ぜひ試してみてください。
【超重要!!】
自前の環境で検証はしていますが何かあっても僕は責任が取れませんので実行する際は自己責任でお願いします。
まずはテスト環境で試すのが鉄則ですよ!
🚀 準備:新しいPowerShellモジュールを導入しよう
まずは準備から始めましょう!
新しいモジュールをまだ入れたことがない方は、PowerShellを「管理者として実行」して、以下のコマンドを一度だけ実行してください。
# Microsoft Graph PowerShell SDKのインストール
Install-Module Microsoft.Graph -Scope CurrentUser
はい、これだけです!-Scope CurrentUser を指定しているので、管理者権限なしでもインストールできますよ。
1. 謎の文字列を解読せよ!「ライセンス利用状況レポート」
まずは問題提起から
皆さん、PowerShellでライセンス情報を取得したことありますか?
取得すると、こんな謎の英数字(GUID)が返ってくるんですよね…
6fd2c87f-b296-42f0-b197-1e91e994b900
「これ何のライセンス!?」ってなりませんか?(なりますよね)
今回のスクリプトはテナント内のライセンス情報を直接取得して、「Office 365 E3」みたいな分かりやすい名前に変換してCSVに出力してくれる優れものです!
では、スクリプトを見ていきましょう
# Microsoft 365に接続
Connect-MgGraph -Scopes "User.Read.All", "Directory.Read.All", "Organization.Read.All"
# ライセンスマッピング辞書を作成
$LicenseMap = @{}
Get-MgSubscribedSku -All | ForEach-Object { $LicenseMap[$_.SkuId] = $_.SkuPartNumber }
# レポート作成(全ユーザー取得後、ライセンス保有者のみフィルタリング)
$Report = Get-MgUser -All -Property Id, DisplayName, UserPrincipalName, AssignedLicenses, LicenseAssignmentStates |
Where-Object { $_.AssignedLicenses.Count -gt 0 } |
ForEach-Object {
$User = $_
foreach ($License in $User.AssignedLicenses) {
$State = $User.LicenseAssignmentStates | Where-Object { $_.SkuId -eq $License.SkuId }
[PSCustomObject]@{
表示名 = $User.DisplayName
ユーザープリンシパル名 = $User.UserPrincipalName
ライセンス名 = if ($LicenseMap[$License.SkuId]) { $LicenseMap[$License.SkuId] } else { "不明" }
割り当てタイプ = if ($State.AssignedByGroup) { "グループ経由" } else { "直接割り当て" }
}
}
}
# プレビュー表示
Write-Host "`nライセンス割り当て済みユーザー: $($Report.Count)件`n" -ForegroundColor Cyan
$Report | Select-Object -First 5 | Format-Table -AutoSize
# ファイル保存ダイアログ
Add-Type -AssemblyName System.Windows.Forms
$Dialog = New-Object System.Windows.Forms.SaveFileDialog -Property @{
Filter = "CSVファイル (*.csv)|*.csv"
FileName = "LicenseReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
InitialDirectory = [Environment]::GetFolderPath("Desktop")
}
if ($Dialog.ShowDialog() -eq 'OK') {
$Report | Export-Csv -Path $Dialog.FileName -NoTypeInformation -Encoding UTF8
Write-Host "✓ 保存完了: $($Dialog.FileName)" -ForegroundColor Green
if ((Read-Host "フォルダを開く? (Y/N)") -match '^[yY]$') {
Start-Process explorer.exe -ArgumentList (Split-Path $Dialog.FileName)
}
}
# 接続切断
Disconnect-MgGraph
Write-Host "接続を切断しました。" -ForegroundColor Green
ポイント解説
まず、テナント内のライセンス情報を直接取得して、辞書(ハッシュテーブル)を作ります。
$LicenseMap = @{}
Get-MgSubscribedSku -All | ForEach-Object { $LicenseMap[$_.SkuId] = $_.SkuPartNumber }
これで「SkuId → 製品コード(SkuPartNumber)」の変換テーブルが出来上がるわけですね!
そして、ユーザー情報を取得する際に LicenseAssignmentStates プロパティもしっかり取得します。 これで「直接割り当て」か「グループ割り当て」かも判別できるので、ライセンス管理の適正化にめちゃくちゃ役立ちますよ!
(グループベースライセンス管理してる組織は多いと思うので、これ地味に便利です)
2. セキュリティの要!「MFA(多要素認証)設定チェック」
これ、管理者の悩みですよね
「全員MFAを設定したはずなのに…本当に全員設定してるかな?」
この不安、ありませんか?(僕はあります)
新しいMicrosoft Graph環境では、昔みたいに「MFA有効/無効」っていう単純なスイッチはもうないんです。
代わりに、「どんな認証方法(アプリ、電話など)が登録されているか」を確認する必要があります。
このスクリプトは、ユーザーごとの認証方法をチェックして、「パスワードしか設定していない危険なユーザー」をあぶり出してくれます!
スクリプトはこちら
# 接続
Connect-MgGraph -Scopes "User.Read.All", "UserAuthenticationMethod.Read.All"
# ユーザー取得(会議室・リソースアカウント除外)
$Users = Get-MgUser -All -Filter "accountEnabled eq true and userType eq 'Member'" -Property Id, DisplayName, UserPrincipalName |
Where-Object {
$_.DisplayName -notmatch '会議室|Room|リソース|Resource|共有|Shared' -and
$_.UserPrincipalName -notmatch '^(room|resource|shared|conf)'
}
Write-Host "対象ユーザー数: $($Users.Count)" -ForegroundColor Cyan
# MFAレポート作成
$i = 0
$MfaReport = $Users | ForEach-Object {
Write-Progress -Activity "MFA設定チェック" -Status $_.DisplayName -PercentComplete ((++$i / $Users.Count) * 100)
try {
$Methods = (Get-MgUserAuthenticationMethod -UserId $_.Id).AdditionalProperties["@odata.type"] -replace "#microsoft.graph.|AuthenticationMethod", ""
[PSCustomObject]@{
表示名 = $_.DisplayName
UPN = $_.UserPrincipalName
状態 = if (($Methods | Where-Object { $_ -notin @("password", "email") }).Count -gt 0) { "MFA対応OK" } else { "パスワードのみ(危険)" }
認証方法 = $Methods -join ", "
}
} catch { Write-Host "スキップ: $($_.UserPrincipalName)" -ForegroundColor Yellow }
}
Write-Progress -Activity "MFA設定チェック" -Completed
# 統計・プレビュー表示
Write-Host "`n--- 統計情報 ---" -ForegroundColor Cyan
$MfaReport | Group-Object 状態 | ForEach-Object {
Write-Host "$($_.Name): $($_.Count)人" -ForegroundColor $(if ($_.Name -eq "MFA対応OK") {"Green"} else {"Red"})
}
$MfaReport | Select-Object -First 5 | Format-Table -AutoSize
# 保存ダイアログ
Add-Type -AssemblyName System.Windows.Forms
$Dialog = New-Object System.Windows.Forms.SaveFileDialog -Property @{
Filter = "CSVファイル (*.csv)|*.csv"
FileName = "MfaStatus_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
InitialDirectory = [Environment]::GetFolderPath("Desktop")
}
if ($Dialog.ShowDialog() -eq 'OK') {
$MfaReport | Export-Csv -Path $Dialog.FileName -NoTypeInformation -Encoding UTF8
Write-Host "✓ 保存完了: $($Dialog.FileName)" -ForegroundColor Green
if ((Read-Host "フォルダを開く? (Y/N)") -match '^[yY]$') {
Start-Process explorer.exe -ArgumentList (Split-Path $Dialog.FileName)
}
}
# 切断
Disconnect-MgGraph
Write-Host "接続を切断しました。" -ForegroundColor Green
ここがポイント!
認証方法は AdditionalProperties の中に入ってるんです。(ちょっとわかりにくいですよね)
この中から @odata.type を取り出して、microsoft.graph. と AuthenticationMethod を除去すると、password、phone、microsoftAuthenticator みたいな種類名が取れます!
で、この中に password と email 以外のものがあれば「MFA対応OK」、なければ「パスワードのみ(危険)」って判定してるわけです。
これでセキュリティ監査もバッチリですね!
3. 幽霊部員を探せ!「非アクティブユーザー検出」
退職者アカウント、消し忘れてませんか?
これ、あるあるですよね…
退職した人のアカウントが残ったまま、とか。
でも、単純に「最終ログイン日時」だけで判断すると、スマホでメールだけ見ている人を誤って消してしまう事故が起きちゃうんです。(これマジで怖い)
このスクリプトは、「対話型(PC操作)」と「非対話型(バックグラウンド同期)」の両方をチェックする賢いやつです!
注意事項
※実行にはMicrosoft Entra ID P1以上のライセンスが必要です。
(Free版だと SignInActivity プロパティ(lastSignInDateTime等のログイン情報)が取得できないんですよね…)
参考ページ
非アクティブなユーザー アカウントを検出して調査する方法
スクリプトどうぞ!
# 接続
Connect-MgGraph -Scopes "AuditLog.Read.All", "User.Read.All"
# 非アクティブ期間設定
$Days = 90
$Threshold = (Get-Date).AddDays(-$Days)
Write-Host "ユーザー情報を取得中..." -ForegroundColor Cyan
# 非アクティブユーザーレポート作成(null値チェック付き)
$InactiveUsers = Get-MgUser -All `
-Property Id, DisplayName, UserPrincipalName, SignInActivity, UserType `
-Filter "accountEnabled eq true" |
Where-Object {
# 通常ユーザーのみ(会議室・リソース除外)
$_.UserType -eq 'Member' -and
$_.DisplayName -notmatch '会議室|Room|リソース|Resource|共有|Shared' -and
# SignInActivityが存在し、LastSignInDateTimeがnullでないことを確認
$_.SignInActivity -and
$_.SignInActivity.LastSignInDateTime -and
# 指定期間以上ログインなし
[datetime]$_.SignInActivity.LastSignInDateTime -lt $Threshold
} |
ForEach-Object {
[PSCustomObject]@{
表示名 = $_.DisplayName
UPN = $_.UserPrincipalName
最終対話型ログイン = if ($_.SignInActivity.LastSignInDateTime) {
[datetime]$_.SignInActivity.LastSignInDateTime
} else {
"なし"
}
最終非対話型ログイン = if ($_.SignInActivity.LastNonInteractiveSignInDateTime) {
[datetime]$_.SignInActivity.LastNonInteractiveSignInDateTime
} else {
"なし"
}
状態 = "要確認"
}
}
Write-Host "非アクティブユーザー数: $($InactiveUsers.Count)" -ForegroundColor Yellow
# プレビュー表示
if ($InactiveUsers.Count -gt 0) {
Write-Host "`n--- データプレビュー (最初の5件) ---" -ForegroundColor Cyan
$InactiveUsers | Select-Object -First 5 | Format-Table -AutoSize
} else {
Write-Host "`n✓ $Days 日以上ログインのないユーザーはいません。" -ForegroundColor Green
}
# 保存ダイアログ
if ($InactiveUsers.Count -gt 0) {
Add-Type -AssemblyName System.Windows.Forms
$Dialog = New-Object System.Windows.Forms.SaveFileDialog -Property @{
Filter = "CSVファイル (*.csv)|*.csv"
FileName = "InactiveUsers_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
InitialDirectory = [Environment]::GetFolderPath("Desktop")
}
if ($Dialog.ShowDialog() -eq 'OK') {
$InactiveUsers | Export-Csv -Path $Dialog.FileName -NoTypeInformation -Encoding UTF8
Write-Host "`n✓ 保存完了: $($Dialog.FileName)" -ForegroundColor Green
Write-Host " $Days 日以上PCログインのないユーザー: $($InactiveUsers.Count)人" -ForegroundColor Yellow
if ((Read-Host "`n保存先フォルダを開く? (Y/N)") -match '^[yY]$') {
Start-Process explorer.exe -ArgumentList (Split-Path $Dialog.FileName)
}
}
}
# 切断
Write-Host "`nMicrosoft Graph接続を切断中..." -ForegroundColor Cyan
Disconnect-MgGraph
Write-Host "接続を切断しました。" -ForegroundColor Green
解説のポイント
ここで大事なのは、-Property パラメータで SignInActivity を明示的に指定すること!
指定しないと取得されないんですよね。(これハマりがちポイントです)
ちなみに一度もサインインしていないユーザーはSignInActivityプロパティ自体がnullになるため、二重のnullチェックを行っています。
そして、LastSignInDateTime(対話型)とLastNonInteractiveSignInDateTime(非対話型)を分けて見ることで、「PCでは長期間ログインしてないけど、スマホでメールは見てる」みたいなユーザーを誤って削除しないようにできます!
さらに、このスクリプトは賢く動きます:
- 一度もログインしていないユーザー(
SignInActivityがnull)でもエラーにならない - 会議室やリソースアカウントは自動的に除外される
はい、如何でしたでしょうか?
最初は「黒い画面(コンソール)」に抵抗があるかもしれませんが、これらのスクリプトを保存しておけば、次回からはワンクリックでレポートが出せるようになります!
2025年の強制移行に慌てないためにも、今のうちから少しずつ新しいコマンドに慣れていきましょう。
今回は「読み取り・確認」系のスクリプトを中心にご紹介しましたが、好評であれば「ユーザー一括作成」などの「実行・変更」系スクリプトもご紹介したいと思います!
それでは皆さん、良い業務ハックライフを~


コメント