Script every AD user on every DC - powershell

I am beginner in scripting with powershell. My boss asked me to create a script that will get information about the last logon from every user in our domain on every DC. I have created the following script:
Import-Module ActiveDirectory
function Get-ADUsersLastLogon() {
$dcs = Get-ADDomainController -Filter { Name -like "*" }
$users = Get-ADUser -Filter * -Properties LastLogonDate | Where-Object { $_.LastLogonDate -le (Get-Date).AddDays(-30) }
$time = 0
$exportFilePath = "C:\output\aduser.csv"
$columns = "name,username,datetime,domain controller,enabled"
Out-File -filepath $exportFilePath -force -InputObject $columns
foreach ($user in $users) {
$Activeuser = $user.Enabled
foreach ($dc in $dcs) {
$hostname = $dc.HostName
$currentUser = Get-ADUser $user.SamAccountName | Get-ADObject -Server $hostname -Properties lastLogon
if ($currentUser.LastLogon -gt $time) {
$time = $currentUser.LastLogon
}
}
$dt = [DateTime]::FromFileTime($time)
$row = $user.Name + "," + $user.SamAccountName + "," + $dt + "," + $hostname + "," + $Activeuser
Out-File -filepath $exportFilePath -append -noclobber -InputObject $row
$time = 0
}
}
Get-ADUsersLastLogon
My boss asked me to change the following things in this script:
the output of the domain controller is only our DC in an other country. I want to know which DC the user last logged into.
The running of the script is taking too long. Like half a day. Is it possible to make this faster?
I hope someone can help me with this, I tried a few things, but I didn't work :(

the output of the domain controller is only our DC in an other country. I want to know which DC the user last logged into.
It looks like you've already solved this by querying each DC for each user object.
The running of the script is taking too long. Like half a day. Is it possible to make this faster?
That's because you're querying each DC for each user object :)
One way of speeding it up would be to flip your query logic around - query each DC for all the users but only once, and then update the logon timestamps only when a newer one is encountered.
For that to work, you need to keep track of the highest timestamp seen for each user. I'd suggest using a simple hashtable to index the user accounts by their user name:
$dcs = Get-ADDomainController -Filter { Name -like "*" }
# create hashtable to keep track of latest timestamps per user
$userLastLogonTable = #{}
foreach($dc in $dcs){
# fetch all users from each DC
Get-ADUser -Filter * -Properties LastLogonDate -Server $dc | ForEach-Object {
# Only add new timestamps to table if we either haven't seen the username before, or if the timestamp is newer than the current
if(-not $userLastLogonTable.Contains($_.SAMAccountName) -or $userLastLogonTable[$_.SAMAccountName].LastLogonDate -lt $_.LastLogonDate){
$userLastLogonTable[$_.SAMAccountName] = [pscustomobject]#{
LastLogonDate = $_.LastLogonDate
LogonServer = $dc.Name
}
}
}
}
# Now that we have a complete table of all users and their last logon timestamp,
# we can then easily identify usernames that have no recent logons
$staleUserNames = $userLastLogonTable.PSBase.Keys |Where-Object { $userLastLogonTable[$_].LastLogonDate -le (Get-Date).AddDays(-30) }
$staleUserNames now contain the user names of all user accounts that have not logged in for 30 days of more.

Related

Powershell script last logon from ad users last 30 days

