0

Can someone help me by taking this code and making it run as Job/Runspace so it runs on all systems and reports back? This works when I run against a list of servers, but takes for ever as it goes 1 by 1. I want to have all servers run through and append to CSV files. Am I thinking right here or what are your thoughts?

$CurrentDir = split-path -Path $MyInvocation.MyCommand.Definition -Parent

## This is the location the script will save the output file
$OutputFile = "$CurrentDir\PatchStatus.htm"

$Computers = Get-Content "$CurrentDir\Servers.txt"
$Date = (Get-Date).AddDays(-30)

Function Get-RebootEvents
{
    [CmdletBinding()]
    Param (
        [string[]]$ComputerName = $env:COMPUTERNAME
    )
    try
    {
        $RebootEvents = Get-WinEvent -ComputerName $Computer -FilterHashTable @{ LogName = "System"; StartTime = $Date; ID = 1074, 1076 } -ErrorAction Stop
    }
    catch
    {
        if ($_.Exception.Message -match "No events were found that match the specified selection criteria")
        {
            $ErrorMessage = "$Computer - $($Error[0])"
        }
    }
    IF (($ErrorMessage) -or ($RebootEvents -eq $null))
    {
        $RebootObject = [PSCustomObject]@{
            'Date'       = $null
            'User'       = $null
            'Action'     = $null
            'Process'    = $null
            'Reason'     = $null
            'ReasonCode' = $null
            'Comment'    = $ErrorMessage
        }
        $RebootObject
    }
    ELSE
    {
        ForEach ($RebootEvent in $RebootEvents)
        {
            $RebootObject = [PSCustomObject]@{
                'Date' = Get-Date -UFormat "%m/%d/%Y %r" $RebootEvent.TimeCreated
                'User' = $RebootEvent.Properties[6].Value
                'Action' = $RebootEvent.Properties[0].Value
                'Process' = $RebootEvent.Properties[4].Value
                'Reason' = $RebootEvent.Properties[2].Value
                'ReasonCode' = $RebootEvent.Properties[3].Value
                'Comment' = $RebootEvent.Properties[5].Value
            }
            $RebootObject
        }
    }
}

Function Get-UpdateEvents
{
    [CmdletBinding()]
    Param (
        [string[]]$ComputerName = $env:COMPUTERNAME
    )
    try
    {
        $UpdateEvents = Get-WinEvent -ComputerName $Computer -FilterHashTable @{ LogName = "System"; StartTime = $Date; ID = 19, 20 } -ErrorAction Stop | Where { $_.Message -notlike "*Defender*" -and ($_.Message -like "Installation*") -and ($_.Message -notlike "*Malicious*") } | Select TimeCreated, Message
    }
    catch
    {
        if ($_.Exception.Message -match "No events were found that match the specified selection criteria")
        {
            $ErrorMessage = "$Computer - $($Error[0])"
        }
    }
    IF (($ErrorMessage) -or ($UpdateEvents -eq $null))
    {
        $UpdateObject = [PSCustomObject]@{
            'Date'    = $null
            'Status'  = $null
            'Message' = $ErrorMessage
            'KB'      = $null
        }
        $UpdateObject
    }
    ELSE
    {
        ForEach ($UpdateEvent in $UpdateEvents)
        {
            $UpdateMessage = $UpdateEvent.Message -split '(?<=:\s*\S+)\s+'
            $UpdateStatus = $UpdateMessage[0] -split ":"
            $Status = $UpdateStatus[0]
            $Message = $UpdateMessage[2]
            $KB = [regex]::matches($Message, '(?<=\().+?(?=\))').value
            Try
            {
                #$UpdateDate = Get-Date -UFormat "%m/%d/%Y %r" $UpdateEvent.TimeCreated
                $UpdateDate = $UpdateEvent.TimeCreated
            }
            CATCH
            {
                if ($_.Exception.Message -like "*Cannot convert null to type *System.DateTime*")
                {
                    $ErrorMessage = "$Computer - $($Error[0])"
                    $UpdateDate = $null
                    Write-Host $ErrorMessage
                }
            }
            
            $UpdateObject = [PSCustomObject]@{
                'Date'    = $UpdateDate
                'Status'  = $Status
                'Message' = $Message
                'KB'      = $KB
            }
            $UpdateObject
        }
    }
}

