Home > Blockchain >  How-to use best pass a scriptblock for 'Invoke-Command' from a 'Start-Job'
How-to use best pass a scriptblock for 'Invoke-Command' from a 'Start-Job'

Time:02-17

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
  • Related