Windows ActiveDirectory において長期ログインしていない不届き者を暴く

# はじめに
アカウントが必要だと申請するから作成したのに、全くログインしていない奴とか、最初の1度だけログインしてそれっきりとか。
たまったもんじゃない。
そういう不要アカウントはきっちり滅してあげよう。

# 前提条件
- 個人的なポリシーとしてGUIは捨てている。(とカッコつけているだけ）
- コマンドラインが正義である。（と思いこんでいるだけ）
- 定期的にコマンドを実行し、その結果をレポートしてくれるととても助かる。

# PowerShell＋Module ActiveDirectory

## 先ずは出力

ActiveDirectoryで管理しているユーザの一覧を先ず出力してみよう。

```powershell:
Import-Module ActiveDirectory
Get-ADUser -Filter *
```

私の環境ではアカウントが数万存在している為、割といい時間「どぅるるるるる」な感じで画面が動いていた。
全部出力したいならば、これで良い。
ちなみに、`-Filter *`は必須だ。

中にはユーザ一覧をCSV形式で保存したい人もいるだろう。
そんな時は以下のようにすれば良い。

```powershell:
Get-ADUser -Filter * | Export-Csv -Path C:\Users\Administrator\Desktop\Sample.csv
Get-ADUser -Filter * | Export-Csv -Path C:\Users\Administrator\Desktop\Sample.csv -Encoding UTF8
```

1行目でも出力されるけど、文字化けパラダイスなのできちんとEncodeを指定するとお行儀がいいかもしれない。

なお、特に指定無しの`Get-ADUser`の場合おり、`-Properties`を付与した方がさらに情報を出すことが出来る。
但し、沢山出るのでびっくりしないように。

```powershell:
Get-ADUser -Filter * -Properties * | Export-Csv -Path C:\Users\Administrator\Desktop\Sample.csv -Encoding UTF8
```

## 必要な情報だけに絞る

さて、ActiveDirectoryの情報を出すことは分かったが、情報量が多すぎても困る為、精査することにしよう。
不要な情報を得るのはセキュリティ的にも面倒だし、非効率だし、気が散って仕方がない。

今回欲しい情報は、各アカウントのログイン情報となる。レポートも行うので項目としては以下が想定される。
- アカウントID<font color="Red">【必須】</font>
- ログイン情報（最終ログイン日時)<font color="Red">【必須】</font>
- ログイン情報（ログイン有無⇒一度でもログインしたかどうか）
- アカウント表示名or氏名
- アカウントの有効無効

一先ず、1アカウントのみを指定してどんな項目で情報が出ているか確認しよう。

```powershell:
Get-ADUser -Filter 'SamAccountName -eq "testuser01"' -Properties *
```

`SamAccountName`とはWindowsログイン時に入力しているアカウントIDだと思ってくれていい。
これでどれくらいの情報が出ているかわかるだろう。
脱線するが、出力されるオブジェクトのプロパティのみを確認したい場合は以下のようにすると良い。

```powershell:
# オブジェクトの名前や型、説明
Get-ADUser -Filter 'SamAccountName -eq "testuser01"' -Properties * | Get-Member
# CSVに出しちゃえ
Get-ADUser -Filter 'SamAccountName -eq "testuser01"' -Properties * | Get-Member | Select-Object Name | Export-Csv -Path C:\Users\Administrator\Desktop\Get-ADUser_ObjectName.csv -Encoding UTF8
```

さらに脱線で…。
`-Filter`で条件を指定するいくつかのパターンは以下のとおり。

```powershell:
# 完全一致型
Get-ADUser -Filter 'SamAccountName -eq "testuser01"' -Properties *
# 部分一致型
Get-ADUser -Filter 'SamAccountName -like "*testuser01*"' -Properties *
# 他
-and,-or | -gt,-lt,-ne,-ge,-le,-notlike
```

もっとFilterの指定を確認したい場合は以下のURLから。
https://learn.microsoft.com/en-us/previous-versions/windows/server/hh531527(v=ws.10)

話は戻り、プロパティの名前から推察するに必要そうなものを以下に絞った。

| Properties Name | Desc |
|:-|:-|
| SamAccountName  | ログインアカウントIDとなる。  |
| LastLogonTimestamp  | 最終ログイン日時でログインの度に日時が更新され、DC間で共有される。だが更新頻度低めとのこと。  |
| Enabled  | アカウントの有効無効となる。  |
| logonCount  | ログイン回数となる。  |

これを踏まえるとコマンドは以下のようになってくる。

```powershell:
# SamAccountName,Enabledはデフォルトで出力される
Get-ADUser -Filter * -Properties LastLogonTimestamp,logonCount
# 必要なもののみにする場合は以下
Get-ADUser -Filter * -Properties LastLogonTimestamp,logonCount | Select-Object SamAccountName,Enabled,LastLogonTimestamp,logonCount
```

## 長期ログインしていないとする条件

さてさて、ようやく本題に入っていくわけなのだが、長期ログインしていないという定義は今回このように考えた。

1. アカウントが有効である
1. 現在日時より1年間(365+猶予30日)前に最終ログイン日時である

`1.`の場合は、`-Filter`で指定出来るのでその旨盛り込む。
`2.`の場合は、`-Filter`内で日付を入れ込むことが出来なかった(技量不足)ので、一旦最初に定義してから判定に盛り込む。

