I love PowerShell and I'm sure a lot of you do too. I geek out over nearly every script I create but there are some that I get as giddy as a schoolgirl over; this is one of them. I work at a client's site that has hundreds of IP networks and with network admins that don't want to open up multicasting.

I've attempted to mess with WOL unicast but I just couldn't ever get it working so I decided to figure out how to get this to work via PowerShell.

If you're familiar with WOL, you'll know that the WOL magic packet is not routable. It either has to be subnet-directed out which a lot of network admins won't allow or sent from a device on the same logical network as the device you're wanting to wake up.

You end up only being able to wake up computers on the same subnet as you. If you're in a large enterprise, that's near worthless.

I created this script to wake up any PC on any subnet inside an enterprise network regardless of where it is.

It does this by:

  • first checking to see if your PC is already on the same subnet as the PC you want to wake up.
  • If so, it just initiates a WOL traditionally.
  • if it detects the offline PC is on a different subnet, it attempts to find any other online, Windows PCs on that subnet
  • copies a small WOL utility to it
  • initiates the WOL attempt from THAT subnet.

This gets around the non-routable, no multicasting requirement. It also allows you to use PowerShell remoting to do this or, if you don't have remoting enabled, it'll use a WMI RPC call to create the WOL process on the WOL proxy computer.

Download this script here.

#Requires -Version 3

<#
.NOTES
	Created on: 			5/21/2014 1:33 PM
	Created by: 			Adam Bertram
	Filename:   			Send-WolProxyRequest.ps1
	General Requirements: 	Read access to a System Center Configuration Manager database
	Requirements:  
		Wake On LAN Command Line Utility (http://www.depicus.com/wake-on-lan/wake-on-lan-cmd.aspx)
	Todos:	
		Remove the dependency on wolcmd.exe.  Use the Net.Sockets.UdpClient object instead.
		Incorporate the subnet into the local network detection rather than just using the first 3 IP octets
		Speed this up by using jobs
.DESCRIPTION
	This script is designed to send a WOL magic packet to a specified computer.  If the specified computer is not
	on the same network as the originatnating computer, it will attempt to find a "proxy" Windows computer to
	initiate the WOL send.  This gets around traditional network multicasting requirements.

	This script currently requires access to a System Center Configuration Manager database.  This 
	script uses it to find the various network information about the specified computer to wake.

	By default, this script does not use PS remoting but uses the CreateProcess WMI method.
.EXAMPLE
	.\Send-WolProxyRequest.ps1 -Computername COMPUTERNAME    
.EXAMPLE
    .\Send-WolProxyRequest.ps1 -Computername COMPUTERNAME -UsePsRemoting
.PARAMETER Computername
 	This computer name that you'd like to attempt to wake up.
.PARAMETER ConfigMgrSite
	The site code of your ConfigMgr site
.PARAMETER ConfigMgrSiteServer
	The computer name of your ConfigMgr site server hosting your database
.PARAMETER WolCmdFilePath
	The file path where the wolcmd.exe utility is located
.PARAMETER UsePsRemoting
	Use this switch if you'd like to use Powershell remoting to kick off the WOL attempt on the WOL proxy 
	rather than using WMI to initiate the remote process
#>
[CmdletBinding()]
param (
	[Parameter(Mandatory = $True,
			ValueFromPipeline = $True,
			ValueFromPipelineByPropertyName = $True)]
	[ValidateScript({
		if (Test-Connection $_ -Quiet -Count 1) {
			throw "$($_) is already online"
		} else {
			$true	
		}
	})]
	[string]$Computername,
	[Parameter(Mandatory = $False,
			ValueFromPipeline = $False,
			ValueFromPipelineByPropertyName = $False)]
	[string]$ConfigMgrSite = 'UHP',
	[Parameter(Mandatory = $False,
			ValueFromPipeline = $False,
			ValueFromPipelineByPropertyName = $False)]
			[ValidateScript({ Test-Connection $_ -Quiet -Count 1 })]
	[string]$ConfigMgrSiteServer = 'CONFIGMANAGER',
	[Parameter(Mandatory = $False,
			ValueFromPipeline = $False,
			ValueFromPipelineByPropertyName = $False)]
			[ValidateScript({ Test-Path $_ })]
	[string]$WolCmdFilePath = '\\hosp.uhhg.org\netlogon\wolcmd.exe',
	[Parameter(Mandatory = $False,
			ValueFromPipeline = $False,
			ValueFromPipelineByPropertyName = $False)]
	[switch]$UsePsRemoting = $false
)

begin {

	function Get-DnsHostname ($IPAddress) {
		## Use nslookup because it's much faster than any other cmdlet
		nslookup $IPAddress |
		where { $_ -match 'name' } |
		select @{ n = 'DNSHostname'; e = { $_.Replace('Name:    ', '') } } | select -ExpandProperty DnsHostName
	}
	
	## TODO: Make this actually figure out if the IPs are not on the same network
	function Validate-IsOnSameNetwork($Computername1,$Computername2) {
		#$LocalIPAddressNetworks = Get-NetIPAddress | select @{ n = 'Network'; e = { $_.IPAddress.Split('.')[0..2] -join '.' } } | select -ExpandProperty Network
		#$LocalIPAddressNetworks -contains ($IpAddress.Split('.')[0..2] -join '.')
	}
	
	function Get-WolProxyComputerName ($IpAddress) {
		$IpSplit = $IPAddress.Split('.')
		$HostIPOctets = ($IpSplit[0,1,2] -join '.')
		## Search the IP network from .2 to .253
		foreach ($num in 2..253) {
			$PotentialWolProxyIP = "$HostIPOctets.$num"
			Write-Verbose "Checking $PotentialWolProxyIP if good candidate for WOL proxy..."
			## Check to see if our WOL proxy PC is online
			if (Test-Connection -Count 1 -ComputerName $PotentialWolProxyIP -quiet) {
				Write-Verbose "$PotentialWolProxyIP is online..."
				## Assume if the C$ share is available, it's a Windows computer we can
				## copy the wolcmd utility to and run.  This could be better.
				$WolProxyComputerName = Get-DnsHostname $PotentialWolProxyIP
				if ($WolProxyComputerName) {
					Write-Verbose "DNS resolved $PotentialWolProxyIP to $WolProxyComputerName..."
					if (Test-Path "\\$WolProxyComputerName\c<code>$") {
						Write-Verbose "$WolProxyComputerName is a Windows PC. This is our WOL proxy..."
						##HACK: Should not break out of a foreach
						$Script:KnownWolProxies += @{ $WolProxyComputerName = $PotentialWolProxyIP }
						return $WolProxyComputerName
					}
				}
			}
		}
	}
	
	$WolCmdFileName = Split-Path -Path $WolCmdFilePath -Leaf
	
	## Find all of the IP networks that each IP on the local machine is a part of
	$LocalIPAddressNetworks = Get-NetIPAddress | select @{ n = 'Network'; e = { $_.IPAddress.Split('.')[0..2] -join '.' } } | select -ExpandProperty Network
	
	## Common WOL UDP ports are 7 and 9
	$WolUdpPort = 9
	
	$WmiQuery = "SELECT DISTINCT * 
		FROM SMS_R_System AS sys 
		JOIN SMS_G_System_NETWORK_ADAPTER_CONFIGURATION AS net ON net.ResourceID = sys.ResourceID 
		WHERE sys.Name = '$ComputerName' AND
		net.IPAddress IS NOT NULL"
	
	$WmiParams = @{
		'ComputerName' = $ConfigMgrSiteServer
		'Namespace' = "root\sms\site_$ConfigMgrSite"
		'Query' = $WmiQuery
	}
	
	## Query all network interfaces on the local machine and parse out IP address, subnet mask and the MAC
	$NicResults = Get-WmiObject @WmiParams |
		select @{n='IPAddress'; e={([regex]'\b(?:\d{1,3}\.){3}\d{1,3}\b').Matches($_.net.IPAddress)}},
		@{ n = 'Subnet'; e = { ([regex]'\b(?:\d{1,3}\.){3}\d{1,3}\b').Matches($_.net.IPSubnet) } },
		@{ n = 'MACAddress'; e = { $_.net.MACAddress } }
}

process {
	try {
		foreach ($Nic in $NicResults) {
			## Cast to string from [System.Text.RegularExpressions.Match]
			[string]$IpAddress = $Nic.IPAddress
			Write-Verbose "Processing IP address $IPAddress..."
		
			## Strip out any delims in MAC
			$MacAddress = (($Nic.MacAddress.replace(":", "")).replace("-", "")).replace(".", "")
			Write-Verbose "Processing MAC Address $MacAddress..."
			
			Write-Verbose "Checking IP $IPAddress to see if it's in the local subnet..."
			if ($LocalIPAddressNetworks -contains ($IpAddress.Split('.')[0..2] -join '.')) {
				Write-Verbose 'IP found to be on local subnet. No WOL proxy needed. Sending WOL directly to the intended machine...'
				& $WolCmdFilePath $MacAddress $IPAddress $($Nic.Subnet) $WolUdpPort 2>&1> $null
			} else {
				Write-Verbose 'IP not found to be on local subnet. Getting WOL proxy computer...'
				$WolProxyComputerName = Get-WolProxyComputerName $IPAddress
				Write-Verbose "WOL Proxy computer name found: $WolProxyComputerName"
				
				## Copy wolcmd to the remote proxy computer
				Write-Verbose "Copying $WolCmdFilePath to \\$WolProxyComputerName\c</code>$..."
				Copy-Item $WolCmdFilePath "\\$WolProxyComputerName\c$" -Force
				
				$WolCmdString = "C:\$WolCmdFileName $MacAddress $IPAddress $($Nic.Subnet) $WolUdpPort"
				Write-Verbose "Initiating the string <code>"$WolCmdString</code>"..."
				if ($UsePsRemoting.IsPresent) {
					Write-Verbose "Connecting to $WolProxyComputerName and attempting WOL proxy function via PS remoting..."
					Invoke-Command -ComputerName $WolProxyComputerName -ScriptBlock {
						$using:WolCmdString
					}
				} else {
					Write-Verbose "Connecting to $WolProxyComputerName and attempting WOL proxy function via WMI RPC method..."
					
					$NewProcess = ([WMICLASS]"\\$WolProxyComputerName\Root\CIMV2:Win32_Process").create($WolCmdString)
					if ($NewProcess.ReturnValue -eq 0) {
						Write-Verbose "Waiting for process ID $($NewProcess.ProcessID) on $WolProxyComputerName..."
						while (Get-Process -Id $NewProcess.ProcessID -ComputerName $WolProxyComputerName -ErrorAction 'SilentlyContinue') {
							sleep 1
						}
						Write-Verbose "Process ID $($NewProcess.ProcessID) has exited"
					} else {
						throw "Process failed.  Exit code was $ExitCode"
					}
				}
				## Cleanup all files copied to the proxy computer
				Write-Verbose 'Cleaning up file remnants on WOL proxy computer...'
				if (Test-Path "\\$WolProxyComputerName\c<code>$\$WolCmdFileName") {
					Remove-Item -Path "\\$WolProxyComputerName\c</code>$\$WolCmdFileName" -Force
				}
			}
			
		}
	} catch {
		Write-Error $_.Exception.Message
	}
}