2

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.

harvey263
  • 76
  • 1
  • 4

3 Answers3

1

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 attribute msDS-UserPasswordExpiryTimeComputed is read. If TO is not in a domain NC, then TO!msDS-UserPasswordExpiryTimeComputed = null. Otherwise let D be the root of the domain NC containing TO. The DC applies the following rules, in the order specified below, to determine the value of TO!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 in TO!userAccountControl, then TO!msDS-UserPasswordExpiryTimeComputed = 0x7FFFFFFFFFFFFFFF.

  • Else, if TO!pwdLastSet = null, or TO!pwdLastSet = 0, then TO!msDS-UserPasswordExpiryTimeComputed = 0.

  • Else, if Effective-MaximumPasswordAge = 0x8000000000000000, then TO!msDS-UserPasswordExpiryTimeComputed = 0x7FFFFFFFFFFFFFFF (where Effective-MaximumPasswordAge is defined in [MS-SAMR] section 3.1.1.5).

Else, TO!msDS-UserPasswordExpiryTimeComputed = TO!pwdLastSet + Effective-MaximumPasswordAge (where Effective-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.

Bacon Bits
  • 30,782
  • 5
  • 59
  • 66
  • Thanks for pointing me in the right direction. Once I checked aduc I realized what was going on, then it helped me figure out the initial problem. – harvey263 Oct 29 '17 at 09:13
1

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}
harvey263
  • 76
  • 1
  • 4
  • I would avoid using `*`. It's fetching a lot of data doing that -- especially if you're using thumbnail photos -- and making your domain controllers do more work than they need to. Specify the exact, complete list of properties that you wish to use. – Bacon Bits Oct 29 '17 at 13:21
  • That is how I started, then just switched to `*` out of convenience as the property list grew. But it created my PasswordExpiry problem and sent me down a path of confusion. I also changed it to loop through an array of OUs instead of reading from a text file. I feel like it is running faster now during big searches. – harvey263 Oct 29 '17 at 16:30
0

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
Zunair
  • 1,085
  • 1
  • 13
  • 21