powershell to get exact date for passwordlastset - powershell

I'm working on scripts which send emails to users 14, 10, 7, 3 days before password expiring.
Password expires in 60 days.
If I set like below it works for accounts with 3 and less days to expiring. I don't want accounts with 2 days, 1 day, etc.
$ExpiringUsers = $AdUsers | where PasswordLastSet -lt (get-date).AddDays(-57)
If I set like below it doesn't work at all
$ExpiringUsers = $AdUsers | where PasswordLastSet -eq (get-date).AddDays(-57)
How to set equal 3 days not more not less.
Thanks!

You need to define and filter by a time range, for example, set equal 3 days not more not less would be like this:
$start = [datetime].AddDays(-3) # => April 2 at 12:00 AM
$finish = [datetime].AddDays(-2) # => April 3 at 12:00 AM
# Note `-lt $finish` instead of `-le $finish`
$AdUsers | Where-Object { $_.PasswordLastSet -ge $start -and $_.PasswordLastSet -lt $finish }

A better approach is to use msDS-UserPasswordExpiryTimeComputed for checking how long until the account credential expires:
$serviceAccounts =
'acct1#domain.tld',
'acct2#domain.tld
$daysLeft = 20
$expiringAccounts = ( $serviceAccounts | Foreach-Object {
Get-AdUser -Filter "(UserPrincipalName -eq '$_' -Or sAMAccountName -eq '$_' ) -And ( PasswordNeverExpires -eq 'false' )" -Properties msDS-UserPasswordExpiryTimeComputed |
Where-Object { ( New-TimeSpan -Start $currentDate -End ( [DateTime]::FromFileTime( $_.'msDS-UserPasswordExpiryTimeComputed' ) ) ).Days -le $daysLeft }
} )
As written, this code will gather the accounts expiring within 20 days. Adjust $daysLeft to control the remaining threshold until expiry.
Note that [DateTime]::FromFileTime is required to transpose the value ofmsDS-UserPasswordExpiryTimeComputed from the file-time format it is stored in AD as to a workable DateTime object.
You can define the account as a sAMAccountName or in Universal Principal Name (UPN) format.
This also exemplifies using AD filters in order to have AD return only the objects you need for this query, minimizing the local processing performed with Where-Object.
The notable exception is msDS-UserPasswordExpiryTimeComputed; because it is a Constructed Attribute within Active Directory, neither AD filters or LDAP filters can evaluate it and so this must be done via additional local processing.
Here is an answer I have also written which goes into deeper detail on using the AD filter syntax.
What is wrong with PasswordLastSet for checking credential expiration if you know the account's lifecycle?
The problem with checking PasswordLastSet is that, while it works, if the threshold until expiry changes on the Active Directory side, your script will need to be updated or it will incorrectly identify accounts whose credential is set to expire soon. It also becomes more difficult to track which accounts are beholden to different lifecycles in environments where not all accounts are beholden to the same security policies.

Related

Exporting last logon date for inactive users via PowerShell

