Need help reviewing script. Output for one variable not coming through

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.

I reworked a couple lines to get passed the issue at present.
Instead of this;

        $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

I added/changed to this;

        $installed = get-hotfix -ComputerName $server | Sort-Object -Property InstalledOn
        $lastPatch = $installed | select InstalledOn -ExpandProperty InstalledOn -Last 1
        $lastPatchInstallTime = ($lastly).AddHours(-4)

So the issue appears to be with the Convert-Time function, which I can look into, but now I noticed a separate issue.
For background, trying to find ALL of the patches and their install time has been cumbersome since Windows.Update.Session doesn’t show all patches, and it sometimes shows 1899 as the year of install. Get-hotfix shows all patches, but doesn’t show the time it was actually installed. Because of that, any install through Get-hotfix will revert to midnight, but if a server reboots at a certain time, it can look like it requires another reboot when it doesn’t. That’s the reason I have the convert-time function and now the addhours (-4), so it puts it before any timing overlap that could happen with how get-hotfix returns install time.

My new issue is that a server that rebooted in december, but took a patch in january and hasn’t rebooted, shows as False on pending reboot. Don’t know why, it appears to be skipping the year and only going off of the month.

Got the new issue figured out. Still looking at why convert time doesn’t want to work, but the workaround that caused the new issue of not comparing dates correctly was related to this entry;

if (($lastBootTime) -lt ($lastPatchInstallTime))

I changed it to;

if (($lastBootTime | Get-Date) -lt ($lastPatchInstallTime | Get-Date))

and that worked for me.
I’ll update if I can figure out the convert time function since i’d rather have something that goes off of Daylight Savings Time instead of an arbitrary -4 hours.
But as is, the script works as intended.