Windows Server health check script

I know that many of us have experience with Priority 1 (or Critical) tickets. You do not have  much time for troubleshooting, but you need some “answers”.

Answers for questions like:

  • Which version of powershell is installed on the server?
  • What is the server’s name?
  • When was the server booted last time?
  • How many Gigs are free on system drive?
  • What are the last XX errors in System log?
  • What is the reason for last XX shutdowns/restarts?
  • Which processes use the most CPU and MEM?
  • etc.

This script provides you the basic report of health of your server and exports for you the System, Application and Security log to the CSV format. These logs are also compressed because of the size reduction.

Health Information about server, System and Application log are exported to the HTML format, so you could have complex view of your server stats. Maybe you want to ask, why I do not mention the Security Log. Answer is simple: Security log is too big (MBs) for this type of reports, so I decided to skip it.

System and Application logs are also exported to the HMTL format for better reading experience. The both logs are also zipped for the next troubleshooting.

All of the exported/compressed files are zipped to the one zip file.

If .NET class for zipping files is not present on your server, script will use 7zip.  If you need to use 7zip, you have to download the 7zip package  and save/extract this package to this location “C:\Temp\HealthCheck\7z1900-extra\7za.exe”. Location of 7za.exe is possible to change in variable $7zipEXE of course. Or you can use:

$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath

The whole thing is as simple as writing the two lines of code mentioned above. They will create the object $dir which always point to the path from where this script was run.

Part of the script where do you use .NET zipping method or 7zip:

$ZipFileClass = Get-ChildItem 'C:\Windows\Microsoft.NET\*' -Recurse -Filter "system.io.compression.filesystem.dll"

