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
Related
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
I'm trying to get the password expiration date in active directory using powershell for users with hyphenated names (IE firstname.last-name) and on the hyphenated names it gives an invalid cmdlet error. How do I query the hyphenated names?
The current command I have is
net user $username /DOMAIN | find "Password expires"
Maybe use the ActiveDirectory module instead of the net commands:
$MaxPwdAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.Days
Get-ADUser -Filter { Name -like "*-*" } -Properties 'PasswordLastSet', 'DisplayName' |
Select-Object Name,DisplayName,
#{ Name = 'PasswordExpires'; Expression = { $_.PasswordLastSet.AddDays( $MaxPwdAge ) } }
If needed You can change the filter to look at DisplayName instead -Filter { DisplayName -like "*-*" }
You may need to adjust the properties you're retrieving depending on what you want to include in the output. This is just an example but it works, and can be used to plot a path forward. It does seem like you have to calculate the expiration date. But I can work on that and see if there's a better way.
If you want to Query for a specific user:
Get-ADUser Name-Name -Properties 'PasswordLastSet',DisplayName |
Select-Object Name,DisplayName,
#{ Name = 'PasswordExpires'; Expression = { $_.PasswordLastSet.AddDays( $MaxPwdAge ) } }
This assumes the Hyphenated name is a samAccountName. If you need to search by DisplayName you'll have to resort back to filter, even if you are looking for only the one user.
Get-ADUser -Filter { DisplayName -eq "Name-Name" } -Properties 'PasswordLastSet',DisplayName |
Select-Object Name,DisplayName,
#{ Name = 'PasswordExpires'; Expression = { $_.PasswordLastSet.AddDays( $MaxPwdAge ) } }
Note: That you have to change the "Name-Name". Also in the last example I changed to using the -eq operator instead of -like. Obviously this assumes you know exactly what you're looking for. Though you can use -Like with DisplayName or even the surName attribute if you like.
$daysOld = "-90"
$currentDate = get-date
$removeIfBefore = $currentDate.AddDays($daysOld)
$vpnuserstest = Get-ADGroupMember VPN_users -Recursive | select samaccountname | foreach ($_.samaccountname) {
Get-ADUser $_.samaccountname -Properties samaccountname, whenCreated |
Select-Object samaccountname,#{n='Days Since Created';e={($((Get-Date)) - $($_.WhenCreated)).Days}} |
Format-table -AutoSize}
Detailed Description:
I am taking the users in the group VPN_users and searching for their "samaccountname" and "whenCreated" properties. Then I would like to take today's date and go back 90 days. Anyone who's "whenCreated" date falls within that 90 day window, I want to add to a table so that I can export it later.
When I run the above code, I get everything listed as I would like, but it still includes everyone who's "whenCreated" property is above 90 days.
Sorry if the code looks "frankenstein'd" together....because it is. I took different aspects from different Google searches and threw them together.
Here's one way: Construct a timestamp-string and use with the -LDAPFilter parameter. Example:
$daysOld = 90
$timestampUTC = (Get-Date).AddDays(-$daysOld).ToUniversalTime()
$timestampString = "{0:yyyyMMddHHmmss.0Z}" -f $timestampUTC
Get-ADUser -Properties whenCreated -LDAPFilter "(whenCreated<=$timestampString)"
If you want to limit results to users that are a member of a particular group, you can update the LDAP query filter. Example:
$daysOld = 90
$timestampUTC = (Get-Date).AddDays(-$daysOld).ToUniversalTime()
$timestampString = "{0:yyyyMMddHHmmss.0Z}" -f $timestampUTC
Get-ADUser -Properties whenCreated -LDAPFilter "(&(whenCreated<=$timestampString)(memberOf=CN=Group Name,OU=Container,DC=fabrikam,DC=com))"
I'm taking a list of email addresses and future leave dates from a database and trying to populate the account expiry date in AD.
$ds contains email and leave date.
foreach ($mail in $ds[0].rows) {
$ms = $mail.mail;
Get-ADUser -Filter {(emailaddress -eq $ms) -And (Enabled -eq $True)} |
Set-ADAccountExpiration -Datetime $mail.leavedate
}
The Get-ADUser part is working fine but the problem is the $mail.leavedate is not available to the command, only the results from Get-ADUser.
I've tried using $_$mail.leavedate but this passes through empty results.
Perhaps I need to create a new object with the result from Get-ADUser and the leave date, I'm just not sure how to do that or how to then use a new object with the Set-ADAccountExpiration command.
I'm trying to go through a list of users I have and would like to get a few properties (DisplayName, Office) to show in a table then convert the table to a .csv.
I've been working with:
$Users = gc "C:\scripts\Users.txt
foreach ($User in $Users) {
Get-ADUser -Identity $User -Properties DisplayName,Office
}
And that's fine, I can combine it with "select DisplayName,Office" but if I do an "Out-File -append" it just looks terrible. I think I should do this with an array or hash table but I've been reading up on them and don't really understand how I would automate one that can be exported. Thanks for any help you can provide.
Query all users and filter by the list from your text file:
$Users = Get-Content 'C:\scripts\Users.txt'
Get-ADUser -Filter '*' -Properties DisplayName,Office |
Where-Object { $Users -contains $_.SamAccountName } |
Select-Object DisplayName, Office |
Export-Csv 'C:\path\to\your.csv' -NoType
Get-ADUser -Filter '*' returns all AD user accounts. This stream of user objects is then piped into a Where-Object filter, which checks for each object if its SamAccountName property is contained in the user list from your input file ($Users). Only objects with a matching account name are passed forward to the next step of the pipeline. The output can be limited by selecting the relevant properties before exporting the data.
You can further optimize the code by replacing the -contains operator with hashtable lookups:
$Users = #{}
Get-Content 'C:\scripts\Users.txt' | ForEach-Object { $Users[$_] = $true }
Get-ADUser -Filter '*' -Properties DisplayName,Office |
Where-Object { $Users.ContainsKey($_.SamAccountName) } |
Select-Object DisplayName, Office |
Export-Csv 'C:\path\to\your.csv' -NoType
This can be simplified by completely skipping the where object and the $users declaration. All you need is:
Code
get-content c:\scripts\users.txt | get-aduser -properties * | select displayname, office | export-csv c:\path\to\your.csv
#AnsgarWiechers - it's not my experience that querying everything and then pruning the result is more efficient when you're doing a targeted search of known accounts. Although, yes, it is also more efficient to select just the properties you need to return.
The below examples are based on a domain in the range of 20,000 account objects.
measure-command {Get-ADUser -Filter '*' -Properties DisplayName,st }
...
Seconds : 16
Milliseconds : 208
measure-command {$userlist | get-aduser -Properties DisplayName,st}
...
Seconds : 3
Milliseconds : 496
In the second example, $userlist contains 368 account names (just strings, not pre-fetched account objects).
Note that if I include the where clause per your suggestion to prune to the actually desired results, it's even more expensive.
measure-command {Get-ADUser -Filter '*' -Properties DisplayName,st |where {$userlist -Contains $_.samaccountname } }
...
Seconds : 17
Milliseconds : 876
Indexed attributes seem to have similar performance (I tried just returning displayName).
Even if I return all user account properties in my set, it's more efficient. (Adding a select statement to the below brings it down by a half-second).
measure-command {$userlist | get-aduser -Properties *}
...
Seconds : 12
Milliseconds : 75
I can't find a good document that was written in ye olde days about AD queries to link to, but you're hitting every account in your search scope to return the properties. This discusses the basics of doing effective AD queries - scoping and filtering: https://msdn.microsoft.com/en-us/library/ms808539.aspx#efficientadapps_topic01
When your search scope is "*", you're still building a (big) list of the objects and iterating through each one. An LDAP search filter is always more efficient to build the list first (or a narrow search base, which is again building a smaller list to query).