Hi everyone can someone help me with the following script. the next things must be added:
Last logon date of the user
Export to excel .csv
Much appreciated
$dcs = Get-ADDomainController -Filter { Name -like "*" }
# create hashtable to keep track of latest timestamps per user
$userLastLogonTable = #{}
foreach($dc in $dcs){
# fetch all users from each DC
Get-ADUser -Filter * -Properties LastLogonDate -Server $dc | ForEach-Object {
# Only add new timestamps to table if we either haven't seen the username before, or if the timestamp is newer than the current
if(-not $userLastLogonTable.Contains($_.SAMAccountName) -or $userLastLogonTable[$_.SAMAccountName].LastLogonDate -lt $_.LastLogonDate){
$userLastLogonTable[$_.SAMAccountName] = [pscustomobject]#{
LastLogonDate = $_.LastLogonDate
LogonServer = $dc.Name
}
}
}
}
# Now that we have a complete table of all users and their last logon timestamp,
# we can then easily identify usernames that have no recent logons
$staleUserNames = $userLastLogonTable.PSBase.Keys |Where-Object { $userLastLogonTable[$_].LastLogonDate -le (Get-Date).AddDays(-30) }
Add the samaccountname value to the custom object:
$userLastLogonTable[$_.SAMAccountName] = [pscustomobject]#{
SAMAccountName = $_.SAMAccountName
LastLogonDate = $_.LastLogonDate
LogonServer = $dc.Name
}
Then filter on the values of the hashtable rather than the keys:
$staleUserEntries = $userLastLogonTable.PSBase.Values |Where-Object { $_.LastLogonDate -le (Get-Date).AddDays(-30) }
At which point you can export to CSV with Export-Csv:
$staleUserEntries |Select SAMAccountName,LastLogonDate |Export-Csv path\to\output.csv -NoTypeInformation

Is there a better way to get AD group members for a given list of AD group ObjectGUIDs?

I have a list of ObjectGUID's (49 of them) stored in a CSV, and I'm outputting a CSV which contains a listing of all members of those groups in the following format: "LastName, FirstName" , "AD Group Name"
I have it working (most of the time; sometimes I get IOException errors from the Add-Content cmdlt), but it is very slow (takes ~30 minutes for 8000 members). I don't have a lot of experience with Powershell , but I feel like there is a better/more efficient way of doing this. Any ideas on how to make this more efficient?
Note: I use the "-Server" switch because I actually have to run this script across a couple different domains, so the code I've pasted is duplicated twice in my original code, with a different CSV input, and a different domain passed to the "-Server" switch.
#read in CSV of ObjectGUIDs
$guids = Import-CSV .\ObjectGUIDs.csv
#loop through each AD group
foreach($group in $guids) {
$group_name = Get-ADObject -identity $group.objectGUID -server myDomain
$group_name = $group_name.Name
#get list of users in current group
$users = get-adgroupmember -server myDomain -identity
$group.ObjectGUID | where {$_.objectclass -eq 'user'}
#loop through each user of the current group
foreach ($user in $users) {
#get display name of current user
$display_name = get-aduser -identity $user.objectGUID -server
myDomain -properties DisplayName
#build the current row
$row = ('"' + $display_name.DisplayName + '"' + ',' + '"' +
$group_name + '"')
add-content -path "C:\Path\to\output.csv" -value $row
}
}
As I said above, this usually works, but takes a long time.
Gathering data in a [PSCustomObject] and using Export-Csv only once should be much more efficient.
## Q:\Test\2018\12\27\SO_53946702.ps1
$guids = Import-CSV .\ObjectGUIDs.csv
$CsvFile = "C:\Path\to\output.csv"
$Server = myDomain
$CsvData = foreach($group in $guids) {
$GroupName = (Get-ADObject -identity $group.objectGUID -server $Server).Name
#get list of users in current group
$users = Get-ADgroupMember -Server $Server -Identity $group.ObjectGUID |
Where-Object ObjectClass -eq 'user'
foreach ($user in $users) {
[PSCustomObject]#{
DisplayName = (Get-ADuser -Identity $user.objectGUID -server $Server -Properties DisplayName).DisplayName
GroupName = $GroupName
}
}
}
$CsvData | Export-Csv $CsvFile -NoTypeInformation

Get list of users from file for powershell loop

