Subtraction between two dates in Powershell is evaluating to null - powershell

I am using the following code to display some values , the subtraction between two dates as in the expression
[datetime]::Now.AddDays(-365) - $_.PasswordLastSet).Days
is evaluating to null in my data, what could be the reason, how to fix this code ?
Get-ADUser -Filter * -Properties PasswordLastSet, Mail -SearchBase $searchBase |
Where-Object{
([datetime]::Now.AddDays(-365) - $_.PasswordLastSet).Days -in #(" ",364, 350, 354, 340, 335,' ','',"")
}|
Select-Object name, PasswordLastSet,#{n='DaysUntilExpired';e={(([datetime]::Now.AddDays(365) - [datetime]::$_.PasswordLastSet).Day).days}} |
Add-Content -Path C:\Temp\PasswordExpList.txt

There are two obvious bugs in your code, both here:
#{n='DaysUntilExpired';e={(([datetime]::Now.AddDays(365) - [datetime]::$_.PasswordLastSet).Day).days}}
However, I can't tell if you should specify this:
#{n='DaysUntilExpired';e={([datetime]::Now.AddDays(365) - $_.PasswordLastSet).Days}}
Or, if you're looking for the last day of the month prior to the expiration date, then this:
#{n='DaysUntilExpired';e={(([datetime]::Now.AddDays(365) - $_.PasswordLastSet.Day).Days}}
As the comments mention, [datetime]::$_.PasswordLastSet is nonsense. That's the first bug. The second one is that the property of a timespan is Days, not Day. Day is a property of a datetime.
However, assuming you're trying to calculate the password expiration date defined by AD password policy, you should look at the msDS-UserPasswordExpiryTimeComputed property in AD because that's what that property contains. You have to convert it from a large integer date, but that's what [datetime]::fromfiletime() does.
Get-ADUser -Filter * -Properties PasswordLastSet, Mail, 'msDS-UserPasswordExpiryTimeComputed' -SearchBase $searchBase |
Select-Object -Properties Name, PasswordLastSet, #{n='PasswordExpirationDate';e={[DateTime]::FromFileTime($_.'msDS-UserPasswordExpiryTimeComputed')}}
This doesn't do everything your script does, but then I don't understand why you're looking for accounts whose password was set an exact number of days in the past. That doesn't seem useful.

Related

Unable to get AD users/mailboxes created within a certain number of days using Powershell

Good morning. As the title states, the company I work for has a requirement to get a list of user accounts and mailboxes that were created over the last 30 days.
I am using the following Powershell script to achieve this, but I am NOT getting any results returned.
$lastday = ((Get-Date).AddDays(-30))
Get-ADUser -filter {(whencreated -ge $lastday)}
Any help in this regard would be appreciated.
That code should work just fine, but for the sake of readability i would remove the extra () in both lines.
You'll also want to add -properties whencreated so you can actually see the date of creation.
$lastday = (Get-Date).AddDays(-30)
Get-ADUser -filter {whencreated -ge $lastday} -properties whencreated
If you're not getting results that's because there's no users created in the past 30 days.
This is what you want:
$lastday = ((Get-Date).AddDays(-30))
Get-ADUser -Filter * -Properties whencreated | Where-Object {$_.whencreated -ge $lastday}
If you don't get any results it means no AD user where crated in that period, or someone has deleted them.

Querying custom LDAP filtering with last created 24 hours

I want to use LDAP query to return all user objects created in the last 24 hours with the following Attributes.
I have like below so far.
(&(objectCategory=person)(objectClass=user))
Attributes:
samaccountname (username)
givenName (first name)
sn (last name)
mail (mail addresses)
Thanks,
You'll need some code to generate a string for "24 hours ago" (or whatever delta time you want). The datetime string format needs to be YYYYMMDDHHmmss.0Z -- the timestamp is UTC.
The following filter finds all Active Directory users created since 11AM UTC yesterday:
(&(objectCategory=person)(objectClass=user)(whenCreated>=20200617110000.0Z))
If you want to find accounts that have changed in the previous time delta, change whenCreated to whenChanged
Here is my powershell answer to the question. Not an LDAP filter, but it will get the info you want.
$1DayAgo = ((Get-Date).AddDays(-1)).Date
$UserProperties = "samaccountname", "givenName", "sn", "mail"
Get-ADUser -Filter {whenCreated -ge $1DayAgo} -Properties $UserProperties
Compare against whenCreated, using the date time format yyyyMMddHHmmss.fZ:
$since = (Get-Date).AddDays(-1).ToUniversalTime().ToString('yyyyMMddHHmmss.fZ')
Get-ADUser -LDAPFilter "(whenCreated>=$since)"

