#* Version: 2.0 #* Date: 10/28/2022 #* Time: 17:27 PM #* Issue: Unable to process multiple computers with single output #* Solution: Changed the script to use a pipleple process where data is #* collected then outout as objects. #* #*============================================================================= Param( [string]$CsvList, [string]$NameColumn = 'Name', [switch]$ExportResults ) #*============================================================================= #* FUNCTIONS #*============================================================================= Function Get-ServerUptime { <# .SYNOPSIS Calculates the percent uptime for the given server. .DESCRIPTION The getServerUpTime script returns the uptime for a remote or local computer. Without parameters, getServerUptime returns the uptime for the local computer over the past 30 days. .PARAMETER ComputerName Calculates the percent uptime for the specified computers. The default is the local computer. Type the NetBIOS name, an IP address, or a fully qualified domain name of a computer. To specify the local computer, type the computer name, a dot (.), or "localhost". .PARAMETER NumberOfDays The function will calculate the uptime of the computer for the past N days, where N equals the value of NumberOfDays. .EXAMPLE PS C:\> Get-ServerUptime Retrieving shutdown and startup events from MyLaptop for the past 30 days... WARNING: This could take several minutes! Name : MyLaptop Status : Online NumOfDays : 30 NumOfCrashes : 1 NumOfReboots : 18 MinutesDown : 16 hours 20 minutes MinutesUp : 703 hours 40 minutes PercentDowntime : 2.2675% PercentUptime : 97.7325% .EXAMPLE PS C:\> Get-ServerUptime -ComputerName SVR001 -NumberOfDays 365 -Raw Retrieving shutdown and startup events from SVR001 for the past 365 days (time will be in raw format)... WARNING: This could take several minutes! Name : SVR001 Status : Online NumOfDays : 365 NumOfCrashes : 1 NumOfReboots : 13 MinutesDown : 63.15 MinutesUp : 525,536.85 PercentDowntime : 0.0120 % PercentUptime : 99.9880 % .EXAMPLE PS C:\> Get-ADComputer -filter * -Properties * | Select -ExpandProperty Name | Get-ServerUptime -ShowProgress | Format-Table Retrieving shutdown and startup events all systems in AD for the past 365 days (Progress of each server will display)... WARNING: This could take several minutes! Name Status NumOfDays NumOfCrashes NumOfReboots MinutesDown MinutesUp PercentDowntime PercentUptime Details ---- ------ --------- ------------ ------------ ----------- --------- --------------- ------------- ------- Lab-dc2 Online 30 1 13 16 hours 20 minutes 703 hours 40 minutes 2.2675% 97.7325% {Retrieving shutd... LAB-DC3 Online 30 2 26 32 hours 39 minutes 687 hours 21 minutes 4.5350% 95.4650% {Retrieving shutd... LAB-SCCM Denied {No access to the... .EXAMPLE PS C:\> Get-ADComputer -Filter "OperatingSystem -Like '*Windows Server*' -and Enabled -eq 'True'" | Export-Csv $env:USERPROFILE\desktop\serverlist.csv -NoTypeInformation PS C:\> $CsvList = "$env:USERPROFILE\desktop\serverlist.csv" PS C:\> $CsvList.Name | Get-ServerUptime -ShowProgress -Raw Retrieving shutdown and startup events all systems in AD export to list (Progress of each server will display)... WARNING: This could take several minutes! .EXAMPLE PS C:\> 'LAB-DC3','LAB-DC2','LAB-OFFCA','LAB-WAP','LAB-NDES' | Get-ServerUptime -Verbose | Format-Table Retrieving shutdown and startup events all listed systems in verbose data and formatted as a table... WARNING: This could take several minutes! #> [cmdletbinding()] Param( [Parameter(Mandatory=$false,ValueFromPipeline=$true)] [string[]]$ComputerName =$env:COMPUTERNAME, [int] $NumberOfDays = 30, [switch]$Raw, [switch]$ShowProgress ) Begin{ $Status = @() [timespan]$downtime = 0 # Count the number of system crashes $crashCounter = 0 # Count the number of reboots $rebootCounter = 0 $events = $null } Process { #$Name = $ComputerName[0] Foreach($Name in $ComputerName){ If($ShowProgress){Write-Host ('Please wait to generating uptime data for [{0}]...' -f $ComputerName) -NoNewline} # Create a custom object to hold the results $results = "" | Select-Object Name, Status, NumOfDays, NumOfCrashes, NumOfReboots, MinutesDown, MinutesUp, PercentDowntime,PercentUptime,Details $results.Details = @() # Ensure the server is reachable if (Test-Connection -ComputerName $Name -Count 1 -TimeToLive 10 -Quiet) { # Ensure that this is a Windows server that we are working with and # that we have the appropriate permissions if (Test-Path -Path "\\$Name\C$") { # Did the user pass in an appropriate value for number of days? # If not, we will assume the default, 30 days. If the value is # more than 365, we use 365 as the maximum. if ($NumberOfDays -le 0) { $NumberOfDays = 30 $results.Details += "Defaulting to 30 days..." } elseif ($NumberOfDays -gt 365) { $NumberOfDays = 365 $results.Details += "Using maximum value (365 days)..." } # We begin by assuming 100% uptime. We will calculate effective # uptime by subtracting downtime from this value [timespan]$uptime = New-TimeSpan -Days $NumberOfDays $currentTime = Get-Date $startUpID = 6005 $shutDownID = 6006 $minutesInPeriod = $uptime.TotalMinutes $startingDate = (Get-Date).adddays(-$NumberOfDays) # Output some useful debugging info Write-Verbose "========================================" Write-Verbose "Computer: $Name" Write-Verbose "Uptime: $uptime" Write-Verbose "Downtime: $downtime" Write-Verbose "Current time: $currentTime" Write-Verbose "Start time: $startingDate" # Warn the user that this could take a while $results.Details += "Retrieving shutdown and startup events from " $results.Details += "$Name for the past $NumberOfDays days..." $results.Details += "This could take several minutes!" # Create a new PSSession to be used throughout the script # Remotely retrieve the events from the system event log that # occurred in the past $NumberOfDays days ago $events = Invoke-Command $ComputerName $Name -ScriptBlock {` param($days,$up,$down) Get-EventLog ` -After (Get-Date).AddDays(-$days) ` -LogName System ` -Source EventLog ` | Where-Object { $_.eventID -eq $up ` -OR ` $_.eventID -eq $down } } -ArgumentList $NumberOfDays,$startUpID,$shutDownID -ErrorAction Stop # Create a new sorted array object $sortedList = New-object system.collections.sortedlist # If there are shutdown or startup events, add them to the # sorted array, otherwise add zeroes to the array as placeholders if ($events.Count -ge 1) { ForEach($event in $events) { $sortedList.Add( $event.timeGenerated, $event.eventID ) } #end foreach event } else { # There were no shutdown events during this time period $sortedList.Add( 0, 0 ) } # Iterate through the sorted events and add up the downtime For($i = 1; $i -lt $sortedList.Count; $i++ ) { if( ($sortedList.GetByIndex($i) -eq $startupID) -AND ($sortedList.GetByIndex($i) -ne $sortedList.GetByIndex($i-1)) ) { # There was a shutdown event paired to the startup event, # thus it was a planned shutdown # Write each event to the Debug pipeline Write-Verbose "Shutdown `t $($sortedList.Keys[$i-1])" # Shutdown Write-Verbose "Startup `t $($sortedList.Keys[$i])" # Startup # Outage duration = startup timestamp - shutdown timestamp $duration = ($sortedList.Keys[$i] - $sortedList.Keys[$i-1]) $downtime += $duration Write-Verbose " Outage duration: $duration" Write-Verbose " Downtime is now: $downtime" Write-Verbose "" # Bump the reboot counter $rebootCounter++ } elseif( ($sortedList.GetByIndex($i) -eq $startupID) -AND ($sortedList.GetByIndex($i) -eq $sortedList.GetByIndex($i-1)) ) { # This was an unplanned outage (a system crash). # Basically this means that we have 2 startup events # with no shutdown event # Get the date from the event stating that there was an # unexpected shutdown $tempevent = Invoke-Command $ComputerName $Name` -ScriptBlock {` param([datetime]$date, [string]$log) Get-EventLog ` -Before $date.AddSeconds(1) ` -Newest 1 ` -LogName System ` -Source EventLog ` -EntryType Error ` -ErrorAction "SilentlyContinue" | ` Where-Object {$_.EventID -eq 6008} } -ArgumentList $sortedList.Keys[$i],$($eventlog.log) # The 6008 event has the data we're looking for in the # ReplacementStrings property but the date portion of the # data has a special character that we need to remove, # [char]8206, so we replace it with a space. $lastEvent = [datetime](` ($tempevent.ReplacementStrings[1]).Replace([char]8206, " ")` + " " + $tempevent.ReplacementStrings[0]) # Write each event to the Debug pipeline Write-Verbose "CRASH `t $lastEvent" Write-Verbose "Startup `t $($sortedList.Keys[$i])" # Startup # Calculate downtime = Startup timestamp - Last event # written to any log timestamp $duration = ($sortedList.Keys[$i] - $lastEvent) $downtime += $duration Write-Verbose " Outage duration: $duration" Write-Verbose " Downtime is now: $downtime" Write-Verbose "" Write-Verbose "--------------------------------------" # Bump the crash counter $crashCounter++ } } #end for item # Subtract downtime from calculated uptime to get true uptime $uptime -= $downtime $results.Name = $Name $results.NumOfDays = $NumberOfDays $results.NumOfCrashes = $crashCounter $results.NumOfReboots = $rebootCounter If($Raw){ $results.MinutesDown = [math]::Round($downtime.TotalMinutes) $results.MinutesUp = [math]::Round($uptime.TotalMinutes) }Else{ $results.MinutesDown = Format-Duration -Duration ([math]::Round($downtime.TotalMinutes)) $results.MinutesUp = Format-Duration -Duration ([math]::Round($uptime.TotalMinutes)) } $results.PercentDowntime = "{0:p4}" -f (1 - $uptime.TotalMinutes/$minutesInPeriod) $results.PercentUptime = "{0:p4}" -f ($uptime.TotalMinutes/$minutesInPeriod) $results.Status = 'Online' $Status += $results #return $results | Format-Table If($ShowProgress){Write-Host 'Done' -ForegroundColor Green} } else { # This usually means that you've encountered a server that # is not running Windows, like a Linux server $results.Details += "No access to the default share - \\$Name\C`$" $results.Name = $Name $results.Status = 'Denied' $Status += $results $results = $null If($ShowProgress){Write-Host 'Failed' -ForegroundColor Red} } } else { # This server is not online $results.Details += "Unable to connect - $Name" # Create a custom object to hold the results $results.Name = $Name $results.Status = 'Offline' $Status += $results $results = $null If($ShowProgress){Write-Host 'Off' -ForegroundColor Yellow} } } # end loop } # end Process End{ return $Status } } Function Format-Duration ($Duration) { $TimeSpan = New-TimeSpan -Minutes $Duration $h = $TimeSpan.Days * 24 + $TimeSpan.Hours $m = $TimeSpan.Minutes if($h -gt 0){"$h hours $m minutes"} else{"$m minutes"} } #*============================================================================= #* MAIN #*============================================================================= If( $(try { Test-path $CsvList } catch { $false }) ){ $SystemList = Import-Csv $CsvList $Export = $SystemList.$NameColumn | Get-ServerUptime -ShowProgress -Raw }Else{ '-----------------------------------------------------' write-host 'Run for single server: ' -ForegroundColor Cyan -NoNewline write-host 'Get-ServerUptime ' -ForegroundColor White -NoNewline write-host '-Computername ' -ForegroundColor Gray -NoNewline write-host "'DC01'" -ForegroundColor Cyan write-host 'Run for multiple servers: ' -ForegroundColor Cyan -NoNewline write-host "'LAB-DC3','LAB-DC2','LAB-OFFCA','LAB-WAP','LAB-NDES' " -ForegroundColor DarkCyan -NoNewline write-host '| ' -ForegroundColor Gray -NoNewline write-host "Get-ServerUptime " -ForegroundColor White -NoNewline write-host '-ShowProgress ' -ForegroundColor Gray -NoNewline write-host '| ' -ForegroundColor Gray -NoNewline write-host "Format-Table" -ForegroundColor White write-host 'Run from a server list: ' -ForegroundColor Cyan -NoNewline write-host ".\Get-ServerUptime.ps1 " -ForegroundColor White -NoNewline write-host '-CsvList ' -ForegroundColor Gray -NoNewline write-host '$env:UserProfile' -ForegroundColor Cyan -NoNewline write-host '\Desktop\serverlist.csv ' -ForegroundColor DarkCyan -NoNewline write-host '-ExportResults' -ForegroundColor White write-host 'Review more examples: ' -ForegroundColor Cyan -NoNewline write-host "Get-Help " -ForegroundColor White -NoNewline write-host "Get-ServerUptime" -ForegroundColor DarkCyan -NoNewline write-host " -examples" -ForegroundColor White '-----------------------------------------------------' } If($ExportResults){ $Filename = "SystemExport_$(Get-Date -Format 'yyyy-MM-dd_Thh-mm-ss-tt').csv" If($CsvList){ $ExportFolder = Split-Path $CsvList -Parent }Else{ $ExportFolder = "$env:USERPROFILE\desktop" } $Export | Export-Csv "$ExportFolder\$Filename" -NoTypeInformation Write-host ("Exported to {0}" -f "$ExportFolder\$Filename") -ForegroundColor Cyan }