I've hacked together a script that lists all AD computer objects and enriches with some other information: Namely, is the machine pingable and what are the members of its local admin group. Each of these data elements are retrieved in an independent pipeline function.
For reasons unknown, the array of objects I'm pipelining is being truncated. Instead of the 883 computers returned from the GetAdComputer cmdlet, I'm left with only ~230 computers in the final CSV.
I've narrowed down the problem to the command that retrieves local admins via PS remoting. I'm sure there are machines for which this command fails so it's likely an exception is being thrown at some point. I assume such an exception may terminate the loop and lop off array items? I tried wrapping the Invoke-Command in a try/catch but that didn't resolve the issue. Still missing array items.
I'm sure this is a noob oversight. I don't use PS often enough to keep the language semantics straight. Any help is appreciated.
Import-Module active*
$now=(Get-Date).ToString("yyyyMMddTHHmmss")
Get-ADComputer -Filter 'Name -like "prod-idfi*"' -Properties LastLogonDate, Description |select LastLogonDate,DNSHostName,DistinguishedName,Description |foreach{
$alive = Test-Connection -CN $_.DNSHostName -Count 1 -BufferSize 16 -Quiet
return [PSCustomObject]#{
PSTypeName = "Computer"
Id=$null
Name = $_.DNSHostName
IsAlive = If($alive) {1} Else {0}
DistinguishedName = $_.DistinguishedName
Description = $_.Description
LastLogonDate = $_.LastLogonDate
LocalAdmins=''
}
}|foreach{
$computer = $_
if ($computer.IsAlive){
try{
$localAdmins = Invoke-Command -ComputerName $_.Name {([ADSI]"WinNT://./Administrators").psbase.Invoke('Members') | % { ([ADSI]$_).InvokeGet('AdsPath')}}
$computer.LocalAdmins= "$($localAdmins -join ",")"
}
catch {
Write-Host ("something bad happened")
}
}
$computer
} |Export-Csv ".\ComputerInventory_$now.csv" -NoTypeInformation
You do not need to pipe to a second ForEach-Object, since all can be done inside the first loop. Also, the Select-Object can be left out.
In order to enter the catch block also on non-terminating errors, you need to add parameter -ErrorAction Stop to the Invoke-Command.
Your code rewritten
Import-Module Active*
$now = (Get-Date).ToString("yyyyMMddTHHmmss")
$localGroup = 'Administrators'
Get-ADComputer -Filter "Name -like 'prod-idfi*'" -Properties LastLogonDate, Description |
ForEach-Object {
$alive = Test-Connection -ComputerName $_.DNSHostName -Count 1 -BufferSize 16 -Quiet
$localAdmins = if ($alive) {
try {
Invoke-Command -ScriptBlock {
([ADSI]"WinNT://./$localGroup").psbase.Invoke('Members') |
ForEach-Object { ([ADSI]$_).InvokeGet('AdsPath') -join ', '}
} -ErrorAction Stop
}
catch {
Write-Warning "Could not retrieve members of the local '$localGroup' group"
}
}
else { '' }
[PSCustomObject]#{
PSTypeName = "Computer"
Id = $null
Name = $_.DNSHostName
IsAlive = [int]$alive
DistinguishedName = $_.DistinguishedName
Description = $_.Description
LastLogonDate = $_.LastLogonDate
LocalAdmins = $localAdmins
}
} | Export-Csv -Path ".\ComputerInventory_$now.csv" -NoTypeInformation
Related
I have this script that I need to use to retrieve the data of a particular user "ADTuser" from a list of servers the script works well, but the output file with my user add also other users' detail that is not needed for my final output how can I filter it to only the user that I need.
get-content C:\servers.txt | foreach-object {
$Comp = $_
if (test-connection -computername $Comp -count 1 -quiet) {
([ADSI]"WinNT://$comp").Children | ?{$_.SchemaClassName -eq 'user' } | %{
$groups = $_.Groups() | %{$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
$_ | Select #{n='Computername';e={$comp}},
#{n='UserName';e={$_.Name}},
#{n='Memberof';e={$groups -join ';'}},
#{n='status'; e={if($groups -like "*Administrators*"){$true} else{$false}}}
}
} Else {Write-Warning "Server '$Comp' is Unreachable hence Could not fetch data"}
} | Out-File -FilePath C:\users.txt
This should be an easier way of doing what you're looking for, Get-CimInstance and Get-CimAssociatedInstance have been around since PowerShell 3:
Get-Content C:\servers.txt | ForEach-Object {
$computer = $_
try {
$query = Get-CimInstance Win32_UserAccount -Filter "Name='ADTuser'" -ComputerName $_ -ErrorAction Stop
foreach($object in $query) {
$membership = Get-CimAssociatedInstance -InputObject $object -ResultClassName Win32_Group -ComputerName $_
[pscustomobject]#{
Computername = $_
UserName = $object.Name
Memberof = $membership.Name -join ';'
Status = $membership.Name -contains 'Administrators'
}
}
}
catch {
Write-Warning "Server '$computer' is Unreachable hence Could not fetch data"
}
} | Export-Csv C:\users.csv -NoTypeInformation
If that doesn't work for you, your code would require a simple modification on your first filtering statement:
Where-Object { $_.SchemaClassName -eq 'user' -and $_.Name.Value -eq 'ADTuser' }
It's important to note that Test-Connection -ComputerName $_ -Count 1 -Quiet is not a relevant test for this script, this command is testing for ICMP response and adsi over WinNT requires RPC connectivity as well SMB.
Putting it all together with minor improvements the script would look like this:
Get-Content C:\servers.txt | ForEach-Object {
if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {
Write-Warning "Server '$_' is Unreachable hence Could not fetch data"
return
}
$computer = $_
([adsi]"WinNT://$_").Children.ForEach{
if($_.SchemaClassName -ne 'user' -and $_.Name.Value -ne 'ADTuser') {
return
}
$groups = $_.Groups().ForEach([adsi]).Name
[pscustomobject]#{
Computername = $computer
UserName = $_.Name.Value
Memberof = $groups -join ';'
Status = $groups -contains 'Administrators'
}
}
} | Export-Csv C:\users.csv -NoTypeInformation
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}
}
}
I'm trying to get the hostname and the MAC address from all PCs in the Active Directory. I know that MAC addresses are not in the Activce Directory. That's why I already used a small script from someone else. The point is that I have to make a list of hostnames, which I can do, but then the other script runs into a problem because some computers are not online.
Can anyone help me get a list with only the pc's that are online?
This is the part that searches the list I create with hostnames.
$Computers = Import-CSV C:\Users\admin_stagiair\Desktop\Computers.txt
$result = #()
foreach ($c in $Computers){
$nic = Invoke-Command {
Get-WmiObject Win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"'
} -ComputerName $c.Name
$x = New-Object System.Object | select ComputerName, MAC
$x.Computername = $c.Name
$x.Mac = $Nic.MACAddress
$result += $x
}
$result | Export-Csv C:\Users\admin_stagiair\Desktop\Computers.csv -Delimiter ";" -NoTypeInformation
And this is the part that I tried to make search the list and filter out the online computers, which absolutely does not work and I can't figure out how to do it.
$Computers = Import-Csv C:\Users\admin_stagiair\Desktop\Computers.txt
foreach ($c in $Computers) {
$ping = Test-Connection -Quiet -Count 1
if ($ping) {
$c >> (Import-Csv -Delimiter "C:\Users\admin_stagiair\Desktop\online.txt")
} else {
"Offline"
}
}
Last bit, this is the part I use to create a list of all computers in the Active Directory.
Get-ADComputer -Filter {enabled -eq $true} -Properties * |
select Name > C:\Users\(user)\Desktop\Computers.txt
If you only want one property from Get-ADComputer don't fetch all
a computer could have more than one MAC, to avoid an array be returned join them.
$result += inefficiently rebuilds the array each time, use a PSCustomObject instead.
Try this (untested):
EDIT: first test connection, get MAC only when online
## Q:\Test\2018\09\18\SO_52381514.ps1
$Computers = (Get-ADComputer -Filter {enabled -eq $true} -Property Name).Name
$result = ForEach ($Computer in $Computers){
If (Test-Connection -Quiet -Count 1 -Computer $Computer){
[PSCustomPbject]#{
ComputerName = $Computer
MAC = (Invoke-Command {
(Get-WmiObject Win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"').MACAddress -Join ', '
} -ComputerName $Computer)
Online = $True
DateTime = [DateTime]::Now
}
} Else {
[PSCustomPbject]#{
ComputerName = $Computer
MAC = ''
Online = $False
DateTime = [DateTime]::Now
}
}
}
$result | Export-Csv C:\Users\admin_stagiair\Desktop\Computers.csv -Delimiter ";" -NoTypeInformation
What about trying something like this:
# Get all computers in list that are online
$Computers = Import-Csv C:\Users\admin_stagiair\Desktop\Computers.txt |
Select-Object -ExpandProperty Name |
Where-Object {Test-Connection -ComputerName $_ -Count 1 -Quiet}
# Grab the ComputerName and MACAddress
$result = Get-WmiObject -ComputerName $computers -Class Win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"' |
Select-Object -Property PSComputerName, MacAddress
$result | Export-Csv C:\Users\admin_stagiair\Desktop\Computers.csv -Delimiter ";" -NoTypeInformation
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 have the following short script to grab serial numbers of computers and monitors in an OU, which works fine:
Import-Module ActiveDirectory
$searchbase = "OU=some,OU=organisational,OU=units,DC=somedomain,DC=local"
Write-Host ""
Write-Host "Serial Numbers for Computers and Monitors in" $searchbase
Write-Host "--"
Get-ADComputer -SearchBase $searchbase -Filter '*' | `
Select-Object -Expand Name | %{Write-Host ""; echo $_ ; Get-WMIObject -Class Win32_BIOS -ComputerName $_ | Select-Object -Expand SerialNumber; `
$monitor = gwmi WmiMonitorID -Namespace root\wmi -computername $_; ($monitor.SerialNumberID | foreach {[char]$_}) -join ""};
This script doesn't check to see if the computer is online before attempting to fetch the WMIObject, so if a computer is offline it takes ages before the RPC call times out.
I tried to modify the script to use the Test-Connection cmdlet before trying to get the WMIObject:
Import-Module ActiveDirectory
$searchbase = "OU=some,OU=organisational,OU=units,DC=somedomain,DC=local"
Write-Host ""
Write-Host "Serial Numbers for Computers and Monitors in" $searchbase
Write-Host "--"
Get-ADComputer -SearchBase $searchbase -Filter '*' | `
Select-Object -Expand Name | `
if (Test-Connection -ComputerName $_ -Quiet) {
%{Write-Host ""; echo $_ ; Get-WMIObject -Class Win32_BIOS -ComputerName $_ | Select-Object -Expand SerialNumber; `
$monitor = gwmi WmiMonitorID -Namespace root\wmi -computername $_; ($monitor.SerialNumberID | foreach {[char]$_}) -join ""};}
}
else {
Write-Host ""; Write-Host $_ "is offline";
}
I'm sure I'm doing something syntactically stupid. Can someone point me in the right direction?
You can't pipe directly to an if statement, only to cmdlets.
Put the if statement inside the ForEach-Object block (% is an alias for ForEach-Object):
... | Select-Object -Expand Name | `
%{
if (Test-Connection -ComputerName $_ -Quiet) {
# Get-WmiObject in here
}
else {
Write-Host ""; Write-Host $_ "is offline";
}
}
If you don't care about writing each machine's status to the host, you could also filter out offline computers with Where-Object(alias ?):
... | Select-Object -Expand Name | ?{
Test-Connection $_ -Quiet
} | % {
Get-WmiObject -ComputerName $_
}
In addition to the answer from #Mathias R. Jessen, you can get rid of the backticks for line continuation.
They are not needed if the end of the line infers there is another block of code required for the statement. Like | or { or (.
"foo", "bar" |
% {$_}
works just fine...