Screencast 29: Bones

Powershell Script - bones.ps1
1
<#
.SYNOPSIS
    Simple HTML report generator for server status, free RAM, and Disk capacity.
    The report info is injected into a web page. If anything is in the red it'll yell
    at you via email.
.NOTES
    Original script from Martin Pugh (@TheSurlyAdm1n), greatly hacked/rewritten/made worse
    by Tobin Bradley (@fuzzytolerance).
#>

[CmdletBinding()]
Param (
    [Parameter(ValueFromPipeline=$true,
        ValueFromPipelinebyPropertyName=$true)]
    [Alias("Servers")]
    [string[]]$Name = (Get-Content ".\servers.txt"),
    [int]$DriveAlertThreshold = 10,
    [int]$RAMAlertThreshold = 512,
    [string]$Credential = "domain\username",
    [string]$PathToCred = ".\",
    [string]$smtpServer = "servername",
    [string]$publishPath = "\\where\to\put\it\index.html",
    [string]$toMail = "hottub@fun.com"
)

Begin {
    $path = split-path -parent $MyInvocation.MyCommand.Definition
    Set-Location -Path $Path

    Function Get-Credentials {
        Param (
            [String]$AuthUser = $env:USERNAME
        )
        $Key = [byte]29,36,18,74,72,75,85,52,73,44,0,21,98,76,99,27

        #Build the path to the credential file
        $CredFile = $AuthUser.Replace("\","~")
        $File = $PathToCred + "\Credentials-$CredFile.crd"
        #And find out if it's there, if not create it
        If (-not (Test-Path $File)) {
            (Get-Credential $AuthUser).Password | ConvertFrom-SecureString -Key $Key | Set-Content $File
        }
        #Load the credential file
        $Password = Get-Content $File | ConvertTo-SecureString -Key $Key
        $AuthUser = (Split-Path $File -Leaf).Substring(12).Replace("~","\")
        $AuthUser = $AuthUser.Substring(0,$AuthUser.Length - 4)
        $Credential = New-Object System.Management.Automation.PsCredential($AuthUser,$Password)
        Return $Credential
    }

    Function Send-Mail {
        param($To, $From, $Subject, $Body, $mailServer)
        $msg = new-object Net.Mail.MailMessage
        $smtp = new-object Net.Mail.SmtpClient($mailServer)
        $msg.From = $From
        $msg.To.Add($To)
        $msg.Subject = $Subject
        $msg.IsBodyHtml = 1
        $msg.Body = $Body
        $smtp.Send($msg)
    }

    Write-Verbose "$(Get-Date): Script begins!"
    $AllComputers = @()
}

Process {
    #Gather all computer names before processing
    $Name = $Name | Sort
    ForEach ($Computer in $Name) {
        $AllComputers += $Computer
    }
}

End {
    #Sort the servers by name, then start getting information
    Write-Verbose "Sort server names and gather Credential information"

    $DiskData = @()
    $date = Get-Date
    $thedate = "<p>Last Ran " + $date + "</p>"
    $errors = $null

    If ($Credential) {
        $Cred = Get-Credentials $Credential
    }

    ForEach ($Computer in $AllComputers) {
        Write-Verbose "Testing $Computer..."
        $ErrorReport = $null
        $DriveReport = $null
        If (Test-Connection $Computer -Quiet) {
            #Set parameters for splat, determine if checking local
            $CredParameter = @{
                ComputerName = $Computer
                ErrorAction = "Stop"
            }
            If ($Computer.ToUpper() -notlike "*$($env:COMPUTERNAME.ToUpper())*" -and $Cred) {
                $CredParameter.Add("Credential",$Cred)
            }

            #Get uptime and memory information
            Try {
                $WMI = Get-WmiObject Win32_OperatingSystem @CredParameter
                If ($WMI) {
                    $Uptime = New-TimeSpan -Start $($WMI.ConvertToDateTime($WMI.LastBootUpTime)) -End (Get-Date)
                    $UpText = "<td>Up for $($Uptime.Days)d, $($Uptime.Hours)h, $($Uptime.Minutes)m</td>"
                }
                Else {
                    $UpText = "<td class=""up"">Up</td>"
                }
                #Get disk information and pretty up the data
                $Disks = Get-WmiObject Win32_LogicalDisk -Filter "DriveType=3" @CredParameter | Select `
                    @{LABEL="Server";EXPRESSION={$Computer}},
                    @{LABEL="DriveLetter";EXPRESSION={$_.DeviceID}},
                    @{LABEL="Size";EXPRESSION={[int]("{0:N0}" -f ($_.Size/1gb))}},
                    @{LABEL="FreeSize";EXPRESSION={[int]("{0:N0}" -f ($_.FreeSpace/1gb))}},
                    @{LABEL="perUsed";EXPRESSION={[int]("{0:N0}" -f ((($_.Size - $_.FreeSpace)/$_.Size)*100))}},
                    @{LABEL="perFree";EXPRESSION={[int]("{0:N0}" -f (100-(($_.Size - $_.FreeSpace)/$_.Size)*100))}},
                    VolumeName
                $DiskData += $Disks

                # check the ram
                $theram = ([math]::round($WMI.FreePhysicalMemory / 1024))
                if ($theram -ge $RAMAlertThreshold) {
                    $FreeRAM = "<td class=""text-right"">$theram MB</td>"
                }
                else {
                    $FreeRAM = "<td class=""down text-right"">$theram MB</td>"
                    $errors += "Low free RAM on $Computer.<br>"
                }
            }
            Catch {
                Write-Verbose "Error encountered gathering information for $Computer"
                $ErrorReport = $Error[0]
                $Error.Clear | Out-Null
            }

            #Create the simple Status table
            If ($ErrorReport) {
                $UpText = "<td class=""down"">WMI Error</td>"
                $DiskHTML = "<div class=""red"">$($Error[0])</div>"
            }
            ElseIf ($Disks) {
                $DiskHTML = $null
                ForEach ($Disk in $Disks) {
                    If ($Disk.perFree -le $DriveAlertThreshold) {
                        $FreeClass = "red"
                        $errors += "$Computer has less than 10% free space on drive $($Disk.DriveLetter) <br>"
                    }
                    Else {
                        $FreeClass = "free"
                    }
                    $DiskHTML += "<div class=""green"" style=""width:$($Disk.perUsed)%"">$($Disk.DriveLetter) $($Disk.Size)gb</div><div class=""$FreeClass"" style=""width:$($Disk.perFree)%"">$($Disk.FreeSize)gb ($($Disk.perFree)%)</div>`n<div style=""clear:both""></div>"
                }
            }
            Else {
                $DiskHTML = ""
            }
            $DetailHTML += "<tr><td>$Computer</td>$UpText $FreeRAM<td>$DiskHTML</td></tr>`n"
        }
        Else {
            $DetailHTML += "<tr><td>$Computer</td><td class=""down"">DOWN</td><td class=""down""></td><td class=""down""></td></tr>`n"
            $errors += "$Computer is down.<br>"
        }
    }

    # Insert Stuff into index.html
    (Get-Content .\index.html) -join "" | foreach{$_ -replace "<!-- Server Report -->(.*?)<!-- End Server Report -->","<!-- Server Report -->$DetailHTML<!-- End Server Report -->"} | Set-Content ./index.html
    (Get-Content .\index.html) -join "" | foreach{$_ -replace "<!-- Updated -->(.*?)<!-- End Updated -->","<!-- Updated -->$date<!-- End Updated -->"} | Set-Content ./index.html

    # Copy index.html to web server
    Copy-Item -Path .\index.html -Destination $publishPath

    #Send email if shit went bad
    if ($errors) {
        Send-Mail -To $toMail -From "noreply@noreply.com"  -Subject "He's dead Jim!" -Body "<h1>Oh shit.</h1> <p> $errors </p> <p>Head to http://maps.co.mecklenburg.nc.us/bones for more information.</p>" -mailServer $smtpServer
    }

    Write-Verbose "$(Get-Date): Script completed!"
}
Sample Index.html
1
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW">
    <title>Page Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Bootstrap -->
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
    <style>
        img {
            max-width: 100%;
        }
        header {
            margin: 75px 0 40px;
        }
        footer {
            background: #d42127;
            margin-top: 20px;
            box-shadow: 0 -2px 10px #666;
        }
        nav {
            box-shadow: 0 2px 10px #666;
        }
        td, td.div { white-space: nowrap; }
        td.div { overflow: hidden; }
        td.up { background-color:#32CD32; }
        td.down { background-color:#B22222; }
        div.red { background-color:#B22222;
          float:left;
          text-align:right;
        }
        div.green {
            background-color:#32CD32;
            float:left;
        }
        div.free {
          background-color:#7FFF00;
          float:left;
          text-align:right;
        }
        span.green {
            background: #32CD32;
        }
        span.free {
            background: #7FFF00;
        }
        .up, .down, .red, .green, .free {
            padding: 5px;
        }

    </style>
</head>

<body>
    <div class="container">
        <header class="text-center">
            Updated <!-- Updated --><!-- End Updated -->
        </header>
        <div>
            <h2>Server Report</h2>
            <table class="table">
                <thead>
                    <tr>
                        <th>Server</th>
                        <th>Status</th>
                        <th class="text-right">Free Ram</th>
                        <th style="width: 60%">Disk Status (
                            <span class="green">Capacity</span>/
                            <span class="free">Free</span>)</th>
                    </tr>
                </thead>
                <tbody>
                    <!-- Server Report --><!-- End Server Report -->
                </tbody>
            </table>
        </div>
    </div>
</body>

</html>

Resources