I'm trying to modify this PowerShell script to allow input of users from a text or CSV file. I've pulled the script directly from Tim Rhymer.
This is the original script:
Import-Module ActiveDirectory
function Get-ADUsersLastLogon() {
$dcs = Get-ADDomainController -Filter {Name -like "*"}
$users = Get-ADUser -Filter *
$time = 0
$exportFilePath = "c:lastLogon.csv"
$columns = "name,username,datetime"
Out-File -FilePath $exportFilePath -Force -InputObject $columns
foreach ($user in $users) {
foreach ($dc in $dcs) {
$hostname = $dc.HostName
$currentUser = Get-ADUser $user.SamAccountName | Get-ADObject -Server $hostname -Properties lastLogon
if ($currentUser.LastLogon -gt $time) {
$time = $currentUser.LastLogon
}
}
$dt = [DateTime]::FromFileTime($time)
$row = $user.Name + "," + $user.SamAccountName + "," + $dt
Out-File -FilePath $exportFilePath -Append -NoClobber -InputObject $row
$time = 0
}
}
Get-ADUsersLastLogon
I'm thinking I should modify the portion of the script the sets the variable of $user and how the foreach ($user in $users) loop processes each user, but can't figure it out.
As for this...
allow input of users from a text or CSV file.
… that is all that this is...
$users = Get-ADUser -Filter *
… You just replace that with your file
$users = Import-Csv -Path $UncToUserList.csv
Yet, by your question, since this is a really common, basic PowerShell thing, I am assuming you are very new to this and would advise really ramping up on PowerShell using all the freely available learning resources on TechNet MVA, MSChannel9 and YouTube, as well as freely available eBooks, as to avoid much confusion and frustration in your learning cycles as possible.
Also, you should never run any untrusted code, or code you do not fully understand. Use only in a test environment that you can fully recover from before ever considering production runs to avoid things that can cause you to have an RPE error --- 'Resume Producing Event'.

Powershell script extract AD group members - find last-login time for each user

I'm trying to create a script that will pull a list of group members from AD and run a foreach loop to determine when the last time each user logged into any given domain controller. I got some of the code for the measure-latest function here . I would like to have the script run through the foreach loop and print the samAccountName (username) and last login time stamp (measure-latest) for each user in the group, but so far have not been able to get it working. I think I've got something wrong in logic but i can't seem to figure it out. Any help is appreciated, thank you.
# Get a list of last login times for a group of users
# Script Requires Quest Cmdlet features: https://support.software.dell.com/activeroles-server/download-new-releases
Add-PSSnapin Quest.ActiveRoles.ADManagement
# filter out $nulls and produce the latest of them
function Measure-Latest {
BEGIN { $latest = $null }
PROCESS {
if (($_ -ne $null) -and (($latest -eq $null) -or ($_ -gt $latest))) {
$latest = $_
}
}
END { $latest }
}
# Get list of group users by username
Get-ADGroupMember -identity "Domain Admins" | select samAccountName | Export-csv -path C:\Scripts\UserInformationByGroup\Groupmembers.csv -NoTypeInformation
# Get list of users from group, assign user value
$userlist = import-csv C:\Scripts\UserInformationByGroup\Groupmembers.csv
$user = $userlist | Select samAccountName
# Loop through list of users and print Username ------ Last Login time
foreach ($user in $userlist) {
Get-QADComputer -ComputerRole DomainController | foreach {
(Get-QADUser -Service $_.Name -SamAccountName $user).LastLogon
} | Measure-Latest $samAccountName | out-file -filepath C:\Scripts\UserInformationByGroup\userListLastLogin.txt -append
}
I should mention that when I run the script like this, and just enter each username manually it works and prints the last login time:
Add-PSSnapin Quest.ActiveRoles.ADManagement
function Measure-Latest {
BEGIN { $latest = $null }
PROCESS {
if (($_ -ne $null) -and (($latest -eq $null) -or ($_ -gt $latest))) {
$latest = $_
}
}
END { $latest }
}
Get-QADComputer -ComputerRole DomainController | foreach {
(Get-QADUser -Service $_.Name -SamAccountName USER_NAME_HERE).LastLogon
} | Measure-Latest
This part of the pipeline makes no sense:
| Measure-Latest $samAccountName |
Since nothing is assigned to the variable $samAccountName
You'll need to add pipeline support to your Measure-Latest function, like so:
function Measure-Latest {
[CmdletBinding]
param(
[parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
$LastLogon
)
# rest of the script (begin-process-end blocks) goes here
}
And now you can pipe the LastLogon values directly to the function:
(Get-QADUser -SamAccountName $user).LastLogon |Measure-Latest | Out-File #or whatever
Thanks to the ValueFromPipelineByPropertyName flag, you can now also pipe the entire object to the function:
Get-QADUser -SamAccountName $user | Measure-Latest
Because the parameter name ($LastLogon) matches that property anyways
In addition to that, I would probably change the logic a bit, so that you don't perform 1 LDAP query per DC per user. If you have 5 Domain Controllers and 200 users, that's now 1000 individual queries to the directory service.
You could simply retrieve ALL the users from each DC, in a single query, using the -LdapFilter parameter with Get-QADUser:
$LDAPClauses = #()
foreach($user in $userlist){
$LDAPClauses += "(samaccountname={0})" -f $user
}
# The | in an LDAP filter means logical OR
$LDAPFilter = "(&(LastLogon=*)(|$(-join($LDAPClauses))))"
Now you can run just a single query per DC:
Get-QADUser -LdapFilter $LDAPFilter
and it'll retrieve all the users in $userlist that has a LastLogon attribute value on that specific DC (you're not really interested in $null-values anyways, right?)

