Powershell script last logon from ad users last 30 days - powershell

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

Related

ExportTo-CSV does not work properly in PS

I'm in the end of creating a simple PS script.
It basically take specific AD user, check which users are directly reports to him and filter these users by prefix EXT then it listing Expiration Date of account and name of these accounts.
I found it difficult to Import these data to CSV file.
Yes, it works but results are more far that what I expect. I should get file filled with Account Names and expiration date of these accounts, instead I got only this:
results
Here is my script
$external_users_data = #{}
$if_ext_user = Get-AdUser -Identity xxxx.xxxx -Properties DirectReports | Select -ExpandProperty DirectReports
ForEach ($user in $if_ext_user)
{
$U = Get-AdUser -identity $user -Property *
if ($U.UserPrincipalName -like 'EXT.*'){
Write-Output $U.UserPrincipalName
Write-Output $U.AccountExpirationDate
<#$user_object = #{
AccountName = $U.UserPrincipalName
ExpirationDate = $U.AccountExpirationDate
}#>
$external_users_data.add($U.UserPrincipalName,$U.AccountExpirationDate)}}
Write-Host $external_users_data
[ExtAccount_expiration_dates]#{AccountName = $U.UserPrincipalName; ExpirationDate = $U.AccountExpirationDate} |Export-Csv -Append -Path 'C:xxxxxxxxx/csv'
for security reasons, I blurred sensitive lines with name of account and file path.
Any ideas are much appreciated.
I'm expecting that final CSV file will be filled with proper data like AccountName and ExpirationDate of that account.
PSCustomObject available from PS V3.0 or newer. You can use it for better results. For more info please refer PSCustomObject Doc
I have no environment to test this code. So, please test it from your end. Hope it works.
$users = Get-AdUser -Identity xxxx.xxxx -Properties DirectReports | Select-Object -ExpandProperty DirectReports
$external_users_data = ForEach ($user in $users) {
$U = Get-AdUser -identity $user -Property *
if ($U.UserPrincipalName -like 'EXT.*') {
[PSCustomObject]#{
"AccountName" = $U.UserPrincipalName
"ExpirationDate" = $U.AccountExpirationDate
}
}
}
$external_users_data | Export-Csv -Append -Path 'C:xxxxxxxxx/csv'

Script every AD user on every DC

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.

Getting computername and the Canonical and distinguished OU name from AD

