Hello,
I am new to trying my own runspaces, but I don’t think that’s the issue here.
I’m trying to write a script that will eventually go through about 1000 servers to pull patch information that can be sent to app owners so they know that their servers need to be rebooted, when they were last patched, etc.
Everything works for the most part. The only part that doesn’t is “Last Patch Install Time”. Which also throws off the “Pending Reboot” output since there is a final check of last boot time vs. last patch install time to determine if pending reboot is true or false.
I’ve verified that the scriptblock code works on a remote computer directly, just won’t work while running the full script with the threads.
function StartThreads ($servers, $scriptBlock) {
$ErrorActionPreference = "SilentlyContinue"
$maxThreads = 50
$maxResultTime = 300
$sleepTimer = 2000
$jobs = New-Object System.Collections.ArrayList
$output = @()
$runspacePool = [runspacefactory]::CreateRunspacePool(1, $maxThreads)
$runspacePool.Open()
foreach ($server in $servers) {
$thread = [Powershell]::Create().AddScript($scriptBlock).AddArgument($server)
$thread.RunspacePool = $runspacePool
$handle = $thread.BeginInvoke()
$job = "" | select Handle, Thread, Object
$job.Handle = $handle
$job.Thread = $thread
$job.Object = $server
$null = $jobs.Add($job)
}
$resultTimer = Get-Date
$threadTimeout = $false
while ((@($jobs | where {$_.Handle -ne $null}).Count -gt 0) -and ($threadTimeout -eq $false)) {
$secondsUntilTimeout = $maxResultTime - [int32]((New-TimeSpan -Start $resultTimer -End (Get-Date)).TotalSeconds)
$runningThreads = $maxThreads - $runspacePool.GetAvailableRunspaces()
$percentComplete = [int32]((($jobs.Count - (($jobs | where {$_.Handle.IsCompleted -eq $false}).Count)) / $jobs.Count) * 100)
$remainingCount = (@($jobs | where {$_.Handle.IsCompleted -eq $false}).Count)
$remaining = @(($jobs | where {$_.Handle.IsCompleted -eq $false} | select -First 20).Object) -join ", "
if (($percentComplete -eq 100) -and ($remainingCount.Count -gt 0)) {
$percentComplete = 99
}
ClearScreen
Write-Host "$runningThreads of $maxThreads threads running (" -NoNewline -ForegroundColor Cyan;Write-Host "$percentComplete%" -NoNewline -ForegroundColor Green;Write-Host " complete)`n" -ForegroundColor Cyan
Write-Host "$secondsUntilTimeout seconds until thread timeout`n" -ForegroundColor Yellow
Write-Host "$remainingCount remaining ($remaining ...)" -ForegroundColor Cyan
foreach ($job in $($jobs | where {$_.Handle.IsCompleted -eq $true})) {
$output += $job.Thread.EndInvoke($job.Handle)
$job.Thread.Dispose()
$job.Thread = $null
$job.Handle = $null
$job.Object = $null
$resultTimer = Get-Date
}
if ([int32]((New-TimeSpan -Start $resultTimer -End (Get-Date)).TotalSeconds) -gt $maxResultTime) {
$threadTimeout = $true
$secondsUntilTimeout = 0
$runningThreads = 0
$percentComplete = 100
$remainingCount = 0
$remaining = ""
ClearScreen
Write-Host "$runningThreads of $maxThreads threads running (" -NoNewline -ForegroundColor Cyan;Write-Host "$percentComplete%" -NoNewline -ForegroundColor Green;Write-Host " complete)`n" -ForegroundColor Cyan
Write-Host "$secondsUntilTimeout seconds until thread timeout`n" -ForegroundColor Yellow
Write-Host "$remainingCount remaining ($remaining ...)" -ForegroundColor Cyan
}
Start-Sleep -Milliseconds $sleepTimer
}
if ($threadTimeout -eq $false) {
Write-Host "`n`nAll threads completed successfully. Disposing of runspace pool..." -ForegroundColor Green
$runspacePool.Close() | Out-Null
$runspacePool.Dispose() | Out-Null
} else {
Write-Host "`n`nThread timeout. Attempting to collect output from completed threads..." -ForegroundColor Yellow
}
return $output
}
function RunPatchReport {
function ConvertTime ($time, $timeZone) {
if ($time.IsDayLightSavingTime()) {$offset_01 = 0} else {$offset_01 = 1}
$offset_02 = switch -Wildcard ($timeZone) {"Eastern*" {4};"Central*" {5};"Mountain*" {6};"Pacific*" {7}}
$offset = New-TimeSpan -Hours ([int32]$offset_01 + [int32]$offset_02)
return ($time - $offset)
}
$ErrorActionPreference = "SilentlyContinue"
$scriptBlock = {
param ($servers)
$ErrorActionPreference = "SilentlyContinue"
foreach ($server in $servers) {
$myTimeZone = Get-WmiObject -Class Win32_TimeZone -Property Caption -ComputerName $server | select -ExpandProperty Caption | foreach {$_.Split(")")[1]} | foreach {($_.Split("(")[0]).Trim()}
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $server
$caption = ($os.Caption).Replace(",", "").Replace("(R)", "").Replace("Microsoft ", "").Replace(" Edition", "").Replace("Datacenter", "DC").Replace("Standard", "Std").Replace("Enterprise", "Ent").Replace("without", "w/o").Replace("Windows ", "Win ").Replace("Server ", "Srv ").Trim()
$lastBootTime = $os | Select-Object @{n="LastBootUpTime";e={(([wmi]"").ConvertToDateTime($_.LastBootUpTime)).ToString("MM/dd/yyyy hh:mm:ss tt")}} | Select-Object -ExpandProperty LastBootUpTime
$installed = get-hotfix -ComputerName $server | Sort-Object -Property InstalledOn
$lastPatchInstallTime = $installed | select @{n="InstalledOn";e={ConvertTime -time $_.InstalledOn -timeZone $myTimeZone}} | Select-Object -ExpandProperty InstalledOn -Last 1
$installedInPast24Hours = $installed | Where-Object {$_.InstalledOn -ge [datetime]::Today.AddHours(-168)} | Select-Object @{n="MyData";e={"$(($_.InstalledOn).ToString("MM/dd/yyyy hh:mm:ss tt")) - $($_.Title)"}} | Select-Object -ExpandProperty MyData
if ($installedInPast24Hours) {$installedInPast24HoursCount = @($installedInPast24Hours).Count} else {$installedInPast24HoursCount = 0}
if (Get-Item -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending") {
$pendingReboot = $true
} elseif (Get-Item -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootInProgress") {
$pendingReboot = $true
} elseif (Get-Item -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") {
$pendingReboot = $true
} elseif (Get-Item -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\PostRebootReporting") {
$pendingReboot = $true
} elseif (Get-Item -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") {
$pendingReboot = $true
} else {
if (($lastBootTime) -lt ($lastPatchInstallTime)) {
$pendingReboot = $true
} else {
$pendingReboot = $false
}
}
$output = "" | Select-Object `
@{n="Server";e={$server}},
@{n="Operating System";e={$caption}},
@{n="Pending Reboot";e={$pendingReboot}},
@{n="Last Boot Time";e={$lastBootTime}},
@{n="Last Patch Install Time";e={$lastPatchInstallTime}},
@{n="Installed (Past 7 Days)";e={$installedInPast24HoursCount}}
}
return $output
}
StartThreads -servers $servers -scriptBlock $scriptBlock
}
$servers = Get-Content -Path C:\temp\week1.txt
RunPatchReport
Expected output and what it looks like when I run it remotely against a computer with just the code.
Server : TS3M360FSPRD01
Operating System : Win Srv 2012 R2 Std
Pending Reboot : True
Last Boot Time : 01/10/2023 03:00:53 AM
Last Patch Install Time : 1/23/2023 7:00:00 PM
Installed (Past 7 Days) : 1
Output from running with threads(runspace pool)
Server : TS3M360FSPRD01
Operating System : Win Srv 2012 R2 Std
Pending Reboot : False
Last Boot Time : 01/10/2023 03:00:53 AM
Last Patch Install Time :
Installed (Past 7 Days) : 1
One other thing to note, the other checks for the registry keys giving indication of a reboot never seem to be consistent, so that’s why there’s the final check for last boot related to last patch install time. If the registry key check was consistent, I wouldn’t be in the situation.
I appreciate any help.