I am trying to query multiple computers from the Domain using Get-ADComputer. I would like to append the pc name I queryed to the array with the word "error" or a nonsensical date or even a blank value in that spot.
Import-Module ActiveDirectory
$PCNames = "laptop-namea", "laptop-nameb", "laptop-badname"
$Output = #()
$Output = foreach ($PC in $PCNames) {
try {
Get-ADComputer -Identity $PC -Properties * |
Select-Object Name, LastLogonDate
} catch {
$Output += ($PC)
}
}
Current output:
Name LastLogonDate
---- -------------
LAPTOP-NAMEA 1/27/2019 10:37:13 AM
LAPTOP-NAMEB 1/22/2019 8:23:02 AM
Wanted/expected output:
Name LastLogonDate
---- -------------
LAPTOP-NAMEA 1/27/2019 10:37:13 AM
LAPTOP-NAMEB 1/22/2019 8:23:02 AM
LAPTOP-BADNAME
Use -Filter instead of -Identity to avoid throwing errors in case of invalid names.
$Output = foreach ($PC in $PCNames) {
New-Object -Type PSObject -Property #{
'Name' = $PC
'LastLogon' = Get-ADComputer -Filter "Name -eq '$PC'" -Property LastLogonDate |
Select-Object -Expand LastLogonDate
}
}
Beware that querying AD for each individual computer is time-consuming. If the number of queries grows beyond a certain point it's better to query all computers, put them into an appropriate data structure (usually a hashtable), and then look up the desired information in that data structure.
$computers = #{}
Get-ADComputer -Filter '*' -Property LastLogonDate | ForEach-Object {
$computers[$_.Name] = $_.LastLogonDate
}
$Output = foreach ($PC in $PCNames) {
New-Object -Type PSObject -Property #{
'Name' = $PC
'LastLogon' = $computers[$PC].LastLogonDate
}
}
Try - Catch - Finally blocks handle terminating errors. Apply the common parameter -ErrorAction -Stop as follows:
Import-Module ActiveDirectory
$PCNames = "laptop-namea","laptop-nameb","laptop-badname"
$Output = ForEach ($PC in $PCNames)
{
try{
Get-ADComputer -Identity $PC -Properties * -ErrorAction Stop |
Select-Object Name, LastLogonDate
}
catch{
[PSCustomObject]#{Name=$PC;LastLogonDate=$null}
}
}
Related
How do I get a list of computers in a particular OU along with the Description and Last logged on user in a .csv?
$userName = (Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $machine -ErrorAction:SilentlyContinue).UserName
$DisComp = Get-ADComputer -LDAPFilter "(Name=LN-*)" -SearchBase "OU=Computers - Disabled,DC=XXXXX,DC=com" | Select-Object Name
$results = foreach ($Machine in $DisComp) {
$Description = Get-AdComputer -Identity $Machine -Properties * | Select-Object Description
$UserName
$Machine
$Description
}
$results | Export-Csv -Path C:\XXXXX
Define the OU and CSV file paths
$ouPath = "OU=Workstations,DC=contoso,DC=com"
$csvPath = "C:\temp\computer-list.csv"
Use the Get-ADComputer cmdlet to get a list of computers in the OU
$computers = Get-ADComputer -SearchBase $ouPath -Filter * -Properties lastlogondate,description
Loop through each computer and get the description and last logged on user
foreach ($computer in $computers) {
$description = $computer.Description
$lastLoggedOnUser = $computer.LastLogonUser
$data = [PSCustomObject]#{
"Computer Name" = $computer.Name
"Description" = $description
"Last Logged On User" = $lastLoggedOnUser
}
Add the computer data to the CSV file
$data | Export-Csv -Path $csvPath -Append -NoTypeInformation
}
AFAIK there is no AD computer property called LastLogonUser or any other property that holds this information.
To get the user that last logged on, you need to query the windows Eventlog on that computer and search for events with ID 4672
As aside, don't use -Properties * if all you want on top of the default properties returned is the Description property.
Try:
$searchBase = "OU=Computers - Disabled,DC=XXXXX,DC=com"
$Computers = Get-ADComputer -LDAPFilter "(Name=LN-*)" -SearchBase $searchBase -Properties Description
$results = foreach ($machine in $Computers) {
# to get the username who last logged on, you need to query the Security log
$events = Get-WinEvent -ComputerName $machine.Name -FilterHashtable #{Logname='Security';ID=4672} -MaxEvents 50 -ErrorAction SilentlyContinue
$lastLogon = if ($events) {
(($events | Where-Object {$_.Properties[1].Value -notmatch 'SYSTEM|NETWORK SERVICE|LOCAL SERVICE'})[0]).Properties[1].Value
}
else {
"Unknown"
}
# output an object
[PsCustomObject]#{
ComputerName = $machine.Name
Description = $machine.Description
LastLoggedOnUser = $lastLogon
CurrentUser = (Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $machine.Name -ErrorAction SilentlyContinue).UserName
}
}
$results | Export-Csv -Path 'C:\Somewhere\Computers.csv' -NoTypeInformation
P.S. You of course need admin permissions to query the eventlog, so perhaps (if you are not a domain admin) you need to use the -Credential parameter on the Get-WinEvent line aswell.
The following script works perfectly, but I think it's way too complex and slow for what it needs to do.
Basically, for a list of users in a variable (manually or obtained from Get-ADUser, doesn't matter), I want to query all Domain Controllers and get the LastLogonDate for each user. I'll later use it for bad password etc.
Any suggestions on cleaning it up please that would improve my coding skills?
$UserList = "User1", "User2"
$DCs = (Get-ADDomainController -Filter *).Name
$Combined = foreach ($User in $UserList)
{
$DCarray = [ordered] #{}
foreach ($DC in $DCs)
{
$DCresponse = Get-ADUser $User -Properties DisplayName, LastLogonDate -Server $DC | Select-Object Name, DisplayName, LastLogonDate
if( -not $DCarray.Contains("Name")) { $DCarray.Add("Name",$DCresponse.name) }
if( -not $DCarray.Contains("DisplayName")) { $DCarray.Add("DisplayName",$DCresponse.DisplayName) }
if( -not $DCarray.Contains($DC)) { $DCarray.Add($DC,$DCresponse.LastLogonDate) }
}
$Return = New-Object -TypeName psobject
foreach ($Key in $DCarray.keys)
{
$Each = $DCarray[$Key]
$Return | Add-Member -MemberType NoteProperty -Name $Key -Value $Each
}
$Return
}
$Combined | Format-Table -AutoSize
I think the logic is mostly the same but this should be easier to understand and maintain. In addition, the use of the LDAPFilter should improve the runtime a bit.
$UserList = "User1", "User2", "User3"
$filter = "(|(name={0}))" -f ($UserList -join ')(name=')
# LDAP query string would look like: (|(name=User1)(name=User2)(name=User3))
$DCs = (Get-ADDomainController -Filter *).Name
$props = #{
Properties = 'DisplayName', 'LastLogonDate'
LDAPFitler = $filter
}
$result = foreach($dc in $DCs)
{
$props.Server = $dc
$users = Get-ADUser #props
foreach($user in $users)
{
# If this User's LastLogonDate attribute is NOT null
if($user.LastLogonDate)
{
[pscustomobject]#{
DomainController = $dc
UserName = $user.Name
DisplayName = $user.DisplayName
LastLogonDate = $user.LastLogonDate
}
}
}
}
$result | Sort-Object UserName, LastLogonDate | Out-GridView
I can't leave a comment, so posting as an Answer instead.
For a non-Powershell solution have a look at this tool which will retrieve the the last logon time from all DC in the forest\domain for a list of users, based on a list of samaccountnames.
https://nettools.net/last-logon-time/
Gary
Would anybody have any suggestions? I need to generate a list of users and the computers they're logging into, from Active Directory. I'm hoping to get something like this:
Username Hostname
user.lastname ComputerA1
So far, I've gotten:
Enter-PSSession Import-Module ActiveDirectory Get-ADComputer
-Filter * -Properties Name Get-ADuser -filter * -Properties * | export-csv '\\\AD_UserLists.csv'
This works, kinda. I can generate a list of computers from AD and I can generate a list of ADUsers (albeit with ALL the users information). Unfortunately, I can't generate the data into a single CSV.
Suggestions/Advice????
Thanx,
David
Here is a way to get what you want. You will have to run this against AD-Computer objects when the machines are online, and catch the names of the computers you could not reach. Something like this...
#grab the DN of the OU where your computer objects are located...
$OU = ("OU=Computers,DC=domain,DC=com")
#put your filtered results in $computers (I filtered for Enabled objects)...
$computers = #()
ForEach ($O in $OU) {
$computers += Get-ADComputer -SearchBase $O -filter 'Enabled -eq "True"' -Properties CN,distinguishedname,lastLogonTimeStamp | Select-Object CN,distinguishedname,lastLogonTimeStamp
}
#instantiate some arrays to catch your results
#collected user info
$userInfo = #()
#computers you cannot ping
$offline = #()
#computers you can ping but cannot establish WinRM connection
$winRmIssue = #()
#iterate over $computers list to get user info on each...
ForEach ($computer in $computers) {
#filter out System account SIDs
$WQLFilter = "NOT SID = 'S-1-5-18' AND NOT SID = 'S-1-5-19' AND NOT SID = 'S-1-5-20'"
$WQLFilter = $WQLFilter + " AND NOT SID = `'$FilterSID`'"
#set number of login events to grab
$newest = 20
#attempt to ping computer once by name. return 'true' is success...
if (Test-Connection -ComputerName $computer.CN -Count 1 -ErrorAction Stop -Quiet) {
#if ping is true, try to get some info...
Try {
#currently logged in user...
$user = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computer.CN | select -ExpandProperty username
#the most commonly logged in user, based on the past 20 log-ins...
$UserProperty = #{n="User";e={((New-Object System.Security.Principal.SecurityIdentifier $_.ReplacementStrings[1]).Translate([System.Security.Principal.NTAccount])).ToString()}}
$logs = Get-EventLog System -Source Microsoft-Windows-Winlogon -ComputerName $computer.CN -newest $newest | select $UserProperty
$freqent = $logs | Group User | Sort-Object Count | Select -First 1 | Select-Object -ExpandProperty Name
}
#catch any connection issues...
Catch {
$cantInvoke = [pscustomobject][ordered]#{
'Computer' = $computer.CN
'Message' = "Could not Invoke-Command. Probably a WinRM issue."
}
$winRMIssue += $cantInvoke
}
#custom psobject of gathered user info...
$userInfoObj = New-Object psobject -Property ([ordered]#{
'Computer' = $computer.CN
'LoggedInUser' = $user
'mostCommonUser' = $frequent
})
$userInfo += $userInfoObj
}
#if you could not ping the computer, gather that info here in a custom object...
else {
$noPing = [pscustomobject][ordered]#{
'Computer' = $computer.CN
'DN' = $computer.distinguishedname
'lastLogonDate' = [datetime]::FromFileTime($computer.lastLogonTimeStamp).toShortDateString()
}
$offline += $noPing
}
#then kick out the results to csv
$userInfo | Sort-Object Computer | export-csv -Path c:\path\file.csv -NoTypeInformation
$offline | Sort-Object lastLogonDate | export-csv -Path c:\path.file2csv -NoTypeInformation
$winRmIssue | Sort-Object Computer | export-csv -Path c:\path\file3.csv -NoTypeInformation
You could use the wmi function
Get-WmiObject -Class Win32_ComputerSystem -ComputerName "computersname" | Select-Object Name,Username
I need to generate a list of users and the computers they're logging into, from Active Directory.
This information is not stored in Active Directory. You may be able to retrieve this information with Active Directory auditing. Otherwise, you'll need to poll each individual workstation.
I'm trying to combine the output of two functions with the output of the default Get-ADUser-cmdlet. I'm interested in when an account was created, if it's locked and what it's name is. I also want to know when the user logged on for the last time (using multiple DC's) and if the account is being used as a shared mailbox.
I've written two custom functions Get-ADUserLastLogon and isSharedMailbox, both functions use the Write-Output function to output their output. In case of Get-ADUserLastLogon this will be Lastlogon: time and in case of isSharedMailbox this will be shared: yes/no. I'm also using a standard Get-ADUser call in a foreach loop
Now, the default output of Get-ADUser is:
SAMAccountName LockedOut Created
-------------- --------- -------
ACC False 23-10-2015 8:20:20
Output of the custom functions is as following:
Lastlogon : 1-1-1601 1:00:00
Shared: yes
What I would like is to combine the LastLogon and Shared 'headers' to be combined into the Get-ADUser. So the output would become:
SAMAccountName LockedOut Created LastLogon Shared
Code of current code, where the accounts get imported from an Excel sheet:
foreach($username in $usernameWithTld){
if ($username -eq $NULL){
break
}
$usernameWithoutTld = $username.split('\')
Get-ADUser $usernameWithoutTld[1] -Properties LockedOut, SamAccountName,
Created -ErrorAction Stop | Select-Object SAMAccountName, LockedOut,
Created
Get-ADUserLastLogon -UserName $usernameWithoutTld[1]
# Shared mailbox?
isSharedMailbox -mailboxname $usernameWithoutTld[1]
}
Function code:
function isSharedMailbox([string]$mailboxname){
$isObject = Get-ADUser -Filter {name -eq $mailboxname} -SearchBase "..." | Select-Object DistinguishedName,Name
if ($isObject -match "DistinguishedName"){
$output = "Shared: no"
Write-Output $output
} else {
$output = "Shared: No"
Write-Output $output
}
}
function Get-ADUserLastLogon([string]$userName){
$dcs = Get-ADDomainController -Filter {Name -like "*"}
$time = 0
foreach($dc in $dcs)
{
$hostname = $dc.HostName
$user = Get-ADUser $userName | Get-ADObject -Properties lastLogon
if($user.LastLogon -gt $time)
{
$time = $user.LastLogon
}
}
$dt = [DateTime]::FromFileTime($time)
Write-Output "LastLogon : $dt"
}
I'm sure there are lots of improvements that can be made, I'm still learning how to write (proper) PowerShell. I hope someone can answer my question.
You could use a Calculated Property in your Select-Object. Have a look at example 4 for the MSDN page.
In your case this would be:
Get-ADUser $usernameWithoutTld[1] -Properties LockedOut, SamAccountName, Created -ErrorAction Stop | `
Select-Object SAMAccountName, LockedOut, Created, #{Name='LastLogon';Expression={Get-ADUserLastLogon -UserName $usernameWithoutTld[1]}}, #{Name='IsSharedMailbox';Expression={isSharedMailbox -mailboxname $usernameWithoutTld[1]}}
Or even better, you can use the object(s) that Get-ADUser puts in the pipeline to in turn call your functions for that specific object, and can be useful in case your query returns multiple results:
Get-ADUser $usernameWithoutTld[1] -Properties LockedOut, SamAccountName, Created -ErrorAction Stop | `
Select-Object SAMAccountName, LockedOut, Created, #{Name='LastLogon';Expression={Get-ADUserLastLogon -UserName $_.sAMAccountName}}, #{Name='IsSharedMailbox';Expression={isSharedMailbox -mailboxname $_.sAMAccountName}}
One way to do this is to get your functions to return the values you are interested in, store them in variables, and combine everything together afterwards into a PSObject containing the properties you are interested.
The benefits of storing as an object are many. For example, you can use Select-Object, Sort-Object etc in the pipeline, or Export-CSV and other Cmdlets that expect InputObject
foreach($username in $usernameWithTld){
if ($username -eq $NULL){
break
}
$usernameWithoutTld = $username.split('\')
$adDetails = Get-ADUser $usernameWithoutTld[1] -Properties LockedOut, SamAccountName,
Created -ErrorAction Stop | Select-Object SAMAccountName, LockedOut,
Created
$lastlogin = Get-ADUserLastLogon -UserName $usernameWithoutTld[1]
# Shared mailbox?
$isshared = isSharedMailbox -mailboxname $usernameWithoutTld[1]
# putting together the PSobject
[array]$myResults += New-Object psobject -Property #{
SAMAccountName = $adDetails.SAMAccountName
LockedOut = $adDetails.LockedOut
Created = $adDetails.Created
LastLogon = $lastlogin
Shared = $shared # true/false or yes/no, depending on function
#Shared = if($shared){"yes"}else{"no"} # yes/no, based on true/false from function
}
}
Functions:
function isSharedMailbox([string]$mailboxname){
$isObject = Get-ADUser -Filter {name -eq $mailboxname} -SearchBase "..." | Select-Object DistinguishedName,Name
return ($isObject -match "DistinguishedName") # returns true/false
<# if you prefer to keep yes/no
if ($isObject -match "DistinguishedName"){
return "Yes" # no in original code
} else {
return "No"
}
#>
}
function Get-ADUserLastLogon([string]$userName){
$dcs = Get-ADDomainController -Filter {Name -like "*"}
$time = 0
foreach($dc in $dcs)
{
$hostname = $dc.HostName
$user = Get-ADUser $userName | Get-ADObject -Properties lastLogon
if($user.LastLogon -gt $time)
{
$time = $user.LastLogon
}
}
$dt = [DateTime]::FromFileTime($time)
return $dt
#Write-Output "LastLogon : $dt"
}
You can store the result of the functions in global variables and finally concatenate them is one way.
Else you can use return the output from the function and use the value later or like : $value= functionname then $value will hold the return value of the function and later you can combine the results.
function isSharedMailbox([string]$mailboxname){
$isObject = Get-ADUser -Filter {name -eq $mailboxname} -SearchBase "..." | Select-Object DistinguishedName,Name
if ($isObject -match "DistinguishedName"){
$output = "Shared: no"
$Global:result1= $output
} else {
$output = "Shared: No"
$Global:result1= $output
}
}
function Get-ADUserLastLogon([string]$userName){
$dcs = Get-ADDomainController -Filter {Name -like "*"}
$time = 0
foreach($dc in $dcs)
{
$hostname = $dc.HostName
$user = Get-ADUser $userName | Get-ADObject -Properties lastLogon
if($user.LastLogon -gt $time)
{
$time = $user.LastLogon
}
}
$dt = [DateTime]::FromFileTime($time)
$Global:result2= "LastLogon : $dt"
}
## Calling the function . Change the placeholders accordingly
Get-ADUserLastLogon -UserName $usernameWithoutTld[1]
isSharedMailbox -mailboxname $usernameWithoutTld[1]
$FinalResult = "result1" + "result2"
$FinalResult
Hope it helps you better understanding.
I am running into an error handling issue and have tried a lot of different attempts without much luck. I have a data set and am trying to compare it against AD. A machine may exist in the data set, but not in AD. I would like to retain that information in my end array, but am running into a terminating error:
Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException
$data = Import-Csv -Path .\data.csv
$cdata = #()
foreach ($data in $data) {
foreach ($addata in (Get-ADComputer -Identity $data.Name -Properties LastLogonDate | Select-Object LastLogonDate)) {
$combine = #{
"Name" = $data.Name
"LastPolicyRequest" = $data.LastPolicyRequest
"LastLogonDate" = $addata.LastLogonDate
}
$cdata += New-Object psobject -Property $combine
}
}
Get-ADComputer (and other AD cmdlets) throw this exception when you try to get an object by identity that doesn't exist. Use the -Filter parameter to avoid this issue. Also, don't use the same variable for item and set ($data in $data).
$csv = Import-Csv -Path '.\data.csv'
foreach ($data in $csv) {
$name = $data.Name
foreach ($addata in (Get-ADComputer -Filter "SamAccountName -eq '$name'" -Properties LastLogonDate | Select-Object LastLogonDate)) {
...
}
}
If you just want to add the last logon date to those records that are present in AD you could do something like this:
$cdata = Import-Csv -Path '.\data.csv' |
Select-Object Name, LastPolicyRequest, #{n='LastLogon';e={
Get-ADComputer -Filter "SamAccountName -eq '$($_.Name)'" -Properties LastLogonDate |
Select-Object -Expand LastLogonDate
}}