I ran into such a problem, I don't know how best to solve it.
We have this script, determining the load on the CPU.
$pc = 'cmd'
try {$Connection = Test-Connection -ComputerName $pc -Count 2 -EA Stop}
catch {Write-Host "NO network connection to $PC" -fo Red; break}
try
{
$option = New-CimSessionOption -Protocol Dcom
$session = New-CimSession -ComputerName $pc -SessionOption $option -EA Stop
}
catch
{
try
{
$option = New-CimSessionOption -Protocol Wsman
$session = New-CimSession -ComputerName $pc -SessionOption $option -EA Stop
}
Catch {Write-Host "NOT connect to CimSession on $PC" -fo Red; break}
}
$ComputerSystem = Get-CimInstance -ClassName Win32_ComputerSystem -CimSession $session
$CpuCores = $ComputerSystem.NumberOfLogicalProcessors
Write-Host 'Please wait for CPU Usage data to be collect...' -fo Yellow
if (Get-Counter -ListSet * -ComputerName $pc | ? Paths -Match "\\Процесс\(\*\)"){$CounterPath = "\Процесс(*)\% загруженности процессора"}
else {$CounterPath = "\Process(*)\% Processor Time"}
$CPU_Usage_Counter = (Get-Counter -Counter $CounterPath -SampleInterval 1 -MaxSamples 5 -ComputerName $pc -EA SilentlyContinue).CounterSamples | ? CookedValue -ne 0 | group InstanceName
$CPU_Usage_Data = $CPU_Usage_Counter | select Name,#{N='CPU_Usage_%';E={[math]::Round(($_.Group | %{$_.CookedValue} | measure -Average).Average/$CpuCores,2)}}
$CPU_Usage_Data | ? Name -ne '_total' | sort 'CPU_Usage_%' -Des | ft -a
Write-Host "NumberOfLogicalProcessors: $CpuCores"
$check = Write-Host "CpuUsageProcess" ( [math]::Round((100 - (($CPU_Usage_Data | ? Name -eq 'idle').'CPU_Usage_%')),2) ) -fo Yellow
#####---------------------------------------------------------------------------------------------------------------------------------------------------
$status = (($check).CpuUsageProcess -gt 5)
if($status)
{
#Break
Write-Host -ForegroundColor Green 'if!'
}
else
{
Write-Host -ForegroundColor Red 'else!'
}
After execution, you need to extract the result of the "CpuUsageProcess" value and use it in the if / else condition
As Theo points out, Write-Host never emits any output - it simply causes PowerShell to write the input directly to the screen buffer.
Calculate the value(s) separately before calling Write-Host:
$check = [math]::Round((100 - (($CPU_Usage_Data | ? Name -eq 'idle').'CPU_Usage_%')),2)
Write-Host "CpuUsageProcess $check" -fo Yellow
if($check -gt 5){
# ...
}
Related
#I can't figure out where to place the Export-csv ...
It doesn't seem to work when I put it on the end with a pipe
foreach($ADMachineInOU in $(Get-ADComputer -Filter * -SearchBase "OU=Win10Modern,OU=LN,OU=Workstations,DC=cooley,DC=com" | Select -ExpandProperty Name))
{
# Test if machine is online
if(Test-Connection -ComputerName $ADMachineInOU -Count 1 -ErrorAction SilentlyContinue)
{
Write-Host $ADMachineInOU -ForegroundColor Green
$CimSession = New-CimSession -ComputerName $ADMachineInOU
Get-NetAdapter -CimSession $CimSession -Name "Wi-Fi" | select PSComputerName,DriverVersion
Remove-CimSession -CimSession $CimSession
}
else
{
Write-Host $ADMachineInOU -ForegroundColor Gray
}
Remove-Variable CimSession,ADMachineInOU -ErrorAction SilentlyContinue
}
I would suggest something like this:
$ADComputers = Get-ADComputer -Filter * -SearchBase "OU=Win10Modern,OU=LN,OU=Workstations,DC=cooley,DC=com"
$Report = #()
foreach ($ADComputer in $ADComputers)
{
$HostName = $ADComputer.name
# Test if machine is online
$Ping = Test-Connection $HostName -Count -Quiet -ErrorAction SilentlyContinue
If ($Ping)
{
Write-Host $HostName -ForegroundColor Green
$CimSession = New-CimSession -ComputerName $HostName
$Adapter = Get-NetAdapter -CimSession $CimSession -Name "Wi-Fi"
$Hash = #{
Hostname = $HostName
Hardware = "Wi-Fi"
PSComputerName = $Adapter.PSComputerName
DriverVersion = $Adapter.DriverVersion
}
Remove-CimSession -CimSession $CimSession
$Report += New-Object psobject -Property $hash
}
else
{
Write-Host $ADMachineInOU -ForegroundColor Gray
}
}
$Report | export-csv "c:\Temp\Report.csv" -NoTypeInformation
Simply capture the results from Get-NetAdapter in a variable. Make sure only wanted objects are output in the foreach loop:
$searchBase = "OU=Win10Modern,OU=LN,OU=Workstations,DC=cooley,DC=com"
$result = foreach($ADMachineInOU in (Get-ADComputer -Filter * -SearchBase $searchBase).Name) {
# Test if machine is online
if (Test-Connection -ComputerName $ADMachineInOU -Count 1 -Quiet -ErrorAction SilentlyContinue) {
Write-Host $ADMachineInOU -ForegroundColor Green
$CimSession = New-CimSession -ComputerName $ADMachineInOU
Get-NetAdapter -CimSession $CimSession -Name "Wi-Fi" | select PSComputerName,DriverVersion
# ignore the output from Remove-CimSession to not pollute the captured results
$null = Remove-CimSession -CimSession $CimSession
}
else {
Write-Host "Computer $ADMachineInOU did not respond.." -ForegroundColor Gray
}
}
# now export to Csv file
$result | Export-Csv -Path 'X:\Path\to\Output.Csv' -NoTypeInformation
P.S. I think you want to use -Name '*Wi-Fi*' for Get-NetAdapter..
I am creating a script that reads a list of computer names and collects data from security event logs about who is on the computer, how long they have been on for, and how long it has been since the computer has restarted. I have it working except that it does not output all the data into one CSV. I just receive one CSV file with one computer name.
function Get-KioskInfo {
param (
[parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True,Position=0)]
[Alias('PSComputerName','DNSHostName','CN','Hostname')]
[string]
$ComputerName = $env:COMPUTERNAME
)
#PARAM
$User = try {(Get-WmiObject -ComputerName $ComputerName Win32_ComputerSystem | Select-Object -ExpandProperty username).trimstart("NG\")} catch {Write-Output "User not detected";break}
$BootStart = ((get-date) - (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName).LastBootUpTime).Days
#These variables are for the DATE & Time calculation
If ($user -NE $null)
{ Write-Verbose 1
# Do something
$Date1 = Get-date
Write-Verbose 2
$SP = Get-WinEvent -ComputerName $ComputerName -FilterHashTable #{LogName = "Security";ID="5379";Data=$User; StartTime=((Get-Date).AddDays(-1))}
Write-Verbose 3
$Date2 =($SP | select -first 1).timecreated
Write-Verbose 4
$USERLOGTIME = ($Date1-$Date2).hours.tostring("N2")
Write-Verbose 5
}
else{Write-Output "No user";break}
Write-Verbose 6
#Rename-Computer -ComputerName "Srv01" -NewName "Server001" -DomainCredential Domain01\Admin01 -Force ------ Rename script for computers if it is needed.
#$computers = Get-Content C:\Users\jaycbee\Desktop\kiosknames.txt ------ To load kiosk list
#foreach ($c in $computers) {start-job -Name $c -ScriptBlock ${Function:get-kioskinfo} -ArgumentList $c} for learning how to do a foreach script
Write "Computer Name: $Computername"
Write "---USER---"
Write "Name: $User"
Write "Log in Time $USERLOGTIME"
Write "Boot start $BootStart days ago"
$ComputerName | ForEach-Object {
if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)
{
Invoke-Command -ComputerName $ComputerName {
}
} # Offline Check
else
{
Write-Host "Computer is Unreachable or Offline" -ForegroundColor Gray
}
} # Foreach
$Continue = Read-Host "WARNING! This will READ LIST of computers in \\ou\ouor-groups\Desktop_Support\SD\Kiosks\kiosknames.txt Type CONTINUE to proceed."
if ($Continue -eq "CONTINUE")
{
$Computers = Get-Content '\\ou\ouor-groups\Desktop Support\SD\Kiosks\kiosknames.txt'
foreach ($C in $Computers) {start-job -Name $c -ScriptBlock ${Function:get-kioskinfo} -ArgumentList $c
}
}
[pscustomobject]#{ Name = $ComputerName ; User = $User ; "User Log in time in hours" = $USERLOGTIME;"BootStart days ago" = $BootStart} | export-csv -path "\\ou\ouor-groups\Desktop Support\SD\Kiosks\test45$ComputerName.csv" -Append
} #Function
#For each-computer | do this at this location,
Continuing from my comment. I too wonder why the use of jobs for this use case. Unless you are doing this on hundreds of computers, thus needing parallel processing.
This refactor/formatting is just my way of making sense of what you posted. I'm old, and crowded code just really hurts my eyes. ;-} Yet, code the way you like of course. ;-}
I do not have an environment to test this, but give it a shot.
function Get-KioskInfo
{
param
(
[parameter(ValueFromPipeline = $True,ValueFromPipelineByPropertyName = $True,Position = 0)]
[Alias(
'PSComputerName',
'DNSHostName',
'CN',
'Hostname'
)]
[string]
$ComputerName = $env:COMPUTERNAME
)
($User = try
{
(Get-WmiObject -ComputerName $ComputerName Win32_ComputerSystem |
Select-Object -ExpandProperty username).trimstart("NG\")
}
catch
{
'User not detected'
break
}
)
($BootStart = ((get-date) - (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName).LastBootUpTime).Days)
If ($user -NE $null)
{
($Date1 = Get-date)
($SP = Get-WinEvent -ComputerName $ComputerName -FilterHashTable #{
LogName = 'Security'
ID = '5379'
Data = $User
StartTime = ((Get-Date).AddDays(-1))
})
($Date2 = (
$SP |
select -first 1
).timecreated)
($USERLOGTIME = ($Date1-$Date2).hours.tostring('N2'))
}
else
{
'No user'
break
}
"Computer Name: $Computername
---USER---
Name: $User
Log in Time $USERLOGTIME
Boot start $BootStart days ago"
$ComputerName |
ForEach-Object {
if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)
{Invoke-Command -ComputerName $ComputerName}
else
{Write-Warning -Message 'Computer is Unreachable or Offline'}
}
$UserMessage = '
WARNING!
This will READ LIST of computers in:
\\ou\ouor-groups\Desktop_Support\SD\Kiosks\kiosknames.txt
Type CONTINUE to proceed'
$Continue = Read-Host $UserMessage
if ($Continue -eq 'CONTINUE')
{
Get-Content '\\ou\ouor-groups\Desktop Support\SD\Kiosks\kiosknames.txt' |
foreach {
{start-job -Name $PSItem -ScriptBlock ${Function:get-kioskinfo} -ArgumentList $PSItem}
[pscustomobject]#{
Name = $ComputerName
User = $User
'User Log in time in hours' = $USERLOGTIME
'BootStart days ago' = $BootStart
}
} |
Export-Csv -path "$PWD\$ComputerName.csv" -Append
}
}
These didn't help me with my solution, but you were right about the start-jobs. I have to rework the entire script in order to get the correct info.
I have found this idea how to get drive size
I'm having problem incorporating it to my script as I don't know where to insert the code.
Also the idea is to have only one line per computer outputted even when multi hard drive system exists.
This is the code I'm working with it includes the "Get Drive Data" code
# Output file location to be changed as needed
$file="C:\scripts\reports\InentoryTest_$((Get-Date).ToString('MM-dd-yyyy')).csv"
$txt="c:\scripts\reports\InentoryTest-error_$((Get-Date).ToString('MM-dd-yyyy')).txt"
# Getting computers from Active Directory
$Computers = Get-ADComputer -Filter {Name -like 'M416*'} | select -expand name
Foreach($Computer in $Computers){
if(!(Test-Connection -ComputerName $Computer -BufferSize 16 -Count 1 -ea 0 -quiet))
{
write-host "Cannot reach $Computer is offline" -ForegroundColor red
}
else
{
$Output = #()
Try
{
# Get Drive Data
$disk = Get-WmiObject -ComputerName $Computer Win32_LogicalDisk | Where-Object { ( $_.DriveType ) -eq 3 -and ( ( $_.freespace / $_.size ) -lt .1 ) } | ForEach-Object -Process {
[pscustomobject] #{
Drive = $_.DeviceID
Size = '{0:N1}' -f ( $_.Size / 1GB )
Free = '{0:N1}' -f ( $_.freespace / 1GB )
PercentFree = '{0:N1}' -f ( $_.freespace / $_.size * 100 )
}
}
$domain = Get-WmiObject Win32_ComputerSystem -ComputerName $Computer -ErrorAction Stop
$os = Get-WmiObject Win32_OperatingSystem -ComputerName $Computer -ErrorAction Stop
$mac = Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled='True'" -ComputerName $Computer -ErrorAction Stop
$bios = Get-WmiObject win32_bios -ComputerName $Computer -ErrorAction Stop
$cpu = Get-WmiObject –class Win32_processor -ComputerName $Computer -ErrorAction Stop
$AD = Get-ADComputer $Computer -properties Name,Lastlogondate,ipv4Address,enabled,description,DistinguishedName -ErrorAction Stop
$ram = "{0} GB" -f ((Get-WmiObject Win32_PhysicalMemory -ComputerName $Computer -ErrorAction Stop | Measure-Object Capacity -Sum).Sum / 1GB)
$pc = Get-WmiObject win32_computersystem -ComputerName $Computer -ErrorAction Stop | select #{Name = "Type";Expression = {if (($_.pcsystemtype -eq '2') )
{'Laptop'} Else {'Desktop Or Other'}}
},Manufacturer,#{Name = "Model";Expression = {if (($_.model -eq "$null") ) {'Virtual'} Else {$_.model}}},username
# Create Output
$data = New-Object PSObject -Property #{
SerialNumber = $bios.serialnumber -replace "-.*"
Computername = $AD.name
IPaddress = $AD.ipv4Address
MACaddress = $mac.MACAddress
Enabled = $AD.Enabled
Description = $AD.description
OU = $AD.DistinguishedName.split(',')[1].split('=')[1]
DC = $domain.domain
Type = $pc.type
Manufacturer = $pc.Manufacturer
Model = $pc.Model
RAM = $ram
Disk = $disk #Get Drive Data
ProcessorName = ($cpu.name | Out-String).Trim()
NumberOfCores = ($cpu.NumberOfCores | Out-String).Trim()
NumberOfLogicalProcessors = ($cpu.NumberOfLogicalProcessors | Out-String).Trim()
Addresswidth = ($cpu.Addresswidth | Out-String).Trim()
OperatingSystem = $os.caption
InstallDate = ([WMI] '').ConvertToDateTime($os.installDate)
LastLogonDate = $ld.lastlogondate
LoggedinUser = $pc.username
}
# Only do this kind of update if it hasn't failed yet
$Output += $data
$desc="$($mac.MACAddress) ( $($bios.serialnumber -replace "-.*") ) $($pc.Model) | $((Get-Date).ToString('MM-dd-yyyy'))"
#Set-ADComputer $Computer -Description $desc -verbose
$Output | select Computername,Enabled,Description,IPaddress,MACaddress,OU,DC,Type,SerialNumber,Manufacturer,Model,RAM,Disk,ProcessorName,NumberOfCores,NumberOfLogicalProcessors,Addresswidth,OperatingSystem,InstallDate,LoggedinUser,LastLogonDate | export-csv -Append $file -NoTypeInformation
}
Catch [Exception]
{
# Only do this kind of update if create output has failed
$ErrorMessage = $_.Exception.Message
Add-Content -value "$Computer, $ErrorMessage, skipping to next" $txt
#Set-ADComputer $Computer -Description $ErrorMessage
continue
}
}
}
You're building a complex hierarchical object so you'd be better off collecting all the objects then dumping the result as a JSON or XML file. But if you do want a flat string, then you'll have to explicitly format your disk data into a string before adding it to the object you're dumping to CSV. Something like:
$diskData = $disk | foreach {
"[Drive: $($_.DeviceID), Size: $([int]($_.Size/1GB)), FreeSpace: $([int]($_.freespace/1GB)), PercentFree: $([int]($_.freespace/$_.size *100))]"
}
$diskdata = $diskdata -join " "
I have a code which runs trough Active Directory computers objects to collect information. Part of that information is then updated on Active directory description field.
My problem is that when I get the Exception.Message the AD object of a computer is still updated with the last found computer information.
I would like to find out how can I:
Update AD description when there is Exception.Message
Update Ad description with populated system info
Attached is the script I'm using but can't figure out where to put the output for the two Set-ADComputer statements
# Getting computers from Active Directory
$Computers = Get-ADComputer -Filter {Enabled -eq $true} | select -expand name
Foreach($computer in $computers)
# Checking if the computer is Online
{
if(!(Test-Connection -ComputerName $computer -BufferSize 16 -Count 1 -ea 0 -quiet))
{write-host "Cannot reach $Computer is offline" -ForegroundColor red}
else {
$Output = #()
Try{
$xx = Get-WmiObject win32_computersystem -ComputerName $Computer -ErrorAction Stop
$in = Get-WmiObject Win32_OperatingSystem -ComputerName $Computer -ErrorAction Stop
$mc = Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled='True'" -ComputerName $Computer -ErrorAction Stop
$sr = Get-WmiObject win32_bios -ComputerName $Computer -ErrorAction Stop
$Xr = Get-WmiObject –class Win32_processor -ComputerName $Computer -ErrorAction Stop
$ld = Get-ADComputer $Computer -properties Name,Lastlogondate,ipv4Address,enabled,description,DistinguishedName -ErrorAction Stop
$r = "{0} GB" -f ((Get-WmiObject Win32_PhysicalMemory -ComputerName $Computer -ErrorAction Stop | Measure-Object Capacity -Sum).Sum / 1GB)
$x = Get-WmiObject win32_computersystem -ComputerName $Computer -ErrorAction Stop | select #{Name = "Type";Expression = {if (($_.pcsystemtype -eq '2') )
{'Laptop'} Else {'Desktop Or Other'}}},Manufacturer,#{Name = "Model";Expression = {if (($_.model -eq "$null") ) {'Virtual'} Else {$_.model}}},username
$t= New-Object PSObject -Property #{
SerialNumber = $sr.serialnumber -replace "-.*"
Computername = $ld.name
IPaddress = $ld.ipv4Address
MACaddress = $mc.MACAddress
Enabled = $ld.Enabled
Description = $ld.description
OU = $ld.DistinguishedName.split(',')[1].split('=')[1]
DC = $xx.domain
Type = $x.type
Manufacturer = $x.Manufacturer
Model = $x.Model
RAM = $R
ProcessorName = ($xr.name | Out-String).Trim()
NumberOfCores = ($xr.NumberOfCores | Out-String).Trim()
NumberOfLogicalProcessors = ($xr.NumberOfLogicalProcessors | Out-String).Trim()
Addresswidth = ($xr.Addresswidth | Out-String).Trim()
Operatingsystem = $in.caption
InstallDate = ([WMI] '').ConvertToDateTime($in.installDate)
LastLogonDate = $ld.lastlogondate
LoggedinUser = $x.username
}
$Output += $t
}
Catch [Exception]
{
$ErrorMessage = $_.Exception.Message
Add-Content -value "$Computer, $ErrorMessage, skipping to next" $txt
Set-ADComputer $Computer -Description $ErrorMessage
}
}
# Output file location to be chnaged as needed
$file="C:\scripts\reports\AD-Inentory_$((Get-Date).ToString('MM-dd-yyyy')).csv"
$txt="c:\scripts\reports\AD-Inentory-error_$((Get-Date).ToString('MM-dd-yyyy')).txt"
$desc="$($mc.MACAddress) ( $($sr.serialnumber -replace "-.*") ) $($x.Model) | $((Get-Date).ToString('MM-dd-yyyy'))"
# Properties to be included in the output file
$Output | select Computername,Enabled,Description,IPaddress,MACaddress,OU,DC,Type,SerialNumber,Manufacturer,Model,RAM,ProcessorName,NumberOfCores,NumberOfLogicalProcessors,Addresswidth,Operatingsystem,InstallDate,LoggedinUser,LastLogonDate | export-csv -Append $file -NoTypeInformation
Set-ADComputer $Computer -Description $desc -verbose
}
It looks like the reason why it had the problem behaviour is it was catching the error, and setting the description as you wanted with the error.
However it would then continue on to evaluate the code outside of the catch and the else block, since it failed on the current computer the variables used to build the description variable still contained data from the previous computer that was successful and then update the failed computer again.
You could fix this by using the continue statement at the bottom of the catch block to skip the rest of the code for that iteration and move to the next item in the loop.
That solution would look like this:
Catch [Exception]
{
$ErrorMessage = $_.Exception.Message
Add-Content -value "$Computer, $ErrorMessage, skipping to next" $txt
Set-ADComputer $Computer -Description $ErrorMessage
## add continue statement here
continue
}
continue statement documentation here and examples here.
The other option is you restructure your code to make sure this cannot happen, like below.
I think this will fix your issue and do what you are trying to achieve. I put comments in the code around the changes I made (denoted by the ##) feel free to ask questions.
I would recommend you use more descriptive variable names.
## No Need to define these in the foreach loop
# Output file location to be chnaged as needed
$file = "C:\scripts\reports\AD-Inentory_$((Get-Date).ToString('MM-dd-yyyy')).csv"
$txt = "c:\scripts\reports\AD-Inentory-error_$((Get-Date).ToString('MM-dd-yyyy')).txt"
# Getting computers from Active Directory
## Update to use pipeline
Get-ADComputer -Filter {Enabled -eq $true} | Foreach-Object {
$computer = $_.Name
if(!(Test-Connection -ComputerName $computer -BufferSize 16 -Count 1 -ea 0 -quiet))
{
write-host "Cannot reach $Computer is offline" -ForegroundColor red
}
else
{
Try
{
## No Longer Need this because we are uisng the pipe line
#$Output = #()
$xx = Get-WmiObject win32_computersystem -ComputerName $Computer -ErrorAction Stop
$in = Get-WmiObject Win32_OperatingSystem -ComputerName $Computer -ErrorAction Stop
$mc = Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled='True'" -ComputerName $Computer -ErrorAction Stop
$sr = Get-WmiObject win32_bios -ComputerName $Computer -ErrorAction Stop
$Xr = Get-WmiObject –class Win32_processor -ComputerName $Computer -ErrorAction Stop
$ld = Get-ADComputer $Computer -properties Name, Lastlogondate, ipv4Address, enabled, description, DistinguishedName -ErrorAction Stop
$r = "{0} GB" -f ((Get-WmiObject Win32_PhysicalMemory -ComputerName $Computer -ErrorAction Stop | Measure-Object Capacity -Sum).Sum / 1GB)
$x = Get-WmiObject win32_computersystem -ComputerName $Computer -ErrorAction Stop | select #{Name = "Type"; Expression = {if (($_.pcsystemtype -eq '2') )
{'Laptop'} Else {'Desktop Or Other'}}
}, Manufacturer, #{Name = "Model"; Expression = {if (($_.model -eq "$null") ) {'Virtual'} Else {$_.model}}}, username
## Output on creation
New-Object PSObject -Property #{
SerialNumber = $sr.serialnumber -replace "-.*"
Computername = $ld.name
IPaddress = $ld.ipv4Address
MACaddress = $mc.MACAddress
Enabled = $ld.Enabled
Description = $ld.description
OU = $ld.DistinguishedName.split(',')[1].split('=')[1]
DC = $xx.domain
Type = $x.type
Manufacturer = $x.Manufacturer
Model = $x.Model
RAM = $R
ProcessorName = ($xr.name | Out-String).Trim()
NumberOfCores = ($xr.NumberOfCores | Out-String).Trim()
NumberOfLogicalProcessors = ($xr.NumberOfLogicalProcessors | Out-String).Trim()
Addresswidth = ($xr.Addresswidth | Out-String).Trim()
Operatingsystem = $in.caption
InstallDate = ([WMI] '').ConvertToDateTime($in.installDate)
LastLogonDate = $ld.lastlogondate
LoggedinUser = $x.username
}
## Only do this kind of update if it hasnt failed yet
$desc = "$($mc.MACAddress) ( $($sr.serialnumber -replace "-.*") ) $($x.Model) | $((Get-Date).ToString('MM-dd-yyyy'))"
# Properties to be included in the output file
Set-ADComputer $Computer -Description $desc -verbose
## No longer need this
# $t
}
Catch [Exception]
{
$ErrorMessage = $_.Exception.Message
Add-Content -value "$Computer, $ErrorMessage, skipping to next" $txt
Set-ADComputer $Computer -Description $ErrorMessage
continue
}
}
} | select Computername, Enabled, Description, IPaddress, MACaddress, OU, DC, Type, SerialNumber, Manufacturer, Model, RAM, ProcessorName, NumberOfCores, NumberOfLogicalProcessors, Addresswidth, Operatingsystem, InstallDate, LoggedinUser, LastLogonDate | export-csv -Append $file -NoTypeInformation
After looking at the suggestion to include continue statement I was able to achieve the solution to my problem with the following final script:
# Output file location to be changed as needed
$file="C:\scripts\reports\AD-Inentory_$((Get-Date).ToString('MM-dd-yyyy')).csv"
$txt="c:\scripts\reports\AD-Inentory-error_$((Get-Date).ToString('MM-dd-yyyy')).txt"
# Getting computers from Active Directory
$Computers = Get-ADComputer -Filter {Enabled -eq $true} | select -expand name
Foreach($computer in $computers){
if(!(Test-Connection -ComputerName $computer -BufferSize 16 -Count 1 -ea 0 -quiet))
{
write-host "Cannot reach $Computer is offline" -ForegroundColor red
}
else
{
$Output = #()
Try
{
$xx = Get-WmiObject win32_computersystem -ComputerName $Computer -ErrorAction Stop
$in = Get-WmiObject Win32_OperatingSystem -ComputerName $Computer -ErrorAction Stop
$mc = Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled='True'" -ComputerName $Computer -ErrorAction Stop
$sr = Get-WmiObject win32_bios -ComputerName $Computer -ErrorAction Stop
$Xr = Get-WmiObject –class Win32_processor -ComputerName $Computer -ErrorAction Stop
$ld = Get-ADComputer $Computer -properties Name,Lastlogondate,ipv4Address,enabled,description,DistinguishedName -ErrorAction Stop
$r = "{0} GB" -f ((Get-WmiObject Win32_PhysicalMemory -ComputerName $Computer -ErrorAction Stop | Measure-Object Capacity -Sum).Sum / 1GB)
$x = Get-WmiObject win32_computersystem -ComputerName $Computer -ErrorAction Stop | select #{Name = "Type";Expression = {if (($_.pcsystemtype -eq '2') )
{'Laptop'} Else {'Desktop Or Other'}}
},Manufacturer,#{Name = "Model";Expression = {if (($_.model -eq "$null") ) {'Virtual'} Else {$_.model}}},username
## Output on creation
$t= New-Object PSObject -Property #{
SerialNumber = $sr.serialnumber -replace "-.*"
Computername = $ld.name
IPaddress = $ld.ipv4Address
MACaddress = $mc.MACAddress
Enabled = $ld.Enabled
Description = $ld.description
OU = $ld.DistinguishedName.split(',')[1].split('=')[1]
DC = $xx.domain
Type = $x.type
Manufacturer = $x.Manufacturer
Model = $x.Model
RAM = $R
ProcessorName = ($xr.name | Out-String).Trim()
NumberOfCores = ($xr.NumberOfCores | Out-String).Trim()
NumberOfLogicalProcessors = ($xr.NumberOfLogicalProcessors | Out-String).Trim()
Addresswidth = ($xr.Addresswidth | Out-String).Trim()
Operatingsystem = $in.caption
InstallDate = ([WMI] '').ConvertToDateTime($in.installDate)
LastLogonDate = $ld.lastlogondate
LoggedinUser = $x.username
}
# Only do this kind of update if it hasn't failed yet
$Output += $t
$desc="$($mc.MACAddress) ( $($sr.serialnumber -replace "-.*") ) $($x.Model) | $((Get-Date).ToString('MM-dd-yyyy'))"
Set-ADComputer $Computer -Description $desc -verbose
$Output | select Computername,Enabled,Description,IPaddress,MACaddress,OU,DC,Type,SerialNumber,Manufacturer,Model,RAM,ProcessorName,NumberOfCores,NumberOfLogicalProcessors,Addresswidth,Operatingsystem,InstallDate,LoggedinUser,LastLogonDate | export-csv -Append $file -NoTypeInformation
}
Catch [Exception]
{
# Only do this kind of update if it has failed
$ErrorMessage = $_.Exception.Message
Add-Content -value "$Computer, $ErrorMessage, skipping to next" $txt
Set-ADComputer $Computer -Description $ErrorMessage
continue
}
}
}
My PowerShell script:
Param([string]$Computers) #Must supply a comma seperated list of servers
$Threshold = 20 #Only show CPU over this number
$NoP = 20 #Number of processes to list
$NoRS = 4 #Number of result sets
If (! $Computers) {
Write-Host "Connection to server failed - please specify a server name." -ForegroundColor Red
Break
} Else {
$ComputerList = $Computers -Split " "#,[StringSplitOptions]'RemoveEmptyEntries')
}
$Credential = $host.ui.PromptForCredential("Need credentials", "Please enter your user name and password.", "", "NetBiosUserName")
If (! $Credential) {
Write-Host "Authentication failed - please verify your username and password." -ForegroundColor Red
Break
}
$UserName = $Credential.Username
$Password = $Credential.GetNetworkCredential().Password
$CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName
$Domain = New-Object System.DirectoryServices.DirectoryEntry($CurrentDomain,$UserName,$Password)
If ($Domain.Name -eq $null){
Write-Host "Authentication failed - please verify your username and password." -ForegroundColor Red
Break
}
ForEach ($ComputerName In $ComputerList) {
$LoadPercentage = $Processors.LoadPercentage
If (!$LoadPercentage) {$LoadPercentage = 0}
Write-Host "Server: $ComputerName (CPU load $LoadPercentage%)" -NoNewline
$Processors = Get-WmiObject win32_processor -ComputerName $ComputerName -Credential $Credential
$i = 1
$TopProcess = #()
$PercentComplete = 0
Do{
$PercentComplete = [Math]::Floor($i/$NoRS*100)
Write-Progress -Activity $ComputerName -Status "$PercentComplete% Complete:" -PercentComplete $PercentComplete
$ProcessList = gwmi Win32_PerfFormattedData_PerfProc_Process -ComputerName $ComputerName -Credential $Credential |
Select IDProcess,Name,PercentProcessorTime |
Where {$_.Name -ne "_Total" -and $_.Name -ne "Idle"} |
Sort PercentProcessorTime -Descending |
Select -First $NoP
ForEach ($Process In $ProcessList) {
$row = New-Object PSObject -Property #{
Id = $Process.IDProcess
Name = $Process.Name
User = (gwmi Win32_Process -ComputerName $ComputerName -Credential $Credential | Where {$_.ProcessId -eq $Process.IDProcess}).GetOwner().User
CPU = $Process.PercentProcessorTime/$Processors.NumberOfLogicalProcessors -f {P}
Description = (gwmi Win32_Process -ComputerName $ComputerName -Credential $Credential | Where {$_.ProcessId -eq $Process.IDProcess}).Description
}
$TopProcess += $row
}
$i++
} While ($i -lt $NoRS + 1)
Write-Progress -Activity $ComputerName -Completed
$Group = $TopProcess | Where {$_.CPU -gt $Threshold} | Group 'ID' | Where Count -eq $NoRS
If (!$Group) {
Write-Host " has no processes persistently above $Threshold percent CPU usage."
} Else {
$Processes = #()
ForEach ($Groupee In $Group) {
$Ungroup = $Groupee | Select -ExpandProperty Group
$CPU = 0
ForEach ($ugr in $Ungroup) {
$CPU += $ugr.CPU
}
$row = new-object PSObject -Property #{
Id = $Ungroup.Id | Select -First 1
Name = $Ungroup.Name | Select -First 1
CPU = $CPU/$NoRS
User = $Ungroup.User | Select -First 1
Description = $Ungroup.Description | Select -First 1
}
$Processes += $row
}
$Processes | Format-Table #{Expression={$_.User};Label="User Name";width=25},#{Expression={$_.CPU};Label="CPU";width=5},#{Expression={$_.Id};Label="ID";width=8},#{Expression={$_.Description};Label="Description";width=48}
}
}
intermittantly gives the following error:
You cannot call a method on a null-valued expression. At C:\Users\Jasons1\CPUusage.ps1:41 char:4
$row = new-object PSObject -Property #{
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : InvalidOperation: (:) [], RuntimeException
FullyQualifiedErrorId : InvokeMethodOnNull
which I fail to understand as it is within a loop and should either work or get skipped as there is a test for null.
Pretty sure that your issues are stemming from this line:
User = (gwmi Win32_Process -ComputerName $ComputerName -Credential $Credential | Where {$_.ProcessId -eq $Process.IDProcess}).GetOwner().User
Specifically from .GetOwner(). Your where clause must not be finding a matching process for that item while it is in the loop. I realize there is not much time elapsed but WMI queries are not the fastest things out there.
What is happening is likely a result of a process queried earlier in $ProcessList = gwmi Win32_PerfFormattedData_PerfProc_Process and then later when you are using gwmi Win32_Process the list of processes changed. You need to account for this as well. Time has elapsed and threads do not live forever.
$queryResult = gwmi Win32_Process -ComputerName $ComputerName -Credential $Credential | Where {$_.ProcessId -eq $Process.IDProcess}
$owner = if($queryResult){$queryResult.GetOwner().User}else{"Process DNE"}
#...
User = $owner
Not very pretty but accounts for the potential of a null return from the wmi query.