I have a list of servers where I need to get the OU, I have put together a script that will do this.
Get-ADOrganizationalUnit
-Identity $(($adComputer = Get-ADComputer -Identity $env:COMPUTERNAME).DistinguishedName.SubString($adComputer.DistinguishedName.IndexOf("OU=")))
The issue here is that the OU name is hard to read and not easy on the eye, so I figured out that what i need is the CanonicalName. However here is the problem.
I have come up with the snippet below.
Get-ADOrganizationalUnit -Filter * -properties CanonicalName, DistinguishedName
| select-Object CanonicalName,DistinguishedName
The problem with the above is that it gets everything in AD, I need to be able to filter by servername so that when I load up the server list in a file, I can use a foreach loop to get a report of the servername and the OU, I have tried to use the -Server filter to no avail, as I believe that is for the AD server.
During my research I found, PowerShell filter by OU. In my test environment, it has been running for hours with no results back.
The snippet below will return groups, I cannot get servername filter to work.
Get-ADOrganizationalUnit -Properties CanonicalName -Filter *
| Sort-Object CanonicalName
| ForEach-Object { [pscustomobject]# {
Name = Split-Path $_.CanonicalName -Leaf
CanonicalName = $_.CanonicalName
UserCount = #(Get-AdUser -Filter * -SearchBase $_.DistinguishedName -SearchScope OneLevel).Count
}
}
This is how I would do it:
First query all the computers from your list. Here I'm assuming the computer list is coming from a CSV and the computers are on a column named Computers.
Get the computer's Organizational Unit by removing the Common Name from their DistinguishedName: (.... -split '(?=OU=)',2)[-1]
Add the computers objects to a Hash Table where the Keys are the computer's OUs. This will let us query each OU only once.
Loop over the Hash Table keys (OU's DistinguishedName) querying their CanonicalName.
Create a new object for each computer with the desired properties.
Export the result to a Csv.
# If it's a txt file instead:
# $computers = Get-Content path/to/computers.txt
$csv = Import-Csv path/to/csv.csv
$map = #{}
# If it's a txt file, instead:
# foreach($computer in $computers)
foreach($computer in $csv.Computers)
{
try
{
$adComputer = Get-ADComputer $computer
$ou = ($adComputer.DistinguishedName -split '(?=OU=)',2)[-1]
if($val = $map[$ou]) {
$map[$ou] = $val + $adComputer
continue
}
$map[$ou] = , $adComputer
}
catch
{
Write-Warning $_.Exception.Message
}
}
$result = foreach($ou in $map.Keys)
{
$params = #{
Identity = $ou
Properties = 'canonicalName'
}
try
{
$canonical = Get-ADOrganizationalUnit #params
foreach($computer in $map[$ou])
{
[pscustomobject]#{
'Computer Name' = $computer.Name
'OU DistinguishedName' = $ou
'OU CanonicalName' = $canonical.CanonicalName
}
}
}
catch
{
Write-Warning $_.Exception.Message
}
}
$result | Export-Csv .... -NoTypeInformation
Would something like this work? Filter on a computer name or a wildcard pattern, then get the OU name and OU canonical name using the computer's DistinguishedName and CanonicalName properties:
$filter = "Name -eq 'SERVER01'" # exact name
# OR
$filter = "Name -like 'SERVER*'" # anything that starts with SERVER
Get-ADComputer -Filter $filter -Properties canonicalname |
select name,
#{ name = 'OU' ; expression = { [void]($_.DistinguishedName -match 'OU=.+$'); $Matches[0] } },
#{ name = 'OU Canonical' ; expression = { $_.CanonicalName -replace "/$($_.Name)" } }

Compare list of computer from text file with AD-User property

I new in Powershell world and trying to write a script which perform following:
Get List of Computers from a text file
Get list of Users from a text file
Control if the computer name is added in LogonWorkstations field in each user account
Here is the script I have written as of yet.
$Computers = Get-Content Computers.txt
$Users = Get-Content -Path Users.txt | Sort-Object -Unique
$ADUsers = Get-ADUser -Filter * -Properties LogonWorkstations -SearchScope Subtree -SearchBase "OU=ck,OU=users-com,DC=domain,DC=com" |
Where-Object {$Users -contains $_.Name} | Format-List Name,LogonWorkstations
As the script shows I read and retrieve property for Users and have list of computers in text file.
There are 50+ computers and users my question is how can I compare this line wise example check if computer from line 1 of Computers.txt exist in LogonWorkstations property of user from line 1 of Users.txt?
If each line of both files are corresponding, you can use a simple for loop to iterate through both lists simultaneously. $ADUsers will contain the output of ADUser objects matching the conditions.
$ADUsers = for ($i = 0; $i -lt $Users.Count; $i++) {
Get-ADUser -Filter "Name -eq '$($Users[$i])'" -Properties LogonWorkstations |
Where-Object { ($_.LogonWorkstations -split ',') -contains $Computers[$i] }
}
Since LogonWorkstations contains a comma-separated string, you will have to do some string manipulation. Using the -split operator on the , character will result in an array of strings. The -contains operator works nicely when comparing an item or collection of items to a single item.
If you want to compare the LogonWorkstations value of a user to any computer in the list, you can do something like the following:
$ADUsers = foreach ($User in $Users) {
Get-ADUser -Filter "Name -eq '$User'" -Properties LogonWorkstations | Where-Object {
Compare-Object -Ref ($_.LogonWorkstations -split ',') -Dif $Computers -IncludeEqual -ExcludeDifferent
}
}
Compare-Object here will only return a value if there is an exact match.
Note: I believe the LogonWorkstations attribute has been replaced with UserWorkstations attribute. Both may work now but may not be guaranteed in the future.
I haven't tried the below code but hopefully, you will be able to work out any little issues:
$computers = Get-Content -Path #PATHTOCOMPUTERS.TXT
$users = Get-Content -Path #PATHTOUSERS.TXT | Sort-Object -Unique
#Set a counter to zero
$counter = 0
foreach ($user in $users){
try{
#Get the current user from AD
$adUser = Get-ADUser -Filter { Name -eq $user} -Properties LogonWorkStations -ErrorAction Stop
#Uses the current index using $counter to get the correct computer from computers.txt and
#checks if the user has it setup as a LogonWorkStation
if ($adUser.LogonWorkstations -eq $computers[$counter]){
Write-Host "$user has $computer as a logon workstation"
}else{
Write-Host "$user doesn't have $computer as a logon workstation"
}
}catch{
Write-Host "Failed to get the AD user"
}
#Increment the $counter
$counter++
}