Search for and disable users that have not logged on for x days

I've tried to create a new powershell script that doesn't seem to be working for me. What I want it to do is to find all users in a specific Organizational Unit and then see if any of those users have not logged in for the last x number of days. After that I want each of those users to be disabled. I have not built the entire script yet, because I'm stuck at calculating whether or not a user has been logged in at all.
The script I have so far looks like this
import-module ActiveDirectory
$numdays = read-host 'provide the number of days user has not logged in'
$OUN = read-host 'provide the Organizational Unit name'
$nu = get-date -format dd-MM-yyyy-HH-mm-ss
$logfile = ".\$nu.txt"
$recent = #()
$oud = #()
$currentdate = [System.DateTime]::Now
$currentdateUTC = $currentdate.ToUniversalTime()
$lltstamplimit = $currentdateUTC.AddDays(- $numdays)
$lltintlimit = $lltstamplimit.ToFileTime()
$Search = "TOPLEVEL DOMAIN"
$ou = (Get-ADOrganizationalUnit -Ldapfilter '(name=*)' -searchbase $search -searchscope Onelevel | where { $_.name -like $OUN }).Distinguishedname
$users = get-aduser -filter * -searchbase "$ou" -properties * | where { $_.enabled -like "True" } | select SamAccountName, LastLogonDate
$convert = foreach ($i in $users.lastlogondate) { [datetime]::FromFileTime($i).ToString('g') }
#$array = foreach ( $i in $users ) {
#if ( $users.Lastlogondate -ge $lltintlimit ) { $recent += $i } else { $oud += $i}
As you might be able to guess the script fails at the convert part. Everything after is comment, I have not really done anything with that yet. Anyway I want to convert the output to a normal date system so I can calculate whether or not the lastlogondate is before or after the period I gave earlier.
Do you have any idea how I can fix this, or an alternative to this that might work as well?
There are some limitations to filtering AD accounts using the LastLogonDate property, mainly that LastLogOnDate is not always updated across all DCs, Technet article explains in more detail.
$inActiveUsers = Search-ADAccount -AccountInactive -TimeSpan "90" -UsersOnly
$inActiveUsers | foreach { Disable-ADAccount -Identity $_ }
The online help for the Search-ADAccount can be found here.