Reputation: 76
I can get a valid "msDS-UserPasswordExpiryTimeComputed" property for a single user:
(([DateTime]::FromFileTime((Get-ADUser $UserName -Properties "msDS-UserPasswordExpiryTimeComputed")."msDS-UserPasswordExpiryTimeComputed")))
But I'm having trouble targeting a group of specific users. My larger script is intended to pull various AD user attributes from accounts in particular OUs and export to csv. All of the properties are populated as expected, except for the "PasswordExpiry" object.
The below sample returns a bogus "PasswordExpiry" date of "12/31/1600" for every user. "C:\UserList.txt" contains sAMAccountNames one per line.
$UserList=Get-Content "C:\UserList.txt"
ForEach ($UserName in $UserList) {Get-ADUser "$UserName" -Properties * |
Select-Object sAMAccountName,whenCreated, `
@{Name="lastLogon";Expression={[DateTime]::FromFileTime($_.lastLogon)}}, `
@{Name="pwdLastSet";Expression={[DateTime]::FromFileTime($_.pwdLastSet)}}, `
@{Name="PasswordExpiry";Expression={[DateTime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed")}}, `
cannotChangePassword,passwordNeverExpires, `
@{Name="GroupMember";Expression={($_ | Select -ExpandProperty MemberOf) | Where {$_ -Like "*Desired.Group*"}}} |
Export-Csv -Path "C:\UserInfo.csv" -Append -NoTypeInformation}
This works if I wanted to query all AD users:
Get-ADUser -Filter * –Properties sAMAccountName,"msDS-UserPasswordExpiryTimeComputed" |
Select-Object -Property sAMAccountName,@{Name="PasswordExpiry";Expression={[DateTime]::FromFileTime($_.“msDS-UserPasswordExpiryTimeComputed”)}}
But if I add the [-SearchBase "OU=Users,DC=Domain,DC=local"] parameter to Get-ADUser, I get null output for "PasswordExpiry". I guess I could try parsing the entire output with some post processing. Seems like touching more than I should have to though.
I know that I can calculate the expiration based off the "pwdLastSet" attribute. But I want to pull the actual value because age policy might be unknown. Any help is appreciated.
Upvotes: 2
Views: 6598
Reputation: 1195
Here's a script that runs on a specific OU and gets username, email, dn, password last set, expiry computed and days in the password will expire in.
Skips any users that has Pass never expire enabled.
Also skips disabled users.
This can be improved using logic mentioned in msDS-UserPasswordExpiryTimeComputed specs (see other answers for details)
Get-ADUser -SearchBase "OU=Users,DC=domain,DC=org" `
-filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} `
–Properties "SamAccountName","mail","distinguishedName","pwdLastSet","msDS-UserPasswordExpiryTimeComputed","msDS-UserPasswordExpiryTimeComputed" |
Select-Object -Property "SamAccountName","mail","distinguishedName",
@{Name="Password Last Set"; Expression={[datetime]::FromFileTime($_."pwdLastSet")}},
@{Name="Password Expiry Date"; Expression={[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed")}},
@{Name="Password Expired"; Expression=`
{
if(([datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed") - (GET-DATE)).Days -le 0)
{
if([datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed").Year -eq 1600)
{
'Password Never Set'
}
else
{
'Yes'
}
}
else
{
'No'
}
}
} |
Export-CSV "C:\temp\PasswordExpirationReport.csv" -NoTypeInformation -Encoding UTF8
Upvotes: 0
Reputation: 32145
Per the spec on msDS-UserPasswordExpiryTimeComputed
:
The msDS-UserPasswordExpiryTimeComputed attribute exists on AD DS but not on AD LDS.
This attribute indicates the time when the password of the object will expire. Let
TO
be the object on which the attributemsDS-UserPasswordExpiryTimeComputed
is read. IfTO
is not in a domainNC
, thenTO!msDS-UserPasswordExpiryTimeComputed = null
. Otherwise letD
be the root of the domainNC
containingTO
. TheDC
applies the following rules, in the order specified below, to determine the value ofTO!msDS-UserPasswordExpiryTimeComputed
:
If any of the
ADS_UF_SMARTCARD_REQUIRED
,ADS_UF_DONT_EXPIRE_PASSWD
,ADS_UF_WORKSTATION_TRUST_ACCOUNT
,ADS_UF_SERVER_TRUST_ACCOUNT
,ADS_UF_INTERDOMAIN_TRUST_ACCOUNT
bits is set inTO!userAccountControl
, thenTO!msDS-UserPasswordExpiryTimeComputed = 0x7FFFFFFFFFFFFFFF
.Else, if
TO!pwdLastSet = null
, orTO!pwdLastSet = 0
, thenTO!msDS-UserPasswordExpiryTimeComputed = 0
.Else, if
Effective-MaximumPasswordAge = 0x8000000000000000
, thenTO!msDS-UserPasswordExpiryTimeComputed = 0x7FFFFFFFFFFFFFFF
(whereEffective-MaximumPasswordAge
is defined in [MS-SAMR] section 3.1.1.5).Else,
TO!msDS-UserPasswordExpiryTimeComputed = TO!pwdLastSet + Effective-MaximumPasswordAge
(whereEffective-MaximumPasswordAge
is defined in [MS-SAMR] section 3.1.1.5).
So, there's at least two magic values for msDS-UserPasswordExpiryTimeComputed
: 0x7FFFFFFFFFFFFFFF
, and 0
. They both appear to represent slightly different meanings of "never" (password has never been set, account password set to never expire, no max password age exists). My guess is that the accounts you're querying fall into one of the above categories. I'd be curious what the raw values of the msDS-UserPasswordExpiryTimeComputed
field are.
If you've recently created a password age policy, I wonder if you might need to force users to reset their passwords in order for it to take effect? I thought the behavior was that everybody's password expired immediately, but it's been years since I needed to think about managing a password age policy.
Upvotes: 1
Reputation: 76
I had to define the property in addition to "*" to make it work. Apparently asterisk alone isn't enough in this case.
Get-ADUser -Identity "$UserName" -Properties *,"msDS-UserPasswordExpiryTimeComputed"
The second example I thought that was only working without -SearchBase was actually working all along. As Bacon Bits highlighted, I was only querying a small group of users that had their expiration set to Never. So null was a valid output there.
This wasn't the problem with the original script. It returned "12/31/1600" no matter what. As mentioned above, I had to add the additional property to get the correct values. These are good now.
$UserList=Get-Content "C:\UserList.txt"
ForEach ($UserName in $UserList) {Get-ADUser "$UserName" -Properties *,"msDS-UserPasswordExpiryTimeComputed" |
Select-Object sAMAccountName,whenCreated, `
@{Name="lastLogon";Expression={[DateTime]::FromFileTime($_.lastLogon)}}, `
@{Name="pwdLastSet";Expression={[DateTime]::FromFileTime($_.pwdLastSet)}}, `
@{Name="PasswordExpiry";Expression={[DateTime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed")}}, `
cannotChangePassword,passwordNeverExpires, `
@{Name="GroupMember";Expression={($_ | Select -ExpandProperty MemberOf) | Where {$_ -Like "*Desired.Group*"}}} |
Export-Csv -Path "C:\UserInfo.csv" -Append -NoTypeInformation}
Get-ADUser -Filter * -SearchBase "OU=Users,DC=Domain,DC=local" –Properties sAMAccountName,"msDS-UserPasswordExpiryTimeComputed" |
Select-Object -Property sAMAccountName,@{Name="PasswordExpiry";Expression={[DateTime]::FromFileTime($_.“msDS-UserPasswordExpiryTimeComputed”)}}
EDIT: Now doing this per Bacon Bits suggestion:
$ListOfOUs="OU=Users01,DC=Domain,DC=local","OU=Users02,DC=Domain,DC=local","OU=Users03,DC=Domain,DC=local"
$ListOfOUs | ForEach {Get-ADUser -Filter * -SearchBase $_ -Properties sAMAccountName,whenCreated,lastLogon, `
pwdLastSet,"msDS-UserPasswordExpiryTimeComputed",cannotChangePassword,passwordNeverExpires,MemberOf |
Select-Object sAMAccountName,whenCreated, `
@{Name="lastLogon";Expression={[DateTime]::FromFileTime($_.lastLogon)}}, `
@{Name="pwdLastSet";Expression={[DateTime]::FromFileTime($_.pwdLastSet)}}, `
@{Name="PasswordExpiry";Expression={[DateTime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed")}}, `
cannotChangePassword,passwordNeverExpires, `
@{Name="GroupMember";Expression={($_ | Select -ExpandProperty MemberOf) | Where {$_ -Like "*Desired.Group*"}}} |
Export-Csv -Path "C:\UserInfo.csv" -Append -NoTypeInformation}
Upvotes: 1