How to export to extensionName in Powershell to CSV without additional characters?

I am trying to provide a report of info for admin accounts in a forest. All admin accounts begin with admin. The owner of the admin accounts has been added as the "extensionName" attribute in active directory and I want to include this in the report (and ideally then look up the owners manager in AD but this could be done in 2 parts).
The value returned seems to be a multiple so I need to select the first value for this but when I use Select #{E={$_.extensionName[0]}} the entry in the CSV file is shown as #{$_.extensionName[0]=domain\username} I want to exclude the #{$_.extensionName[0]= part
Any help would be very much appreciated as I am going in circles.
$AdminReport = "C:\Temp\AdminReport.CSV"
Remove-Item $AdminReport -ErrorAction SilentlyContinue
$STR = "Name,SAM,Enabled,Owner,Group Memberships"
Add-Content $AdminReport $STR
$AllUsersNow = Get-ADUser –Filter "SamAccountName -Like 'admin*'"
Foreach ($ThisUser in $AllUsersNow)
{
$NameOfUser = $ThisUser.UserPrincipalName
$SamOfUser = $ThisUser.SamAccountName
$DisabledOfUser = $ThisUser.Enabled
$ExtOfUser = Get-ADuser $ThisUser -Properties extensionName | Select #{E= {$_.extensionName[0]}}
$GroupsOfUser = (Get-ADPrincipalGroupMembership -Identity $ThisUser).Name -join "; "
IF ($NameOfUser -like "*")
{
$STRNew = $NameOfUser+","+$SamOfUser+","+$DisabledOfUser+","+$ExtOfUser+","+$GroupsOfUser
Add-Content $AdminReport $STRNew
}
}
Actual output results show #{$_.extensionName[0]=domain\username} and I just want domain\username
It makes not much sense to manually build the csv file from strings as all data is present in objects.
Build a gathering [PSCustomObject] and export that.
## Q:\Test\2019\02\12\SO_54652780.ps1
$AdminReportFile = "C:\Temp\AdminReport.CSV"
$AdminReport = Foreach($User in (Get-ADUser –Filter "SamAccountName -Like 'xb*'")){
[PSCustomObject]#{
Name = $User.UserPrincipalName
SAM = $User.SamAccountName
Enabled = $User.Enabled
Owner = $User.ExtensionName|Select-Object -First 1
'Group Memberships' = (Get-ADPrincipalGroupMembership -Identity $User).Name -join "; "
}
}
$AdminReport
$AdminReport | Export-Csv $AdminReportFile -NoTypeInformation
I am not familiar with the extensionName attribute. You could just modify this one line of code:
$ExtOfUser = Get-ADuser $ThisUser -Properties extensionName | Select #{E= {$_.extensionName[0]}}
To be the following:
$ExtOfUser = (Get-ADuser $ThisUser -Properties extensionName).extensionName
Or perhaps the following if extensionName is an array:
$ExtOfUser = (Get-ADuser $ThisUser -Properties extensionName).extensionName[0]