Use Search-ADAccount to find all accounts with Account Expiration Date Not Set

Is there a way in Powershell to list all AD Accounts in the domain which don't have an account expiration date set? ie the accounts expiration date is set to 'Never'.
I can utilise Get-ADuser to get the expiry of a single account.
get-aduser username | Select-Object -Property name,Enabled,AccountExpirationDate,DistinguishedName | Where-Object {$_.AccountExpirationDate -eq '`n'}
However is there a way to use "Search-ADAccount -AccountExpiring....." to get a list of all accounts in the domain which don't have an account expiration date set or a better way to do this?
When I use the below command I don't get any return information however I setup a test account with no Expiration Date (ie Never) set to test the command however the account is not displayed in the results.
Search-ADAccount -AccountExpiring | Select-Object -Property name,AccountExpirationDate | Where-Object {$_.AccountExpirationDate -eq '`n'}
Solution with Get-ADUser
You can still use Get-ADUser to find the accounts, if you use the correct Filter:
Get-ADUser -Filter {-not(AccountExpirationDate -like "*") } -Properties AccountExpirationDate | Select-Object -Property name,Enabled,AccountExpirationDate,DistinguishedName
This will give you every Account where no value is set for the Attribute AccountExpirationDate.
Solution with Search-ADAccount
If you still want to use Search-ADAccount you have to offer a timestamp until the Accounts should expire (take a look at the ref). There is no option to search for accounts that doesn't have any value set. You allways have to offer some kind of DateTime.
For example, if you want to find all Accounts that expire in 2 Weeks you have to options:
First:
A solution with a date on which the account expires:
Search-ADAccount -AccountExpiring -DateTime ((Get-Date).AddDays(14))
Second:
A solution on the remaining days until the account expires:
Search-ADAccount -AccountExpiring -Timespan "14"
I found a solution combining Paxz reply above and this Technet thread. The 'AccountExpirationDate' is too hard to filter out of AD effectively without exporting every users Account Expiration Date and formatting it to readable date-time string.
The Technet article linked above explains that "accountExpires AD attribute is a large integer (64-bit) representing dates (in UTC) as the number of 100-nanosecond intervals since 12:00 am January 1, 1601. The AccountExpirationDate parameter is a property method that converts the value of accountExpires into the corresponding date in the local time zone". From additional research when an account is set to Never expire it will have an 'accountexpires' integer of 9223372036854775807.
'Get-ADUser' can be used to filter for all accounts where the 'accountexpires' integer equals '9223372036854775807'. This avoids the need to convert the date for every account in the Domain.
Get-ADUser -filter {Enabled -eq 'True' -and accountexpires -eq '9223372036854775807'} | Select-Object name,enabled,accountexpires,distinguishedName

Contradictory values from Active Directory regarding password expiry date

