Search code examples
powershellwake-on-lan

Please explain how this wake-on-LAN script works


I found this PowerShell code on a blog a couple months ago. It sends wake-on-LAN signals to the MAC address of your choice without using external programs. I commented on the blog post and asked the author to describe the logic behind the script because I was curious about it. I went back to the blog post at a later date to see if the author replied to my comment. I was surprised to see that I was redirected to a page where the author said he lost his blog due to a crash. I can't remember the details of it, but I don't think I have that blog bookmarked anymore.

So now I would like to request to have the brilliant minds at Stack Overflow look at this code and explain its logic to me. A comment for each line would be fantastic. I'm quite curious to know how this works. It appears to be more robust than other scripts that I've found in that it works across subnets. I don't know much about networking, though.

One of the things I'm most curious about is the for loop at the end. Why send the signal multiple times? And why on different ports? But I really would like to know the logic behind the entire script.

The code:

param (
    $targetMac,
    $network = [net.ipaddress]::Broadcast,
    $subnet = [net.ipaddress]::Broadcast
)
try {
    if($network.gettype().equals([string])) {
        $network = [net.ipaddress]::Parse($network);
    }
    if($subnet.gettype().equals([string])) {
        $subnet = [net.ipaddress]::Parse($subnet);
    }
    $broadcast = new-object net.ipaddress (([system.net.ipaddress]::parse("255.255.255.255").address -bxor $subnet.address -bor $network.address))

    $mac = [Net.NetworkInformation.PhysicalAddress]::Parse($targetMac.toupper().replace(".",""))

    $u = New-Object net.sockets.udpclient
    $ep = New-Object net.ipendpoint $broadcast, 0
    $ep2 = New-Object net.ipendpoint $broadcast, 7
    $ep3 = New-Object net.ipendpoint $broadcast, 9

    $payload = [byte[]]@(255,255,255, 255,255,255);
    $payload += ($mac.GetAddressBytes()*16)

    for($i = 0; $i -lt 10; $i++) {
        $u.Send($payload, $payload.Length, $ep) | Out-Null
        $u.Send($payload, $payload.Length, $ep2) | Out-Null
        $u.Send($payload, $payload.Length, $ep3) | Out-Null
        sleep 1;
    }
} catch {
    $Error | Write-Error;
}

Solution

  • #These are the parameters to the script. The only mandatory param here is the mac address
    #[net.ipaddress]::Broadcast will resolve to something like 255.255.255.255
    param (
        $targetMac,
        $network = [net.ipaddress]::Broadcast,
        $subnet = [net.ipaddress]::Broadcast
    )
    
    #We start the try, catch error handling here.
    #if something in try block fails, the catch block will write the error
    try {
    
    #This will evaludate to False. Hence, $network will have whatever was passed through params or the default value
    #in this case the default value is 255.255.255.255
        if($network.gettype().equals([string])) {
            $network = [net.ipaddress]::Parse($network);
        }
    
    #This will evaludate to False. Hence, $network will have whatever was passed through params or the default value
    #in this case the default value is 255.255.255.255    
        if($subnet.gettype().equals([string])) {
            $subnet = [net.ipaddress]::Parse($subnet);
        }
    
        #Not sure if this is really required here. But, assuming that the default value for both $network and $subet is 255.255.255.255,
        #this will result in $broadcast set to 255.255.255.255
        $broadcast = new-object net.ipaddress (([system.net.ipaddress]::parse("255.255.255.255").address -bxor $subnet.address -bor $network.address))
    
    #This again assumes that you had given . as the delimeter in MAC address and removes that from MAC address    
        $mac = [Net.NetworkInformation.PhysicalAddress]::Parse($targetMac.toupper().replace(".",""))
    
    #Create a new object of type net.sockets.udpclient
        $u = New-Object net.sockets.udpclient
    
    #WOL magic packet can be sent on port 0, 7, or 9    
    #Create a end point for the broadcast address at port 0    
        $ep = New-Object net.ipendpoint $broadcast, 0
    
    #Create a end point for the broadcast address at port 7    
        $ep2 = New-Object net.ipendpoint $broadcast, 7
    
    #Create a end point for the broadcast address at port 9    
        $ep3 = New-Object net.ipendpoint $broadcast, 9
    
    #Create a payload packet
    #First, create a byte array
        $payload = [byte[]]@(255,255,255, 255,255,255);
    
    #add the mac address to the above byte array    
        $payload += ($mac.GetAddressBytes()*16)
    
    #Send 10 magic packets for each port number or end point created above.
    #one is more than enough. If everything is congfigured properly
        for($i = 0; $i -lt 10; $i++) {
            $u.Send($payload, $payload.Length, $ep) | Out-Null
            $u.Send($payload, $payload.Length, $ep2) | Out-Null
            $u.Send($payload, $payload.Length, $ep3) | Out-Null
            sleep 1;
        }
    } catch {
    #catch block catches any error from try block
        $Error | Write-Error;
    }