if ($ZipFileClass.Exists -ne $true)
{
SevenzipFiles "C:\Temp\HealthCheck-$Date\SysAppSecLogs-$env:computername-$date.zip" "C:\Temp\HealthCheck-$Date\Logs-$Date\"
SevenzipFiles "C:\Temp\HCreport-$env:computername-$date.zip" "C:\Temp\HealthCheck-$Date\"
}
else{
ZipFiles "C:\Temp\HealthCheck-$Date\SysAppSecLogs-$env:computername-$date.zip" "C:\Temp\HealthCheck-$Date\Logs-$Date\"
ZipFiles "C:\Temp\HCreport-$env:computername-$date.zip" "C:\Temp\HealthCheck-$Date\

The whole script in one block:

.SYNOPSIS

.DESCRIPTION

.PARAMETER SysLogTopErr
Number of last System Log Errors, which do you want to see in HTML report.
.PARAMETER ShutRebTop
Number of last Shutdowns/Reboots, which do you want do see in HTML report.
.PARAMETER ProcessCPUTop
Number of processes that have the most CPU usage.
.PARAMETER ProcessMEMTop
Number of processes that have the most Memory usage.
.PARAMETER EventsDays
Number of days of the history of logs, which do you want to see in HTML report.
.EXAMPLE
.\HealthCheck.ps1 -SysLogTopErr 8 -ShutRebTop 8 -ProcessCPUTop 3 -ProcessMEMTop 3 -EventsDays 2
.NOTES
Martin

#>

param (
       [int]$SysLogTopErr = 5,
       [int]$ShutRebTop = 5,
       [int]$ProcessCPUTop = 10,
       [int]$ProcessMEMTop = 10,
       [int]$EventsDays = 1
)
function TestAdministrator
{
       $user = [Security.Principal.WindowsIdentity]::GetCurrent();
       (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
$TestAdmin = TestAdministrator
if ($TestAdmin -ne $true)
{
       Write-Host "Powershell is not running with Administrator priviliges. Run powershell as Administrator."
       break
}

Write-host "Health Check starts $(Get-Date)"

$ping = New-Object System.Net.NetworkInformation.Ping
$strDomainDNS = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
$OS = Get-WmiObject Win32_OperatingSystem
$SystemDisk = get-WmiObject win32_logicaldisk -Filter "DeviceID='C:'"
$LastBoot = $OS.LastBootUpTime
$Date = Get-Date -Format "MM-dd-yyyy-HHmm"
$ZipFileClass = Get-ChildItem 'C:\Windows\Microsoft.NET\*' -Recurse -Filter "system.io.compression.filesystem.dll"
$7zipEXE = "C:\Temp\HealthCheck\7z1900-extra\7za.exe"

$HealthCheckDir = Get-ChildItem -Path C:\Temp\HealthCheck-$Date\ -ErrorAction SilentlyContinue
$HealthCheckLogsDir = Get-ChildItem -Path C:\Temp\HealthCheck-$Date\Logs-$Date\ -ErrorAction SilentlyContinue

if ($HealthCheckDir.Exists -ne $true)
{
       New-Item -ItemType Directory -Path C:\Temp\HealthCheck-$Date\
}
if ($HealthCheckLogsDir.Exists -ne $true)
{
       New-Item -ItemType Directory -Path C:\Temp\HealthCheck-$Date\Logs-$Date\
}

Get-EventLog -LogName System | Export-Csv "C:\Temp\HealthCheck-$Date\Logs-$Date\SystemLog-$Date.csv"
Get-EventLog -LogName Application | Export-Csv "C:\Temp\HealthCheck-$Date\Logs-$Date\ApplicationLog-$Date.csv"
Get-EventLog -LogName Security | Export-Csv "C:\Temp\HealthCheck-$Date\Logs-$Date\SecurityLog-$Date.csv"

function ZipFiles( $zipfilename, $sourcedir )
{
       Add-Type -AssemblyName System.IO.Compression.FileSystem
       $compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
       [System.IO.Compression.ZipFile]::CreateFromDirectory($sourcedir, $zipfilename, $compressionLevel, $false)
}

function SevenzipFiles([String] $zipfilename, [String] $sourcedir){
       #change the path where you downloaded the 7z exe
       [string]$pathToZipExe = "$($7zipEXE)";
       [Array]$arguments = "a", "-tzip", "$zipfilename", "$sourcedir";
       & $7zipEXE $arguments;
}

$domainping = $ping.send($strDomainDNS)

$style = "<style>BODY{font-family:Arial;font-size:10pt;}"
$style = $style + "TABLE{border:1px solid black;border-collapse:collapse;}"
$style = $style + "TH{border:1px solid black;background:#dddddd;padding:5px;}"
$style = $style + "TD{border:1px solid black;padding:5px;}"
$style = $style + "tr:nth-child(odd) { background-color:#d3d3d3;}"
$style = $style + "tr:nth-child(even) { background-color:white;}"
$style = $style + "</style>"

$body = "<body><b>Naposledy generovane:</b> $(Get-Date) <br><br>"
$body = $body + "<body><b>Powershell version:</b> $($Host.version) <br>"
$body = $body + "<body><b>Computer Name:</b> $($env:computername) <br>"
$body = $body + "<body><b>Domain:</b> $($strDomainDNS) <br>"
$body = $body + "<body><b>Domain Ping:</b> $($DomainPing.Status) <br>"
$body = $body + "<body><b>Last Boot Time:</b> $($OS.ConvertToDateTime($LastBoot)) <br>"
$body = $body + "<body><b>System disk free space (GB):</b> $([math]::round($SystemDisk.FreeSpace/1GB, 3)) <br><br>"
$body = $body + "<body><a href='C:\Temp\HealthCheck-$Date\SystemLogReport-$Date.html'>System Log </a> </br>"
$body = $body + "<body><a href='C:\Temp\HealthCheck-$Date\AppLogReport-$Date.html'>Application Log </a> </br>"
$body = $body + "</head>"

$ErrorsSystemHTML = Get-EventLog -LogName System -EntryType Error | Select-Object -Last $SysLogTopErr | ConvertTo-Html -Property TimeGenerated,EntryType,Source,Message -Title "Last $($SysLogTopErr) Errors in System Log" -PreContent "<h1>Last $($SysLogTopErr) Errors in System Log</h1>"

$SystemLog = Get-EventLog -LogName System -EntryType Information,Error,Warning -After (Get-Date).AddDays(-$EventsDays)
$AppLog = Get-EventLog -LogName Application -EntryType Information,Error,Warning -After (Get-Date).AddDays(-$EventsDays)

$SystemLogHTML = $SystemLog | ConvertTo-Html -Property TimeGenerated,EntryType,Source,Message -Title "System Log" -Head $style -PreContent "<h1>System Log</h1>" | Out-File -FilePath C:\Temp\HealthCheck-$Date\SystemLogReport-$Date.html
$AppLogHTML = $AppLog | ConvertTo-Html -Property TimeGenerated,EntryType,Source,Message -Title "Application Log" -Head $style -PreContent "<h1>Application Log</h1>" | Out-File -FilePath C:\Temp\HealthCheck-$Date\AppLogReport-$Date.html

$RestartShutdownEventHTML = Get-EventLog -LogName System | Where-Object {($_.EventID -eq 1074)} | Select-Object -Last $ShutRebTop | ConvertTo-Html -Property TimeGenerated,UserName,EventID,Message -Title "Last $($ShutRebTop) restart/shutdown event in System Log" -PreContent "<h1>Last $($ShutRebTop) restart/shutdown event in System Log</h1>"

$cpu = Get-Counter '\Processor(_Total)\% Processor Time' -SampleInterval 1 -MaxSamples 10
$mem = Get-Counter '\Memory\% Committed Bytes In Use' -SampleInterval 1 -MaxSamples 10

$cpuHTML = $cpu.CounterSamples | ConvertTo-Html -Property Path,CookedValue -Title "CPU Counter" -PreContent "<h1>CPU Counter</h1>"
$memHTML = $mem.CounterSamples | ConvertTo-Html -Property Path,CookedValue -Title "MEM Counter" -PreContent "<h1>MEM Counter</h1>"

$Top10ProcessesCPUHTML = Get-Process | Sort-Object CPU -desc | Select-Object -first $ProcessCPUTop | ConvertTo-Html -Property ProcessName,Company,Description,Product -Title "Top $($ProcessCPUTop) processes by CPU usage" -PreContent "<h1>Top $($ProcessCPUTop) processes by CPU usage</h1>"

$Top10ProcessesMEMHTML = Get-Process | Sort-Object VirtualMemorySize -desc | Select-Object -first $ProcessMEMTop | ConvertTo-Html -Property ProcessName,Company,Description,Product -Title "Top $($ProcessMEMTop) processes by MEM usage" -PreContent "<h1>Top $($ProcessMEMTop) processes by MEM usage</h1>"

ConvertTo-HTML -body "$body $ErrorsSystemHTML $RestartShutdownEventHTML $cpuHTML $memHTML $Top10ProcessesCPUHTML $Top10ProcessesMEMHTML" -Head $style | Out-File -FilePath C:\Temp\HealthCheck-$Date\HCreport-$env:computername-$date.html

if ($ZipFileClass.Exists -ne $true)
{
       SevenzipFiles "C:\Temp\HealthCheck-$Date\SysAppSecLogs-$env:computername-$date.zip" "C:\Temp\HealthCheck-$Date\Logs-$Date\"
       SevenzipFiles "C:\Temp\HCreport-$env:computername-$date.zip" "C:\Temp\HealthCheck-$Date\"
}
else{
       ZipFiles "C:\Temp\HealthCheck-$Date\SysAppSecLogs-$env:computername-$date.zip" "C:\Temp\HealthCheck-$Date\Logs-$Date\"
       ZipFiles "C:\Temp\HCreport-$env:computername-$date.zip" "C:\Temp\HealthCheck-$Date\"
}
Write-host "Health Check ends $(Get-Date)"

I really hope that this script will be helpful for you and if you have any questions, feel free to contact me via twitter or LinkedIN.