I am using Powershell to determine the password expiry date for domain accounts. I have used the following command to get this information:
Get-ADUser -Filter {SamAccountName -eq "<username>"} -Properties "DisplayName" , "msDS-UserPasswordExpiryTimeComputed"
I then covert this value to a meaningful date by using:
[datetime]::FromFileTime(<computed filetime from above command>)
This works fine for all the domains I use, except one. In that domain I get a value of 9223372036854775807 as the msDS-UserPasswordExpiryTimeComputed. I am not able to use the FromFileTime function to convert this number into a date. It throws as error. Upon researching I have found that this number means that the password is set not to expire. However, I know that passwords do expire in this domain. Further, the PasswordNeverExpires property from the Get-ADUser cmdlet shows as False.
How can I get 9223372036854775807 from msDS-UserPasswordExpiryTimeComputed attribute and get a False from the PasswordNeverExpires property? This seems like a contradiction. What am I missing? Are there other situations when msDS-UserPasswordExpiryTimeComputed could be 9223372036854775807 too? Thanks.
The documentation lists several conditions in which msDS-UserPasswordExpiryTimeComputed returns 9223372036854775807 aka 0x7fffffffffffffff aka [int64]::MaxValue (TO refers to a given target object):
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 Effective-MaximumPasswordAge = 0x8000000000000000, then TO!msDS-UserPasswordExpiryTimeComputed = 0x7FFFFFFFFFFFFFFF (where Effective-MaximumPasswordAge is defined in [MS-SAMR] section 3.1.1.5).
Without knowing all the details, it seems that the msDS-UserPasswordExpiryTimeComputed property returning 0x7FFFFFFFFFFFFFFF indicates that there is effectively no password expiration, for a variety of reasons, only one of which is PasswordNeverExpires being set to $True.
Therefore, you could filter out such values:
Get-ADUser -Filter "SamAccountName -eq '<username>'" `
-Properties DisplayName, msDS-UserPasswordExpiryTimeComputed | #`
Where { $_.msDS-UserPasswordExpiryTimeComputed -ne 0x7FFFFFFFFFFFFFFF } | ForEach {
# ...
$dt = [datetime]::FromFileTime($_.msDS-UserPasswordExpiryTimeComputed)
}
It may even be possible to incorporate the test for 0x7FFFFFFFFFFFFFFF into the -Filter argument.
As an aside: Note that I've used a string rather than a script block ({ ... }) as the -Filter argument, because using script blocks is best avoided.
I don't have a DC at the moment to test but theoretically this can work.
Get the Password Policy of the domain with Get-ADDefaultDomainPasswordPolicy -Identity Domain.com
Get the passwordLastSet property of a user like this: (Get-ADUser -Identity <UserName> -Properties *).PasswordlastSet and convert to appropriate [datetime] object based on the domain's Culture: Ref Get-Culture. DateTime format may change based on regional settings like US and UK.
Now that you have the passwordpolicy of say 60 days and the passwordlastset date, it is simple math to determine if the password is expiring or not.
Alternately, on 2016 servers, you can also use something like:
Search-ADAccount -AccountExpiring -TimeSpan 10.00:00:00

Converting LastLogon to DateTime format

My objective is to get a list of users from my domain with the following info:
-Display name
-Country
-Manager Name
-Last login date
I am running the following script, and everything looks good except for the LastLogon. It outputs the time into a bunch of random numbers like "129948127853609000". How can I convert this to DateTime format?
Search-ADAccount -UsersOnly -SearchBase "OU=International,DC=mycompany,DC=com" -AccountDisabled:$false | Get-ADUser -Properties Name, manager, LastLogon | Select Name, manager, LastLogon | export-csv C:\Australia.csv -NoTypeInformation
DateTime.FromFileTime should do the trick:
PS C:\> [datetime]::FromFileTime(129948127853609000)
Monday, October 15, 2012 3:13:05 PM
Then depending on how you want to format it, check out standard and custom datetime format strings.
PS C:\> [datetime]::FromFileTime(129948127853609000).ToString('d MMMM')
15 October
PS C:\> [datetime]::FromFileTime(129948127853609000).ToString('g')
10/15/2012 3:13 PM
If you want to integrate this into your one-liner, change your select statement to this:
... | Select Name, manager, #{N='LastLogon'; E={[DateTime]::FromFileTime($_.LastLogon)}} | ...
Get-ADUser -Filter {Enabled -eq $true} -Properties Name,Manager,LastLogon |
Select-Object Name,Manager,#{n='LastLogon';e={[DateTime]::FromFileTime($_.LastLogon)}}
LastLogon is the last time that the user logged into whichever domain controller you happen to have been load balanced to at the moment that you ran the GET-ADUser cmdlet, and is not replicated across the domain. You really should use LastLogonTimestamp if you want the time the last user logged in to any domain controller in your domain.
Use the LastLogonDate property and you won't have to convert the date/time.
lastLogonTimestamp should equal to LastLogonDate when converted.
This way, you will get the last logon date and time across the domain without needing to convert the result.
It is important to know that LastLogonDate is not replicated. LastLogonTimestamp is so it is important that you use timestamp on large domains
While lastlogon is not replicated, lastlogontimestamp is. lastlogontimstamp is not human readable (milliseconds since windows epoch)
Here is a one liner that ACTUALLY solves it without querying every server.
get-adcomputer -filter 'operatingsystem -like "server" -and enabled -eq "true"' -prop lastlogontimestamp|select name, lastlogontimestamp, #{N='LastLogon'; E={[DateTime]::FromFileTime($_.LastLogontimestamp)}}