ps-wakeup/wakeup.ps1

228 lines
7.2 KiB
PowerShell

<#
#>
[CmdletBinding()]
param
(
# Array of strings listing computer name(s) to wake up. Names will be matched using RegEx
[Alias("Computer")]
[String[]]
$TargetComputer = "",
# XML database file containing computer details (Default: WOLDatabase.xml)
[Alias("List", "Database")]
[String]
$WolDatabase = "WOLDatabase.xml",
# Skip connectivity check
[Alias("NoCheck", "NoReadiness")]
[Switch]
$NoConnectivityCheck,
# Delay before running first connectivity check (Default: 10)
[Alias("Delay")]
[Int]
$ConnectivityCheckDelay = 10,
# Seconds before aborting connectivity check (Default: 300)
[Alias("Timeout")]
[Int]
$ConnectivityCheckTimeout = 300,
# Seconds between connectivity checks (Default: 30)
[Alias("Interval")]
[Int]
$ConnectivityCheckInterval = 30,
# Port to use for connectivity check (Default: 3389-RDP)
[Alias("CheckPort")]
[Int]
$ConnectivityCheckPort = 3389
)
function exitError($errMessage, $PSItem, $exitCode = 1)
{
if ( [String]::IsNullOrEmpty($errMessage))
{
Write-Host -ForegroundColor Red "`n`rABNORMAL EXIT: exitError function called without exit message!`n`r"
exit 99
}
Write-Host -ForegroundColor Red "`r`nERROR: ${errMessage}."
if ($PSItem)
{
Write-Host -ForegroundColor Red "`r`nAdditional information:`r`n$PSItem"
}
Write-Host
Exit $exitCode
}
function exitGracefully()
{
Write-Host -ForegroundColor Green "`r`nFinished.`r`n"
Exit 0
}
# constants
Set-Variable -Name wolModuleName -Value "wol-magicPacket" -Option Constant
# exit on error if Send-MagicPacket module is not loaded
if (!(Get-Module -Name $wolModuleName))
{
try
{
Import-Module -Name $wolModuleName -ErrorAction Stop
}
catch
{
$errMessage = "Unable to load '$wolModuleName'"
exitError $errMessage $PSItem
}
}
# exit if database cannot be found/read
if (!(Test-Path -Path $WolDatabase -PathType Leaf))
{
$errMessage = "Unable to find or read Wake-On-LAN database file ($WolDatabase)"
exitError $errMessage
}
# get target computer name if not already specified
if ( [String]::IsNullOrWhiteSpace($TargetComputer))
{
do
{
$TargetComputer = Read-Host -Prompt 'Computer to wake-up'
} while ( [String]::IsNullOrWhiteSpace($TargetComputer))
}
Write-Host
# read database and assemble list of target computers
[xml]$wolDb = Get-Content -Path $WolDatabase
$broadcastIP = $wolDb.WOLDatabase.Configuration.BroadcastAddress
$port = $wolDb.WOLDatabase.Configuration.Port
$dnsSuffix = $wolDb.WOLDatabase.Configuration.DnsSuffix
$targetComputers = [System.Collections.Generic.List[PSObject]]::new()
$TargetComputer | ForEach-Object {
$tgt = $_
$wolDb.WOLDatabase.Computers.Computer | Where-Object { $_.name -match $tgt } | ForEach-Object { $targetComputers.Add($_) }
}
$removeFromTargetComputers = [System.Collections.Generic.List[String]]::new()
# exit if nothing to do (i.e. empty targetComputers list)
if ($targetComputers.Count -eq 0)
{
Write-Host -ForegroundColor Yellow "No computers found matching '$TargetComputer'. Nothing to do.`r`n"
exit 0
}
# send WOL magic packets
$targetComputers | ForEach-Object {
$wolError = @()
$name = $_.name
$friendlyName = $_.friendlyName
if ( [String]::IsNullOrWhiteSpace($friendlyName))
{
Write-Host "Processing request to wake-up '$name'... " -NoNewline
}
else
{
Write-Host "Processing request to wake-up '$friendlyName'... " -NoNewline
}
# send magic packet
$_.mac | Send-MagicPacket -BroadcastIP $broadcastIP -Port $port -ErrorAction SilentlyContinue -ErrorVariable +wolError
if ($wolError.Count -eq 0)
{
Write-Host -ForegroundColor Green "[OK]"
}
else
{
Write-Host -ForegroundColor Red "[ERROR]"
# queue for removal from computers on which to run a connectivity check
$removeFromTargetComputers.Add($name)
}
}
# remove computers from targetComputers if WOL packet was not successfully sent
$removeFromTargetComputers | ForEach-Object {
$removeName = $_
$targetComputers.Remove(($targetComputers | Where-Object { $_.name -eq $removeName })) | Out-Null
}
# exit if connectivity check skipped or if no computers to test
if ($NoConnectivityCheck)
{
Write-Host "`r`nSkipping connectivity checks."
exitGracefully
}
elseif ($targetComputers.Count -eq 0)
{
exitError "No wake-up packets sent successfully" -exitCode 5
}
# wait for initial delay seconds to let computer(s) wake up
for ($i = $ConnectivityCheckDelay; $i -gt 0; $i--) {
Write-Progress -Activity "Waiting for computer(s) to wake-up..." -SecondsRemaining $i
Start-Sleep 1
}
Write-Progress -Activity "Waiting for computer(s) to wake-up..." -Completed
# iterate computers and test connectivity
# TODO: run connectivity test as background job
$connCheckTotalTime = 0
$removeFromTargetComputers.Clear()
do
{
$targetComputers | ForEach-Object {
$name = $_.name
$friendlyName = $_.friendlyName
$fqdn = -join ($name, $dnsSuffix)
$connectionError = @()
if ( [String]::IsNullOrWhiteSpace($friendlyName))
{
Write-Host "Testing connection readiness of '$name'... " -NoNewline
}
else
{
Write-Host "Testing connection readiness of '$friendlyName'... " -NoNewline
}
if (!(Test-NetConnection -ComputerName $fqdn -Port $ConnectivityCheckPort -InformationLevel Quiet -WarningAction SilentlyContinue -ErrorAction SilentlyContinue -ErrorVariable +connectionError))
{
if (($connCheckTotalTime + $ConnectivityCheckInterval) -gt $ConnectivityCheckTimeout)
{
# last iteration, display 'error'
Write-Host -ForegroundColor Red "[ERROR]"
$connectionError | ForEach-Object {
$errMsg = $_.ToString()
Write-Verbose "`t(Additional information: $errMsg)"
}
}
else
{
# more iterations pending, display 'retrying'
Write-Host -ForegroundColor Yellow "[RETRYING]"
}
}
else
{
Write-Host -ForegroundColor Green "[OK]"
# queue for removal from computers to check next round
$removeFromTargetComputers.Add($name)
}
}
# remove computers from targetComputers if connectivity check already successful
$removeFromTargetComputers | ForEach-Object {
$removeName = $_
$targetComputers.Remove(($targetComputers | Where-Object { $_.name -eq $removeName })) | Out-Null
}
# sleep until next test interval and increase time counter
$connCheckTotalTime += $ConnectivityCheckInterval
if ($connCheckTotalTime -le $ConnectivityCheckTimeout -and $targetComputers.Count -gt 0)
{
for ($i = $ConnectivityCheckInterval; $i -gt 0; $i--) {
Write-Progress -Activity "Waiting for next connectivity check..." -SecondsRemaining $i
Start-Sleep 1
}
Write-Progress -Activity "Waiting for next connectivity check..." -Completed
}
} while ($connCheckTotalTime -le $ConnectivityCheckTimeout -and $targetComputers.Count -gt 0)
# exit gracefully
exitGracefully