こんな感じになります。

```powershell:
$chkdate = ([DateTime]::Today.AddDays(-395)).ToFileTime()
Get-ADUser -Filter {Enabled -eq $true -and LastLogonTimestamp -lt $chkdate} `
  -Properties LastLogonTimestamp,logonCount |
  Select-Object SamAccountName,Enabled,LastLogonTimestamp,logonCount |
  ForEach-Object {
    $_.SamAccountName
    [DateTime]::FromFileTime($_.LastLogonTimestamp)
    $_.logonCount
  }
```

日時の取り方についてですが、`Get-Date`の方法もあります。
この2つの違いについては、実行結果のとおり。

```powershell:
[DateTime]::Today.AddDays(-395)
#実行結果 2021年12月22日 0:00:00
([DateTime]::Today.AddDays(-395)).ToFileTime()
#実行結果 132845724000000000
(Get-Date).AddDays(-395)
#実行結果 2021年12月22日 16:08:30
((Get-Date).AddDays(-395)).ToFileTime()
#実行結果 132846305104283932
```

どうして時間が指定されているものとされていないものになっているのだろうか…。これはまた別の機会で調べておく。

## レポートとして出力

レポートとして出力しようと思います。
ですが、前述にある`ForEach-Object`をそのまま`Export-Csv`しようとしてもエラーになります。
この為、`Export-Csv`から`Out-File`に切り替えるのですが、`ForEach-Object`のままでは体裁が悪いです。

```powershell:
$chkdate = ([DateTime]::Today.AddDays(-395)).ToFileTime()
Get-ADUser -Filter {Enabled -eq $true -and LastLogonTimestamp -lt $chkdate} `
  -Properties LastLogonTimestamp,logonCount |
  Select-Object SamAccountName,Enabled,LastLogonTimestamp,logonCount |
  ForEach-Object {
    $_.SamAccountName
    [DateTime]::FromFileTime($_.LastLogonTimestamp)
    $_.logonCount
  } | Out-File -FilePath C:\Users\Administrator\Desktop\Sample.csv -Encoding utf8
```

この為、体裁よく`Format-Table`を使い、出力してみます。

```powershell:
Get-ADUser -Filter {Enabled -eq $true -and LastLogonTimestamp -lt $chkdate} `
  -Properties LastLogonTimestamp,logonCount |
  Select-Object SamAccountName,Enabled,LastLogonTimestamp,logonCount |
  Format-Table SamAccountName,@{name='LastLogonTimestamp';expression={[DateTime]::FromFileTime($_.LastLogonTimestamp)}},logonCount |
  Out-File -FilePath C:\Users\Administrator\Desktop\Sample.csv -Encoding utf8
```

レポートとしてはいい感じになりました。
可能であれば、CSV形式にしたかったところですが、`Out-File`で出したものを`Import-Csv`で入れ込み、再度`Export-Csv`してみましたがデリミタが上手く出来ず、匙を投げました。
お勉強していつか更新します。
一応、以下のような感じです。

```powershell:
$chkdate = ([DateTime]::Today.AddDays(-395)).ToFileTime()
Get-ADUser -Filter {Enabled -eq $true -and LastLogonTimestamp -lt $chkdate} `
  -Properties LastLogonTimestamp,logonCount |
  Select-Object SamAccountName,Enabled,LastLogonTimestamp,logonCount |
  Format-Table -HideTableHeaders -AutoSize SamAccountName,@{name='LastLogonTimestamp';expression={[DateTime]::FromFileTime($_.LastLogonTimestamp)}},logonCount |
  Out-File -FilePath C:\Users\Administrator\Desktop\Sample.csv -Encoding utf8
$T = Import-Csv -Delimiter " " -Encoding UTF8 -Path C:\Users\Administrator\Desktop\Sample.csv
$T | Export-Csv -Path C:\Users\Administrator\Desktop\Sample2.csv -Encoding UTF8
#フィールドの分割がダメでアカウントIDしか出力されない
```

これでおしまい、としたいところですが、本記事をあれこれ書いている中で、`Search-ADAccount`コマンドがあることに気付きました。
今までの記載は何だったん？という結果です…。

## Search-ADAccount を使う

こんな感じで利用します。

```powershell:
Search-ADAccount -AccountInactive -DateTime "2022/10/01" -UsersOnly | Select-Object SamAccountName,Enabled,LastLogonDate
```

ちなみに`-Filter`が無い為、条件を付与したい場合は、`Where-Object`で絞ったうえで`Select-Object`+`Export-Csv`する。
OUの指定をしたい時は`DistinguishedName`でマッチする感じかな。

```powershell:
Search-ADAccount -AccountInactive -DateTime "2022/10/01" -UsersOnly | 
  Where-Object { $_.SamAccountName -like "*testuser01*" -and $_.Enabled -eq $true} |
  Select-Object SamAccountName,Enabled,LastLogonDate |
  Export-Csv -Path C:\Users\Administrator\Desktop\Sample.csv -Encoding UTF8
```

# おわりに

`Search-ADAccount`はユーザ以外にもコンピュータを対象と出来るけど、情報としては少ない。けれども出力としては理想だった。
`Get-ADUser`は多くの情報を参照して精査出来る為、今回のような長期ログインしていないケース以外には有用な感じ。
適材適所でコマンドを使っていくのがいいね。