Function Get-OSInformation
{
    [CmdletBinding()]
    Param (
        [string[]]$ComputerName = $env:COMPUTERNAME
    )
    
    try
    {
        $OS = Get-WmiObject Win32_OperatingSystem -ComputerName $Computer -ErrorAction Stop
    }
    catch
    {
        $ErrorMessage = "$($Error[0])"
        
    }
    IF (($ErrorMessage) -or ($OS -eq $null))
    {
        $OSObject = [PSCustomObject]@{
            'OS'         = $ErrorMessage
            'OSVersion'  = $null
            'Lastboot'   = $null
            'UptimeDays' = $null
        }
        $OSObject
    }
    ELSE
    {
        $Uptime = (Get-Date) - $OS.ConvertToDateTime($OS.LastBootUpTime)
        $OSObject = [PSCustomObject]@{
            'OS'         = $OS.Caption
            'OSVersion'  = $OS.Version
            'Lastboot'   = $OS.ConvertToDateTime($OS.LastBootUpTime)
            'UptimeDays' = ([String]$Uptime.Days)
        }
        $OSObject
    }
}

Function Get-PendingRebootStatus
{
    
<#
.Synopsis
    This will check to see if a server or computer has a reboot pending.
    For updated help and examples refer to -Online version.
  
 
.DESCRIPTION
    This will check to see if a server or computer has a reboot pending.
    For updated help and examples refer to -Online version.
 
 
.NOTES  
    Name: Get-PendingRebootStatus
    Author: The Sysadmin Channel
    Version: 1.00
    DateCreated: 2018-Jun-6
    DateUpdated: 2018-Jun-6
 
.LINK
    https://thesysadminchannel.com/remotely-check-pending-reboot-status-powershell -
 
 
.PARAMETER ComputerName
    By default it will check the local computer.
 
     
    .EXAMPLE
    Get-PendingRebootStatus -ComputerName PAC-DC01, PAC-WIN1001
 
    Description:
    Check the computers PAC-DC01 and PAC-WIN1001 if there are any pending reboots.
 
#>
    
    [CmdletBinding()]
    Param (
        [Parameter(
                   ValueFromPipeline = $true,
                   ValueFromPipelineByPropertyName = $true,
                   Position = 0)]
        [string[]]$ComputerName = $env:COMPUTERNAME,
        [switch]$ShowErrors
        
    )
    
    
    BEGIN
    {
        $ErrorsArray = @()
    }
    
    PROCESS
    {
        foreach ($Computer in $ComputerName)
        {
            try
            {
                $PendingReboot = $false
                
                
                $HKLM = [UInt32] "0x80000002"
                $WMI_Reg = [WMIClass] "\\$Computer\root\default:StdRegProv"
                
                if ($WMI_Reg)
                {
                    if (($WMI_Reg.EnumKey($HKLM, "SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\")).sNames -contains 'RebootPending') { $PendingReboot = $true }
                    if (($WMI_Reg.EnumKey($HKLM, "SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\")).sNames -contains 'RebootRequired') { $PendingReboot = $true }
                    
                    #Checking for SCCM namespace
                    $SCCM_Namespace = Get-WmiObject -Namespace ROOT\CCM\ClientSDK -List -ComputerName $Computer -ErrorAction Ignore
                    if ($SCCM_Namespace)
                    {
                        if (([WmiClass]"\\$Computer\ROOT\CCM\ClientSDK:CCM_ClientUtilities").DetermineIfRebootPending().RebootPending -eq 'True') { $PendingReboot = $true }
                    }
                    
                    
                    if ($PendingReboot -eq $true)
                    {
                        $Properties = @{
                            ComputerName  = $Computer.ToUpper()
                            PendingReboot = 'True'
                        }
                        $Object = New-Object -TypeName PSObject -Property $Properties | Select ComputerName, PendingReboot
                    }
                    else
                    {
                        $Properties = @{
                            ComputerName  = $Computer.ToUpper()
                            PendingReboot = 'False'
                        }
                        $Object = New-Object -TypeName PSObject -Property $Properties | Select ComputerName, PendingReboot
                    }
                }
                
            }
            catch
            {
                $Properties = @{
                    ComputerName  = $Computer.ToUpper()
                    PendingReboot = 'Error'
                }
                $Object = New-Object -TypeName PSObject -Property $Properties | Select ComputerName, PendingReboot
                
                $ErrorMessage = $Computer + " Error: " + $_.Exception.Message
                $ErrorsArray += $ErrorMessage
                
            }
            finally
            {
                Write-Output $Object
                
                $Object = $null
                $ErrorMessage = $null
                $Properties = $null
                $WMI_Reg = $null
                $SCCM_Namespace = $null
            }
        }
        if ($ShowErrors)
        {
            Write-Output "`n"
            Write-Output $ErrorsArray
        }
    }
    
    END { }
}

function Get-UpdateStatus
{
    
    Foreach ($Computer in $Computers)
    {
        
        $OnlineStatus = "Offline"
        Write-Host "Testing Connection to $Computer"
        $IPAddress = Test-Connection -ComputerName $Computer -Count 1 -Quiet
        if ($IPAddress)
        {
            
            $OnlineStatus = "Online"
            $Rebootedby = (Get-RebootEvents -ComputerName $Computer | Sort Date -Descending | Select -First 1).User
            ### Adding [PSObject[]] Forces this to be an array only no matter how many objects
            [PSObject[]]$UpdateEvents = Get-UpdateEvents -ComputerName $Computer | select Date, Status, Message, KB, ErrorMessage
            $OSInformation = Get-OSInformation -ComputerName $Computer | select OS, OSVersion, Lastboot, UptimeDays
            $RebootInformation = Get-PendingRebootStatus -ComputerName $Computer | select ComputerName, PendingReboot
            $infoColl = @()
            ForEach ($UpdateEvent in $UpdateEvents)
            {
                $infoObject = New-Object PSObject
                $infoObject | add-member -memberType NoteProperty -name "ComputerName" -value $Computer
                $infoObject | add-member -memberType NoteProperty -name "OnlineStatus" -value $OnlineStatus
                $infoObject | add-member -memberType NoteProperty -name "OS" -value $OSInformation.OS
                $infoObject | add-member -memberType NoteProperty -name "OSVersion" -value $OSInformation.OSVersion
                $infoObject | add-member -memberType NoteProperty -name "PendingReboot" -value $RebootInformation.PendingReboot
                $infoObject | add-member -memberType NoteProperty -name "LastBoot" -value $OSInformation.Lastboot
                $infoObject | add-member -memberType NoteProperty -name "Rebootedby" -value $Rebootedby
                $infoObject | add-member -memberType NoteProperty -name "Uptime_Days" -value $OSInformation.UptimeDays
                $infoObject | add-member -memberType NoteProperty -name "Date" -value $UpdateEvent.Date
                $infoObject | add-member -memberType NoteProperty -name "Status" -value $UpdateEvent.Status
                $infoObject | add-member -memberType NoteProperty -name "Message" -value $UpdateEvent.Message
                $infoObject | add-member -memberType NoteProperty -name "KB" -value $UpdateEvent.KB
                $infoObject | add-member -memberType NoteProperty -name "ErrorMessage" -value $UpdateEvent.ErrorMessage
                $infoColl += $infoObject
                $infoObject
            }
        }
        ELSE
        {
            Write-Host "$Computer is $OnlineStatus"
            $infoObject = New-Object PSObject
            $infoObject | add-member -memberType NoteProperty -name "ComputerName" -value $Computer
            $infoObject | add-member -memberType NoteProperty -name "OnlineStatus" -value $OnlineStatus
            $infoObject | add-member -memberType NoteProperty -name "OS" -value $null
            $infoObject | add-member -memberType NoteProperty -name "OSVersion" -value $null
            $infoObject | add-member -memberType NoteProperty -name "PendingReboot" -value $null
            $infoObject | add-member -memberType NoteProperty -name "LastBoot" -value $null
            $infoObject | add-member -memberType NoteProperty -name "Rebootedby" -value $null
            $infoObject | add-member -memberType NoteProperty -name "Uptime_Days" -value $null
            $infoObject | add-member -memberType NoteProperty -name "Date" -value $null
            $infoObject | add-member -memberType NoteProperty -name "Status" -value $null
            $infoObject | add-member -memberType NoteProperty -name "Message" -value $null
            $infoObject | add-member -memberType NoteProperty -name "ErrorMessage" -value $null
            $infoColl += $infoObject
            $infoObject
        }
    }
}

Get-UpdateStatus | Export-Csv -path "$CurrentDir\Update_Status_$((Get-Date).ToString('MM-dd-yyyy')).csv" -Append -NoTypeInformation
  • [`Get-WmiObject`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-wmiobject) accepts an _array_ of computer names as its `-ComputerName` argument, which results in _parallel_ processing. The same goes for `Get-CimInstance` and for the general-purpose [`Invoke-Command`](https://learn.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Core/Invoke-Command). – mklement0 Jun 07 '23 at 18:54
  • As an aside: The CIM cmdlets (e.g., `Get-CimInstance`) superseded the WMI cmdlets (e.g., `Get-WmiObject`) in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell (Core) v6+, where all future effort will go, doesn't even _have_ them anymore. Note that WMI still _underlies_ the CIM cmdlets, however. For more information, see [this answer](https://stackoverflow.com/a/54508009/45375). – mklement0 Jun 07 '23 at 18:54

0 Answers0