I have a command that will export a list of users who have logged in for 12 months but I am struggling to export the last login date and time.
The command is as follows:
Search-ADAccount –AccountInActive -UsersOnly –TimeSpan 365:00:00:00 –ResultPageSize 2000 –ResultSetSize $null |?{$_.Enabled –eq $True} | Select-Object Name, SamAccountName, DistinguishedName, lastLogon| Export-CSV “C:\Users\Me\Desktop\InactiveUsers.CSV” –NoTypeInformation
But lastLogon is showing a blank in the CSV file.
I am new to PowerShell I understand the command can be made much smoother.
Any help on this is much appreciated.
Search-ADAccount doesn't have an option to pull other attributes from the AD Objects than the default ones, you can use Get-ADUser with an elaborate filter to query the users who haven't logged on for the past year. One option is to query the user's lastLogonTimeStamp attribute however by doing so you're risking not getting accurate results because this attribute is not replicated in real time. To get accurate one must query the user's lastLogon attribute but, since this attribute is not replicated across the Domain, one must query all Domain Controllers to get the latest logon from the user.
For more information on this topic, please check this excellent TechNet Article: Understanding the AD Account attributes - LastLogon, LastLogonTimeStamp and LastLogonDate.
$dateLimit = [datetime]::UtcNow.AddYears(-1).ToFileTimeUtc()
$AllDCs = Get-ADDomainController -Filter *
$logons = #{}
$params = #{
LDAPFilter = -join #(
"(&" # AND, all conditions must be met
"(!samAccountName=krbtgt)" # exclude krbtgt from this query
"(!samAccountName=Guest)" # exclude Guest from this query
"(userAccountControl:1.2.840.113556.1.4.803:=2)" # object is Disabled
"(lastLogon<=$dateLimit)" # lastLogon is below the limit
")" # close AND clause
)
Properties = 'lastLogon'
}
foreach($DC in $AllDCs) {
$params['Server'] = $DC
foreach($user in Get-ADUser #params) {
# this condition is always met on first loop iteration due to ldap filtering condition
if($logons[$user.samAccountName].LastLogon -lt $user.LastLogon) {
$logons[$user.samAccountName] = $user
}
}
}
$logons.Values | ForEach-Object {
[PSCustomObject]#{
Name = $_.Name
SamAccountName = $_.SamAccountName
DistinguishedName = $_.DistinguishedName
lastLogon = [datetime]::FromFileTimeUtc($_.lastLogon).ToString('u')
}
} | Export-CSV "C:\Users\Me\Desktop\InactiveUsers.CSV" -NoTypeInformation

Powershell Query does not calculate correct Date

HelloThere,
we get AD-Users from an import script that runs daily and if a user was found in a csv we write the current date in the extensionattribute1 in format "dd-MM-yyyy".
Now we want to calculate if a AD user has a different date in the extensionattribute1 (minus 90 days) then we want to inform their managers what should happen to those accounts.
Interesting is, with my query we get correct Users and some Users between 30 and 90 Days. I don't know what is wrong in my code or with the syntax - can someone pls help? Thanks
sorry, i am not a PowerShell Pro by now but working on it :D
Greetings
# get date -90 Days
$date = (Get-Date).AddDays(-90).ToString("dd-MM-yyyy")
$date
# it gets "27-08-2022" -> so the script should only show users less than this date
# query for extensionattribute1 less than 90 days
get-aduser -properties ExtensionAttribute1 -filter {(enabled -eq "true") -and (extensionAttribute1 -gt $date)}
# here the output is partial correct, we get users with an extensionattribute1 from
# 31-05-2022
# 30-06-2022
# 29-07-2022
# but also some Users with a date from between 30 and 90 days what is incorrect =/
# 30-09-2022
# 31-10-2022
You should not compare date strings against eachother, especially not when you use date format "dd-MM-yyyy", since that is a non-sortable date format. (you might get away with it if you would have used format 'yyyy-MM-dd'..)
Instead, convert the value found in the ExtensionAttribute1 to a DateTime object first and compare that to a real reference DateTime object.
Try
# get date -90 Days
$date = (Get-Date).AddDays(-90).Date # set to midnight with time part '00:00:00'
# query for extensionattribute1 less than 90 days
Get-ADUser -Properties ExtensionAttribute1 -Filter 'Enabled -eq $true -and ExtensionAttribute1 -like "*"' |
Where-Object {[datetime]::ParseExact($_.ExtensionAttribute1, 'dd-MM-yyyy', $null) -gt $date}

Get the Last Logon Time for Each User Profile

