ローカルユーザが「次回ログオン時にパスワードの変更が必要」か高速に調べる

# 概要

Workgroup 環境で運用しているサーバで、ローカルユーザの一覧を作成したかったのですが、タイトルにあるプロパティを取ろうとすると結果の出力に非常に時間がかかるという問題がありました。

試行錯誤の結果、高速化する方法を見つけたのでメモしておきます。

# はじめに

Windows のユーザアカウントには「次回ログオン時にパスワードの変更が必要」というプロパティがあるのですが、ローカルアカウントのこの値を取得する方法は限られています。昔ながらの `net user` では、セットはできるもののゲットはできません。よく Web で紹介されている WMI を使った方法は、なぜか間違った方法が目立ちます。ADSI を使った方法で値を取得できたのですが、一覧にして出力しようとすると非常に時間がかかるという問題がありました。

この記事では ADSI を使って「次回ログオン時にパスワードの変更が必要」のプロパティを含むユーザの一覧を高速に作る方法について説明します。

# 環境

- Windows Sever 2012 R2
- PowerShell 3.0

# Webで見かけた間違った方法

Web では WMI(CIM) の Win32_UserAccount の「PasswordExpires」プロパティ（または「PasswordRequired」プロパティ）が「次回ログオン時にパスワード変更が必要」に対応すると説明されている場合がありますが、これは誤りです。

以下のようにしても欲しいプロパティは取得できませんでした。

```powershell
Get-WMIObject -Class Win32_UserAccount | 
    Where-Object { $_.Name -NotIn "Administrator","Guest" } | 
    Foreach-Object { $_.Name + "," + $_.PasswordExpires } | # または PasswordRequired
    Out-File userlist.csv
```

# Webで見かけた正しいが遅い方法

ADSI を使うと「次回ログオン時にパスワード変更が必要」かどうか取得できることがわかりましたが、以下のような方法だとユーザ数が多い場合は非常に時間がかかります。

```powershell
$Users = Get-WMIObject -Class Win32_UserAccount | 
    Where-Object { $_.Name -NotIn "Administrator","Guest" } | 
    Select-Object -ExpandProperty Name

foreach($user in $Users) {
    $adsi = [ADSI]("WinNT://{0}/{1}, user" -f $Env:ComputerName, $User)
    $UserFlags = $adsi.Get("UserFlags")
    if ($UserFlags -band 0x800000) {
        echo "$user needs to change the password at the next logon."
    } else {
        echo "$user doesn't need to change the password at the next logon."
    }
}
```

# 高速化する方法について

ユーザ毎に ADSI にアクセスするのではなく、コンピュータ全体でまとめて取得して絞り込むと高速になることが判りました。

```powershell
# $ADSI = [ADSI]("WinNT://{0}/{1}, user" -f $Env:ComputerName, $User)
$ADSI = [ADSI]("WinNT://{0}" -f $Env:ComputerName)
$Users = $ADSI.Children | Where-Object { $_.SchemaClassName -eq "user" }
$Users | Foreach-Object {
    $user = New-Object psobject
    $user | Add-Member noteproperty Name $_.Name[0]
    $user | Add-Member noteproperty LogonPasswordChg (($_.Get("UserFlags")[0] -band 0x800000) -ne 0)
    return $user
}
```

# おわりに

最終的に作りたかったユーザの一覧は以下のように作成しました。

```powershell
$ADSI = [ADSI]("WinNT://{0}" -f $Env:ComputerName)
$Users = $ADSI.Children | Where-Object { $_.SchemaClassName -eq "user" }
$Users | Foreach-Object {
    $Name = $_.Name[0]
    $Flags = $_.Get("UserFlags")[0]

    $user = New-Object psobject
    $user | Add-Member noteproperty ComputerName        $Env:ComputerName
    $user | Add-Member noteproperty Name                $Name
    $user | Add-Member noteproperty Enable              (($Flags -band 0x2     ) -eq 0)
    $user | Add-Member noteproperty LockOut             (($Flags -band 0x10    ) -ne 0)
    $user | Add-Member noteproperty CantPasswordChange  (($Flags -band 0x40    ) -ne 0)
    $user | Add-Member noteproperty DontExpirePassword  (($Flags -band 0x10000 ) -ne 0)
    $user | Add-Member noteproperty LogonPasswordChange (($Flags -band 0x800000) -ne 0)

    return $user
} | ConvertTo-Csv -NTI | Out-File userlist.csv
```

# 参考
[PowerShellでローカルアカウントの操作](http://www.vwnet.jp/windows/PowerShell/MaintenanceLocalAccunt.htm)
[PowerShell で WorkGroup 環境の Windows User ユーザーフラグを変更する - tech.guitarrapc.cóm](http://tech.guitarrapc.com/entry/2013/09/06/230957)
[Reporting on Local Accounts Using PowerShell -- Microsoft Certified Professional Magazine Online](https://mcpmag.com/articles/2015/04/15/reporting-on-local-accounts.aspx)