I found a powershell example that does mostly what I need (Thanks to Lee_Dailey, https://stackoverflow.com/a/54469296/18171669). However, the example runs serially on the list of machines, meaning that any powered-off machine will cause a timeout which at the moment looks like about 60s per machine. With many machines being possibly offline, that could mean some long waits. So I messed with the example to fetch the info per Start-Job & Invoke-Command. I saw a suggestion from '@user4003407' that made sense to me (use Start-Job to call Invoke-Command) and I have a working version.
To make it work, however, I had to bring the script-block for the Invoke-Command down into the for-loop, which kinda bothers me as it gets initialized with every loop iteration. I was therefore hoping someone might point me in the right direction for a method that might bring the scriptblock out of the for loop, such that it needs only be initialized once. I've tried passing the scriptblock as a parameter, with $using:xxx, and a whole bunch of other ways, but I can't seem to make it work.
I admit herewith to being a powershell noob.
Thanks in advance.
#requires -RunAsAdministrator
param($myCSV = '.\getSysInfo.csv')
# fake reading in a list of computer names
<#
$ComputerList = @'
jupiter
saturn
'@ -split [environment]::NewLine
#>
$ComputerList = Get-ADObject -LDAPFilter "(objectClass=computer)" -SearchBase 'OU=Servercomputers,OU=batcave,DC=gothham,DC=com' -Properties name | Select-Object -Property name | foreach { $_.name }
foreach ($computer in $ComputerList) {
$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -ge 16) {
$running | Wait-Job -Any | Out-Null
}
$job = {
$IC_ScriptBlock = {
$CIM_ComputerSystem = Get-CimInstance -ClassName CIM_ComputerSystem
$CIM_BIOSElement = Get-CimInstance -ClassName CIM_BIOSElement
$CIM_OperatingSystem = Get-CimInstance -ClassName CIM_OperatingSystem
$CIM_Processor = Get-CimInstance -ClassName CIM_Processor
$CIM_LogicalDisk = Get-CimInstance -ClassName CIM_LogicalDisk |
Where-Object {$_.Name -eq $CIM_OperatingSystem.SystemDrive}
$Win32_QuickFixEngineering = Get-CimInstance -ClassName Win32_QuickFixEngineering -Property HotFixId | Select-Object -Property HotFixId
$CIM_NetworkAdapter = Get-CimInstance -ClassName CIM_NetworkAdapter
[PSCustomObject]@{
LocalComputerName = $env:COMPUTERNAME
Manufacturer = $CIM_ComputerSystem.Manufacturer
Model = $CIM_ComputerSystem.Model
SerialNumber = $CIM_BIOSElement.SerialNumber
CPU = $CIM_Processor.Name
SysDrive_Capacity_GB = '{0:N2}' -f ($CIM_LogicalDisk.Size / 1GB)
SysDrive_FreeSpace_GB ='{0:N2}' -f ($CIM_LogicalDisk.FreeSpace / 1GB)
SysDrive_FreeSpace_Pct = '{0:N0}' -f ($CIM_LogicalDisk.FreeSpace / $CIM_LogicalDisk.Size * 100)
RAM_GB = '{0:N2}' -f ($CIM_ComputerSystem.TotalPhysicalMemory / 1GB)
OperatingSystem_Name = $CIM_OperatingSystem.Caption
OperatingSystem_Version = $CIM_OperatingSystem.Version
OperatingSystem_BuildNumber = $CIM_OperatingSystem.BuildNumber
OperatingSystem_ServicePack = $CIM_OperatingSystem.ServicePackMajorVersion
CurrentUser = $CIM_ComputerSystem.UserName
LastBootUpTime = $CIM_OperatingSystem.LastBootUpTime
HotFixes = $Win32_QuickFixEngineering.HotFixId -join '; '
NetworkAdapter_Name = $CIM_NetworkAdapter.Name -join '; '
}
}
Invoke-Command -ComputerName $args[0] -ScriptBlock $IC_ScriptBlock -ErrorAction 'SilentlyContinue'
}
#Write-Host "Starting job for $computer"
Start-Job -Name $computer -ScriptBlock $job -ArgumentList $computer | Out-Null
}
Write-Host "Waiting for jobs to complete..."
# Wait for all jobs to complete and results ready to be received
Wait-Job -Name $ComputerList | Out-Null
# Get-Job *
$RespondingSystems = Receive-Job -Name $ComputerList
Remove-Job -Name $ComputerList
$RespondingSystems | Select-Object -Property * -ExcludeProperty PSShowComputerName, PSComputerName, RunspaceId | Export-Csv -Path $myCSV -NoTypeInformation -Delimiter ';'
$NOT_RespondingSystems = $ComputerList.Where({
$_ -notin $RespondingSystems.LocalComputerName
})
Write-Host "`nNon-Responding Systems: "
$NOT_RespondingSystems
CodePudding user response:
Starting a local job that holds the invocation to remote host is very inefficient, remote jobs on the other hand are highly efficient, your code can be simplified by simply allowing Invoke-Command
take the array of computers and invoke the remote jobs in parallel, you can collect the result of all invocations and then filter by objects which are of the type ErrorRecord
.
$ou = 'OU=Servercomputers,OU=batcave,DC=gothham,DC=com'
$computers = Get-ADComputer -Filter * -SearchBase $ou
$IC_ScriptBlock = { <# Original IC Scriptblock code here #> }
$result = Invoke-Command -ComputerName $computers -ScriptBlock $IC_ScriptBlock 2>&1
$failed, $success = $result.where({
$_ -is [System.Management.Automation.ErrorRecord] }, 'Split'
)
$success | Export-Csv .... -NoTypeInformation
$failed.Exception.Message # => Is the list of failed invocations