I need to get the last logon time for each user profile on a remote computer (not the local accounts). I've tried something similar to the following using Win32_UserProfile & LastLogonTime, however this is not giving accurate results. For example, one this computer, only 1 account has been used in the past year, however LastUpdateTime is showing very recent dates. Some accounts have not even been logged into and should say "N/A", but it doesn't.
$RemoteSB_UserADID = Get-WmiObject win32_userprofile -Property * | Where-Object {$_.LocalPath -like "*users*"} | Sort-Object $_.LastUseTime | ForEach-Object{
$Parts = $_.LocalPath.Split("\")
$ADID = $Parts[$Parts.Length - 1]
if ($ADID -ne "SPECIALPURPOSEACCOUNT1" -and $ADID -ne "SPECIALPURPOSEACCOUNT2"){
$Time = $null
try{
$Time = $_.ConvertToDateTime($_.LastUseTime)
}catch{
$Time = "N/A"
}
"[$ADID | $Time]"
}
}
Example Output
[Acct1 | 03/13/2022 07:18:19]
[Acct2 | 03/15/202214:59:16]
[Acct3 | 03/13/2022 07:18:19]
[Acct4 | 03/16/2022 11:53:17] <--- only "active" account
How can I go about retrieving accurate (or decently accurate) login times for each user profile? Thanks!
It would help to know for what reason you need that, so that I know how to find a (better) solution for you.
If you need to cleanup your profiles not used for a long time at the target system, then take the last changed date of "ntuser.dat". That is the last logon if you define logon like logging on to a new session. If the user was logged on and simply locked the computer or used standby and then relogs then this date won't change.
Use this to get this date from all users you have access to but possibly not getting real user names
Get-ChildItem \\REMOTECOMPUTERNAMEHERE\Users\*\ntuser.dat -Attributes Hidden,Archive | Select #{Name="NameByFolder";Expression={($_.DirectoryName -split "\\")[-1]}},LastWriteTime
Or this a bit more complex version
Invoke-Command -ComputerName REMOTECOMPUTERNAMEHERE -ScriptBlock {$UsersWithProfilePath = #{}
dir "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" |
where {$_.name -like "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-*"} |
foreach {$UsersWithProfilePath[([System.Security.Principal.SecurityIdentifier]$_.name.split("\")[-1]).Translate( [System.Security.Principal.NTAccount]).Value] = $_.GetValue("ProfileImagePath")}
foreach ($Name in $UsersWithProfilePath.Keys) {#{$Name =(dir (join-path $UsersWithProfilePath.$Name ntuser.dat) -Attributes Hidden,Archive,System).LastWriteTime}}}
Depending on what you need you need to change it a bit.
Sorry for the long codelines... it is late here.

powershell get-aduser specific date range

Hello and thank you for taking the time to read this.
I am writing a program that will look back at a certain date range and then return the values that I specify in a get-aduser cmdlet
The code I have thus far is here:
$grabDate = Read-Host 'enter how many days back you want to search'
$date = Get-Date
$desiredDate = $date.AddDays(- $grabDate)
Write-Host $desiredDate 'to' $date 'is your search range'
Pause
Get-ADUser -Filter * -Properties Name, LastLogonDate | Where-Object { $date.AddDays(- $grabDate) } | Select-Object name, LastLogonDate
I know that this is not the cleanest code and there are some redundant steps my main focus is on the line:
Get-ADUser -Filter * -Properties Name, LastLogonDate | Where-Object { $date.AddDays(- $grabDate) } | Select-Object name, LastLogonDate
when I enter 30 days to search back, I am getting strange entries from 2016, does anyone see anything strange with my code?
No need for the pipeline here - just use a little simple math, a conversion from string to int, and use the -Filter parameter as it was designed.
Set $grabDate like this, so you get an actual int value, not a string
# Convert Read-Host input to an integer, and multiply by -1 to get the negative value
$grabDate = [Convert]::ToInt32( (Read-Host 'enter how many days back you want to search' ) ) * -1
Then call Get-ADUser with the following -Filter parameter:
# Use the filter to return only the users who haven't logged on in the
# last $grabDate days
Get-ADUser -Filter "LastLogonDate -ge '$((Get-Date).AddDays( $grabDate ))'"
This way, you only return the ADUsers you care about and you don't have to process the list of users a second time. Using -Filter * can be a costly operation, especially in larger AD environments.
Import-Module ActiveDirectory
# Set the number of days since last logon
$DaysInactive = 90
$InactiveDate = (Get-Date).Adddays(-($DaysInactive))
#-------------------------------
# FIND INACTIVE USERS
#-------------------------------
# Below are four options to find inactive users. Select the one that is most appropriate for your requirements:
# Get AD Users that haven't logged on in xx days
$Users = Get-ADUser -Filter { LastLogonDate -lt $InactiveDate -and Enabled -eq $true } -Properties LastLogonDate | Select-Object #{ Name="Username"; Expression={$_.SamAccountName} }, Name, LastLogonDate, DistinguishedName

Using PowerShell to generate a unique username in AD

I'm writing a script to be used by help desk staff to quickly (and accurately) create AD user accounts by inputting basic employee information. We use portions of a persons name in order to create the samAccountName. Which I've achieve by the following.
$GivenName = "Joe"
$Surname = "Smith"
$SamAccountName = $Surname.substring(0, [System.Math]::Min(5, $Surname.Length)) + $GivenName.Substring(0,1)
This works great as long as there isn't already a "Joe Smith" (or John or Jennifer Smithers, etc). The solution would be to add a number to the end. When manually creating accounts the help desk would search AD look at what number suffix to use if necessary. I'm trying to figure out how PowerShell can do that for us. I've gone through several ideas with help from what I've found online but so far I've been unsuccessful.
My first thought was to do something like this.
$SamSuffix = 2
If ((Get-ADUser -LDAPFilter "(SamAccountName=$samAccountName)")-eq $Null)
{
"$SamAccountName does not exist in AD" #out result for testing.
}
Else{
do
{
Get-ADUser -LDAPFilter "(SamAccountName=$SamAccountName + $SamSuffix++)"
}
until (Get-ADUser -LDAPFilter "(SamAccountName=$SamAccountName + $SamSuffix)")-eq $Null)
}
This obviously doesn't work. Even if it did I don't know how I'd get to the 'next step' to create the account.
I also tried pulling the existing names into a list
$SamExist = (Get-ADUser -LDAPFilter "(SamAccountName=$SamAccountName*)" | Select SamAccountName)
do {$SamAccountName + $SamSuffix++}
until ($SamExist -notcontains $SamAccountName -or $SamAccountName + $SamSuffix)
This also doesn't work but if it did I can see that it would automatically add the suffix even if it wasn't needed.
You approach where you get all the existing matches first would be where I would start. Lets assume $SamAccountName is smithj
$existingAccounts = Get-ADUser -Filter "samaccountname -like '$SamAccountName*'" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty samaccountname
So $existingaccounts that have a samaccountname's starting with smithj. If there are not any then $existingAccounts would be null and we can test for that.
if($existingAccounts){
# Figure out what the suffix will be
$highestValue = $existingAccounts -replace "^$SamAccountName" |
ForEach-Object{[int]$_} |
Measure-Object -Maximum |
Select-Object -ExpandProperty Maximum
} else {
# Create the user as normal.
}
Pretending there are some accounts that exist we trim the leading characters from the samaccountname, convert the remaining to an integer and select the highest from that. So $highestValue is the last number used in a conflicting account.
Add one to that and you have a guaranteed username you can create assuming nothing changes in those moments i.e. two users making to smithj accounts.
If you are looking to fill gaps where a user might have left and you want to use the next available after 1 then you could do this.
$existingAccounts = "smithj1", "smithj5", "smithj10", "smithj2", "smithj3"
# Figure out what the next unused suffix will be
$existingSuffixes = $existingAccounts -replace "^$SamAccountName" | ForEach-Object{[int]$_}
# Once again determine the maximum one in use
$highestValue = $existingSuffixes | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum
# Find the first gap between 1 and the max suffix
$nextAvailableSuffix = 1..($highestValue + 1) | Where-Object{$existingSuffixes -notcontains $_} | Sort-Object desc | Select -First 1
$nextAvailableSuffix would contain 4 using the above example. We add 1 to highest value in case the only other one is 2 so that way there will only be an answer to $nextAvailableSuffix