Skip to content

Instantly share code, notes, and snippets.

@figueroadavid
Created March 13, 2026 13:28
Show Gist options
  • Select an option

  • Save figueroadavid/d203df08133ee2b78e73ced2efb2ac57 to your computer and use it in GitHub Desktop.

Select an option

Save figueroadavid/d203df08133ee2b78e73ced2efb2ac57 to your computer and use it in GitHub Desktop.
Checks the status of the EPS Recovery script and emails the results to the administrator
#Requires -RunAsAdministrator
function Test-WillowEPSStatus {
<#
.SYNOPSIS
Sends an email showing the status of the Willow EPS servers based on the
WamEPSRecovery script
.DESCRIPTION
The script reads the WamEpsRecoveryConfig.xml file to determine the list of EPS servers,
retrieves the failover status and print-related service statuses from each server, and compiles
this information into an HTML-formatted email report.
The email is then sent to the specified recipient(s) using the provided SMTP server.
.NOTES
The script must be run with appropriate permissions to access the XML file and query the remote servers.
Ensure that PowerShell remoting is enabled and properly configured on all target servers.
.PARAMETER EPSXMLFilePath
The file path to the WamEpsRecoveryConfig.xml file containing the EPS server configuration.
.PARAMETER SMTPTo
The email address(es) of the recipient(s) for the status report.
.PARAMETER SMTPFrom
The email address of the sender for the status report.
.PARAMETER SMTPSubject
The subject line for the status report email.
.PARAMETER SMTPServer
The SMTP server to use for sending the email.
.EXAMPLE
PS C:\> $WillowEPSStatusParams = @{
EPSXMLFilePath = 'C:\EPSRecovery\WamEpsRecoveryConfig.xml'
SMTPTo = 'administrator@domain.tld'
SMTPFrom = 'EPSRecoveryXML_{0}@domain.tld' -f $env:COMPUTERNAME
SMTPSubject = 'Willow EPS Recovery XML Status - {0}' -f $env:COMPUTERNAME
SMTPServer = 'smtp.domain.tld'
}
PS C:\> Test-WillowEPSStatus @WillowEPSStatusParams
#>
[CmdletBinding()]
param(
[parameter()]
[ValidateScript({ Test-Path -LiteralPath $_ })]
[string]$EPSXMLFilePath = 'C:\EPSRecovery\WamEpsRecoveryConfig.xml',
[parameter()]
[string]$SMTPTo = 'administrator@domain.tld',
[parameter()]
[string]$SMTPFrom = ('EPSRecoveryXML_{0}@domain.tld' -f $env:COMPUTERNAME),
[parameter()]
[string]$SMTPSubject = ('Willow EPS Recovery XML Status - {0}' -f $env:COMPUTERNAME),
[parameter()]
[string]$SMTPServer = 'smtp.domain.tld'
)
begin {
function ConvertTo-HtmlSafeText {
[CmdletBinding()]
param([AllowNull()][string]$Text)
if ($null -eq $Text) { return '' }
return [System.Security.SecurityElement]::Escape($Text)
}
function ConvertTo-FailoverStatus {
[CmdletBinding()]
param([AllowNull()][string]$Status)
$s = ''
if ($null -ne $Status) { $s = $Status.Trim() }
if ($s -eq 'Active' -or $s -eq 'Passive') { return $s }
return 'Unknown'
}
function ConvertTo-ServiceStatus {
[CmdletBinding()]
param([AllowNull()]$Status)
# ServiceControllerStatus enum or string. We normalize to: Running / Stopped / Unknown
if ($null -eq $Status) { return 'Unknown' }
$s = $Status.ToString().Trim()
switch ($s) {
'Running' { 'Running' }
'Stopped' { 'Stopped' }
default { 'Unknown' }
}
}
$Css = @"
body {
font-family: Segoe UI, Arial, sans-serif;
font-size: 12pt;
color: #000000;
}
h2 {
color: #0078D4;
margin: 0 0 10px 0;
}
h3.path {
font-family: Consolas, "Courier New", monospace;
font-size: 11pt;
font-weight: normal;
margin: 0 0 15px 0;
}
table.status {
border-collapse: collapse;
border: 1px solid #CCCCCC;
width: 100%;
max-width: 900px;
}
table.status th,
table.status td {
border: 1px solid #CCCCCC;
padding: 6px;
text-align: left;
vertical-align: top;
white-space: nowrap;
}
table.status th {
font-weight: bold;
background-color: #E5E5E5;
}
/* Alternating rows */
tr.row-odd { background-color: #FFFFFF; }
tr.row-even { background-color: #F2F2F2; }
/* Failover status coloring */
td.failover-Active {
color: #0B5A0B;
background-color: #E7F3E7;
font-weight: 600;
}
td.failover-Passive {
color: #8A1212;
background-color: #FDE7E9;
font-weight: 600;
}
td.failover-Unknown {
color: #7A4B00;
background-color: #FFF4CE;
font-weight: 600;
}
/* Service status coloring */
td.svc-Running {
color: #0B5A0B;
background-color: #E7F3E7;
font-weight: 600;
}
td.svc-Stopped {
color: #8A1212;
background-color: #FDE7E9;
font-weight: 600;
}
td.svc-Unknown {
color: #7A4B00;
background-color: #FFF4CE;
font-weight: 600;
}
"@
function New-WillowEpsStatusHtml {
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$Title,
[Parameter(Mandatory)][string]$FilePath,
[Parameter(Mandatory)][string[]]$ServiceNames,
[Parameter(Mandatory)][object[]]$Rows,
[Parameter()][string]$CssText
)
$sb = [System.Text.StringBuilder]::new()
[void]$sb.AppendLine('<!DOCTYPE html>')
[void]$sb.AppendLine('<html>')
[void]$sb.AppendLine('<head>')
[void]$sb.AppendLine('<meta charset="UTF-8">')
[void]$sb.AppendLine('<style type="text/css">')
[void]$sb.AppendLine($CssText)
[void]$sb.AppendLine('</style>')
[void]$sb.AppendLine('</head>')
[void]$sb.AppendLine('<body>')
[void]$sb.AppendLine((' <h2>{0}</h2>' -f (ConvertTo-HtmlSafeText $Title)))
[void]$sb.AppendLine((' <h3 class="path">XML Path: {0}</h3>' -f (ConvertTo-HtmlSafeText $FilePath)))
[void]$sb.AppendLine(' <table class="status" cellpadding="0" cellspacing="0">')
[void]$sb.AppendLine(' <tr>')
[void]$sb.AppendLine(' <th>Server</th>')
[void]$sb.AppendLine(' <th>Failover Status</th>')
foreach ($svcName in $ServiceNames) {
[void]$sb.AppendLine((' <th>{0}</th>' -f (ConvertTo-HtmlSafeText $svcName)))
}
[void]$sb.AppendLine(' </tr>')
for ($i = 0; $i -lt $Rows.Count; $i++) {
$row = $Rows[$i]
$rowClass = if (($i % 2) -eq 0) { 'row-odd' } else { 'row-even' }
$serverText = ConvertTo-HtmlSafeText $row.Server
$failover = ConvertTo-FailoverStatus $row.FailoverStatus
[void]$sb.AppendLine((' <tr class="{0}">' -f $rowClass))
[void]$sb.AppendLine((' <td>{0}</td>' -f $serverText))
[void]$sb.AppendLine((' <td class="failover-{0}">{1}</td>' -f $failover, (ConvertTo-HtmlSafeText $failover)))
foreach ($svcName in $ServiceNames) {
$svcRaw = $null
if ($row.PSObject.Properties.Match($svcName).Count -gt 0) {
$svcRaw = $row.$svcName
}
$svcStatus = ConvertTo-ServiceStatus $svcRaw
[void]$sb.AppendLine((' <td class="svc-{0}">{1}</td>' -f $svcStatus, (ConvertTo-HtmlSafeText $svcStatus)))
}
[void]$sb.AppendLine(' </tr>')
}
[void]$sb.AppendLine(' </table>')
[void]$sb.AppendLine('</body>')
[void]$sb.AppendLine('</html>')
$sb.ToString()
}
}
process {
# Load local XML
$xml = [xml]::new()
$xml.Load($EPSXMLFilePath)
$EPSServerList = @(
$xml.Settings.FailoverSettings.FailoverNodes.server |
Where-Object { $_ -and ($_ -notmatch $env:COMPUTERNAME) }
)
# Canonical service list (assumed identical across all servers)
$localServices = @(Get-Service -Name 'EpicPrint*' -ErrorAction SilentlyContinue | Sort-Object -Property Name)
$serviceNames = @($localServices.Name)
if (-not $serviceNames -or $serviceNames.Count -eq 0) {
throw "No services matched 'EpicPrint*' on $($env:COMPUTERNAME). Cannot build a consistent multi-service table."
}
# Local row
$localFailoverRaw = $xml.settings.FailoverSettings.ServerStatus
$localRow = [pscustomobject]@{
Server = $env:COMPUTERNAME
FailoverStatus = (ConvertTo-FailoverStatus $localFailoverRaw)
}
foreach ($svcName in $serviceNames) {
$svc = $localServices | Where-Object { $_.Name -eq $svcName } | Select-Object -First 1
$localRow | Add-Member -MemberType NoteProperty -Name $svcName -Value (ConvertTo-ServiceStatus $svc.Status) -Force
}
# Remote rows
$remoteRows = foreach ($server in $EPSServerList) {
# Default row = Unknown everywhere (your requirement)
$row = [pscustomobject]@{
Server = $server
FailoverStatus = 'Unknown'
}
foreach ($svcName in $serviceNames) {
$row | Add-Member -MemberType NoteProperty -Name $svcName -Value 'Unknown' -Force
}
$session = $null
try {
$session = New-PSSession -ComputerName $server -ErrorAction Stop
# Failover status (anything not Active/Passive => Unknown)
$remoteFailoverRaw = Invoke-Command -Session $session -ErrorAction Stop -ScriptBlock {
$remoteXml = [xml]::new()
$remoteXml.Load($using:EPSXMLFilePath)
$remoteXml.settings.FailoverSettings.ServerStatus
}
$row.FailoverStatus = ConvertTo-FailoverStatus $remoteFailoverRaw
# Services: retrieve by canonical names; failures remain Unknown
$remoteSvc = Invoke-Command -Session $session -ErrorAction Stop -ScriptBlock {
Get-Service -Name $using:serviceNames -ErrorAction SilentlyContinue |
Select-Object -Property Name, Status
}
foreach ($svcName in $serviceNames) {
$match = $remoteSvc | Where-Object { $_.Name -eq $svcName } | Select-Object -First 1
if ($null -ne $match) {
$row.$svcName = ConvertTo-ServiceStatus $match.Status
}
else {
$row.$svcName = 'Unknown'
}
}
}
catch {
# Keep Unknowns (already defaulted) per requirement
}
finally {
if ($session) {
Remove-PSSession -Session $session -ErrorAction SilentlyContinue
}
}
$row
}
$allRows = @($localRow) + @($remoteRows)
# Build HTML (manual to preserve CSS classes + alternating rows)
$title = 'Willow EPS Status'
$htmlBody = New-WillowEpsStatusHtml -Title $title -FilePath $EPSXMLFilePath -ServiceNames $serviceNames -Rows $allRows -CssText $Css
$SMTPParams = @{
To = $SMTPTo
From = $SMTPFrom
Subject = $SMTPSubject
Body = $htmlBody
SmtpServer = $SMTPServer
BodyAsHtml = $true
}
Send-MailMessage @SMTPParams
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment