「退職者のアカウントが残っていないか」「パスワード無期限のアカウントはどれか」は、監査のたびに聞かれる割に、管理画面から目視で数えるのが現実的でない項目です。以前 PowerShellでユーザーが所属するグループ一覧を取得する で1ユーザーの調べ方を書きましたが、棚卸しで必要なのは全件を一覧にして並べ替えられる状態です。
この記事では、Active Directory のユーザーを PowerShell で棚卸しし、CSV に書き出すまでをまとめます。最終ログオン日時の扱いという、見落とすと結論を間違える点についても触れます。

1. 準備:ActiveDirectoryモジュール
Get-ADUser などのコマンドレットは RSAT(リモートサーバー管理ツール)に含まれる ActiveDirectory モジュールで提供されます。入っているかは次で確認できます。
# モジュールがあるか確認する
Get-Module -ListAvailable -Name ActiveDirectory
手元の Windows 11 で確認したところ、バージョン 1.0.1.0 で 151個のコマンドレットが使えました。入っていない場合は「オプション機能」から RSAT を追加します。
# 未導入の場合(管理者権限が必要)
Add-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0
ドメインに参加していない端末で実行すると、次のエラーになります。
Active Directory Web サービスが実行されている状態で既定のサーバーを検索することはできません。
この場合は -Server でドメインコントローラーを明示します。ドメイン参加端末なら指定は不要です。
2. 棚卸しで見る列を絞る
Get-ADUser は既定だと最低限の属性しか返しません。棚卸しに必要なものは -Properties で明示的に指定します。
# 棚卸しに使う属性だけを取得する
$users = Get-ADUser -Filter * -Properties `
LastLogonDate, PasswordLastSet, PasswordNeverExpires, Enabled, Department, Title
全属性を取りたくなりますが、-Properties * はドメインコントローラーへの負荷が大きく、列が多すぎて読めない CSV になります。見る列を先に決めるほうが実用的です。棚卸しで最初に見るのは次の3つです。
| 列 | 見るポイント |
|---|---|
LastLogonDate | 90日以上ログオンが無い=退職者・棚卸し漏れの候補 |
PasswordNeverExpires | 有効なら共有アカウントの可能性が高い |
Enabled | 無効のまま残っている古いアカウント |
3. 判定列を付けてCSVに書き出す
生の値をそのまま出しても読みづらいので、計算プロパティで「要確認」の印を付けます。
# 90日を休眠の基準にする
$border = (Get-Date).AddDays(-90)
$users | Select-Object `
SamAccountName, Name, Department, Enabled,
@{ N = '最終ログオン'; E = {
if ($_.LastLogonDate) { $_.LastLogonDate.ToString('yyyy-MM-dd') } else { '記録なし' } } },
@{ N = '休眠日数'; E = {
if ($_.LastLogonDate) { [int]((Get-Date) - $_.LastLogonDate).TotalDays } else { $null } } },
@{ N = '要確認'; E = {
# ログオン記録が無い、または基準日より古いものに印を付ける
if (-not $_.LastLogonDate -or $_.LastLogonDate -lt $border) { '○' } else { '' } } },
@{ N = 'パス無期限'; E = {
if ($_.PasswordNeverExpires) { '○' } else { '' } } } |
Sort-Object 休眠日数 -Descending |
Export-Csv -Path .\ad-inventory.csv -NoTypeInformation -Encoding utf8BOM
ポイントは2つです。
LastLogonDate が $null になるユーザーが必ずいます。一度もログオンしていないアカウントです。判定式で -not $_.LastLogonDate を先に見ておかないと、日付比較のところで意図しない結果になります。
文字コードは utf8BOM を指定します。BOM が無いと Excel が Shift_JIS と解釈し、日本語の列名や部署名が文字化けします。
4. 最終ログオン日時は「だいたいの値」
ここが一番の落とし穴です。LastLogonDate はドメインコントローラー間で複製されますが、更新に間隔があるため、問い合わせた DC によって数日ずれます。
これは仕様です。毎回のログオンを全 DC に複製すると負荷が高すぎるため、既定では9日から14日程度の幅を持って更新されます。
「90日休眠だから削除」といった判断を、1台の DC の値だけで下すのは危険です。厳密に見たい場合は、全 DC に問い合わせて最大値を採用します。
# 全ドメインコントローラーから最終ログオンを集め、最も新しい値を採用する
$dcs = (Get-ADDomainController -Filter *).HostName
$latest = @{}
foreach ($dc in $dcs) {
# lastLogon は複製されない属性。各DCが個別に持っている
Get-ADUser -Filter * -Properties lastLogon -Server $dc | ForEach-Object {
$t = if ($_.lastLogon) { [DateTime]::FromFileTime($_.lastLogon) } else { $null }
if ($t -and (-not $latest[$_.SamAccountName] -or $t -gt $latest[$_.SamAccountName])) {
$latest[$_.SamAccountName] = $t
}
}
}
lastLogon(複製されない・DCごとの正確な値)と LastLogonDate(複製される・粗い値)は別の属性です。DC が数台なら上の方法で十分ですが、拠点が多い環境では時間がかかるため、まず LastLogonDate で候補を絞ってから、候補だけを全 DC で確認するのが現実的です。
5. 削除の前に無効化する
棚卸しの結果をそのまま削除に流すのは避けます。退職者だと思ったアカウントがサービスアカウントだった、という取り違えは実際に起こります。
# 候補をいったん無効化するだけにとどめる(Descriptionに理由を残す)
$targets = Import-Csv .\ad-inventory.csv | Where-Object { $_.要確認 -eq '○' }
foreach ($t in $targets) {
Set-ADUser -Identity $t.SamAccountName -Enabled $false `
-Description "2026-08 棚卸しで無効化(休眠 $($t.休眠日数) 日)"
}
無効化して1〜2か月様子を見てから削除する、という運用にしておくと戻せます。実行前に必ず -WhatIf を付けて対象を確認してください。
# 実際には変更せず、対象だけを表示する
Set-ADUser -Identity u001 -Enabled $false -WhatIf
6. まとめ
PowerShell で AD ユーザーを棚卸しし、CSV にまとめる手順をまとめました。
-Propertiesで必要な属性だけを指定する。*は負荷が高く、読めない CSV になるLastLogonDateが$nullのユーザーが必ずいる。判定式で先に処理する- 最終ログオン日時は DC によって数日ずれる。削除の判断は全 DC の最大値で確認する
- 削除の前に無効化を挟み、
-WhatIfで対象を確認してから実行する
なお、本記事のコマンドはドメインに参加していない検証端末で作成しているため、集計と判定のロジックは動作を確認していますが、実ドメインに対する Get-ADUser の実行結果までは確認できていません。実行前に、必ず検証用のドメインか、影響のない OU に対象を絞って試してください。
PowerShellでユーザーが所属するグループ一覧を取得する
LinuxサーバのActiveDirectoryへの参加手順【CentOS】
PowerShellの学習におすすめの書籍
PowerShell を体系的に学ぶなら、PowerShell Core 以降のクロスプラットフォーム対応をふまえた解説書が分かりやすいです。Windows の自動化にとどまらず、macOS や Linux でも動く現在の PowerShell を、基礎から実務での使い方まで通して押さえられます。
PowerShell実践ガイドブック クロスプラットフォーム対応の次世代シェルを徹底解説



コメント