3LWakeOnLan.psm1

# Name : 3LWakeOnLan.psm1
# Author : Frits van Drie (3-Link.nl)
# Date : 2021-10-20

Function Invoke-WakeUpComputer {

    <#
 
        .SYNOPSIS
            Send a Wake-On-LAN magic packet
 
        .DESCRIPTION
            Send a Wake-On-LAN magic packet to all passed MAC-addresses
            UDP-port defaults to 7
            IPv4-address defaults to 255.255.255.255
            The following separators in the MAC-address are accepted: - (dash), : (colon), . (dot)
 
        .EXAMPLE
            Start-Computer -macAddress '6805CAAF534A', 'B4-2E-99:A6.55.2D'
            Start-Computer -macAddress 'C8-60-00-BF-B8-7E', '00-15-5D-00-02-A8'
            Broadcasts a magic packet to multiple mac-addresses through UDP-port 7
            Separator characters are removed
 
        .EXAMPLE
            Start-Computer -macAddress '6805CAAF534A' -Port 9 -Verbose
            Broadcasts a magic packet through UDP-port 9
 
        .NOTES
            Author : Frits van Drie (3-Link.nl)
            Versions : 2021.09.18
                                IPv4-address is now optional and defaults to the all-subnet broadcast address
                                Added support for multiple MAC-addresses
                                Added support for MAC-address through pipeline
 
    #>


    [CmdletBinding()]

    Param (

        [parameter(mandatory=$true, ValueFromPipeline=$true)]
        [string[]]$macAddress,

        [ipaddress]$ipv4Address = '255.255.255.255',

        [int]$port = 7

    )

    Begin{ 
        Write-Verbose "Start Function: $($MyInvocation.MyCommand)"
    }

    Process {

        foreach ($mac in $macAddress) {

            # Convert mac-address
            Write-Verbose "Check MAC-address: $mac"

            if ( -not (IsValidMacAddress $mac) ) {

                Write-Error "$mac is not a valid MAC-address"
                return

            }

            Write-Verbose "$mac is a valid MAC-address"

            Write-Verbose "Convert MAC-address: $mac"

            $mac = $mac.replace('-','').Replace(':','').Replace('.','')

            $target = 0,2,4,6,8,10 | Foreach {
                [convert]::ToByte($mac.substring($_,2),16)
            }

            Write-Verbose "Create magic packet"

            $packet    = (,[byte]255 *6) + ($target * 16)
            $udpClient = New-Object System.Net.Sockets.UdpClient

            Write-Verbose "Send magic packet (MAC: $mac, IPv4: $ipv4Address, Port: UDP-$port)"
            Write-Verbose "`t`tMAC : $mac"
            Write-Verbose "`t`tIPv4: $ipv4Address"
            Write-Verbose "`t`tPort: UDP-$port"

            $udpClient.Connect($ipv4Address, $port)

            [void]$udpClient.Send($packet, 102)

        }

    } # process

    End {
        Write-Verbose "End Function: $($MyInvocation.MyCommand)"
    }

}

Function Get-MacAddress{
    <#
 
        .SYNOPSIS
            Get a computer network hardware address
 
        .DESCRIPTION
 
        .NOTE
            Last modified: 2021-10-20
 
        .AUTHOR
            Frits van Drie (3-Link.nl)
 
    #>


    Param (
        [parameter(mandatory=$true)]
        [string[]]$computerName

    )


    foreach ($computer in $computerName) {
        try {

            $networkadapterconfiguration = Get-CimInstance win32_networkadapterconfiguration -ComputerName $computer | select description, macaddress  -ea Stop

        }

        catch {

            Write-Error $Error[0]

        }

        Write-Output $networkadapterconfiguration
    }

}

Function IsValidMacAddress {
    <#
 
        .SYNOPSIS
            Checks the validity of a given mac-address
 
        .DESCRIPTION
            Checks the validity of a given mac-address. Valid separators are: dash (-), colon (:) and dot (.)
 
        .INPUT
            String
 
        .OUTPUT
            Boolean
 
        .NOTE
            Last modified: 2021-09-21
 
        .AUTHOR
            Frits van Drie (3-Link.nl)
 
    #>


    [CmdletBinding()]

    param (

        [parameter(mandatory=$true, position=0)]
        [string]$macAddress

    )

    if ( $macAddress -match "([a-fA-F0-9]{2,2}[:\-\.]{0,1}){5}([a-fA-F0-9]{2,2}){1}$" ) {

        return $true    

    }

    else {

        return $false

    }

}

Function Add-ComputerMacAddressToRegistry{
    <#
 
        .SYNOPSIS
            Save a computer network hardware address in the local registry
 
        .DESCRIPTION
 
        .NOTE
            Last modified: 2021-09-20
 
        .AUTHOR
            Frits van Drie (3-Link.nl)
 
    #>


    [CmdletBinding()]

    param (

        [parameter(mandatory=$true)]
        [string]$computerName,

        [parameter(mandatory=$true)]
        [string]$macAddress

    )


    Begin  {

        Write-Verbose "Start Function: $($MyInvocation.MyCommand)"

        try {

            $regKey = "HKCU:\Software\3-Link\PSWakeOnLan"

            Write-Verbose "Check registry-key: $regKey"

            if ( -not (Test-Path $regKey -ea SilentlyContinue) ) {

                Write-Verbose "Create new registry-key: $regKey"
                $null = New-Item $regKey -Force -ea Stop
                Write-Verbose "Success"

            }

        }

        catch {

            Write-Verbose "Error creating registry-key: $regKey"
            Write-Error $Error[0]
            Return

        }

    }

    Process{

        # Convert mac-address
        Write-Verbose "Check MAC-address: $mac"

        if ( -not (IsValidMacAddress $mac) ) {

            Write-Error "$mac is not a valid MAC-address"
            return

        }

        Write-Verbose "$mac is a valid MAC-address"

        try {

            Write-Verbose "Add property $computerName to $regKey"
            $null = New-ItemProperty -Name $computerName -Path $regKey -Value $mac -ea Stop
            Write-Verbose "Success"

        }

        catch [System.IO.IOException]{

            try {

                Write-Verbose "Add $mac to property $regkey\$computerName"

                $value = (Get-ItemProperty -Path $regKey).$computerName
                $mac   = "$value;$mac"

                $null = Set-ItemProperty -Name $computerName -Path $regKey -Value $mac -ea Stop
                Write-Verbose "Success"

            }

            catch [System.IO.IOException]{


                Write-Verbose "Error adding $mac to $regKey\$computerName"
                Write-Error $Error[0]
                Return

            }

        }

    }

    End {

        Write-Verbose "End Function: $($MyInvocation.MyCommand)"

    }


}

Function Get-ComputerMacAddressFromFile {

    [CmdletBinding()]
    param(
        [string]$filePath = 'C:\Users\fvd\AppData\Roaming\3-Link\PowerShell\WakeOnLan.json',
        [string]$computerName = '*',
        [string]$macAddress   = '*',
        [string]$ipv4Address  = '*',
        [string]$port         = '*'
    )

    # Check filePath
    if ( -not (Test-Path $filePath) ) {
        Write-Error "File not found: $filePath"
        return
    }

    try {
    
        [array]$arr = Get-Content $filePath | ConvertFrom-Json -ea Stop
        $result =  $arr | Where { ($_.computerName -like $computerName) -and ($_.macAddress -like $macAddress) -and ($_.ipv4Address -like $ipv4Address) -and ($_.port -like $port) }
    
    }
    
    catch {
    
        Write-Error "$filePath is not a valid JSON-file"
        return

    }

    Write-Output $result
    
}

Function Add-ComputerMacAddressToFile {

    [CmdletBinding()]
    param(
        [string]$filePath = 'C:\Users\fvd\AppData\Roaming\3-Link\PowerShell\WakeOnLan.json',

        [parameter(mandatory=$true)]
        [string]$computerName,

        [parameter(mandatory=$true)]
        [string]$macAddress,

        [parameter(mandatory=$false)]
        [string]$ipv4Address = '255.255.255.255',

        [parameter(mandatory=$false)]
        [string]$port = 7
    )

    Begin {

        Write-Verbose "Start Function ($($MyInvocation.MyCommand))"

        # Check filePath
        if ( -not (Test-Path $filePath) ) {

            try {

                Write-Verbose "Create new file: $filePath"

                New-Item $filePath -ItemType File -ErrorAction Stop

                Write-Verbose "Success"

            }

            catch {

                Write-Verbose "Error creating new file: $filePath"
                Write-Error $Error[0]
                break

            }

        }

        try {
    
            Write-Verbose "Reading content from file: $filePath"
            $content = Get-Content $filePath -ErrorAction Stop
    
        }
    
        catch {
    
            Write-Error "Error reading $filePath"
            break

        }

        try {

            Write-Verbose "Convert content from file to JSON"
            [array]$arr = $content | ConvertFrom-Json -ErrorAction Stop

        }

        catch {

            Write-Error "$filePath is not a valid JSON-file"
            break

        }

    }


    Process {

        # Check mac-address
        Write-Verbose "Check MAC-address: $macAddress"

        if ( -not (IsValidMacAddress $macAddress) ) {

            Write-Error "$macAddress is not a valid MAC-address"
            return

        }

        Write-Verbose "$macAddress is a valid MAC-address"


        # Check ipv4-Address
        Write-Verbose "Check IPv4-address: $ipv4Address"
<#
        if ( -not (IsValidIPv4Address $ipv4Address) ) {
 
            Write-Error "$ipv4Address is not a valid IPv4-address"
            return
 
        }
#>

        Write-Verbose "$ipv4Address is a valid IPv4-address"


        $newEntry = New-Object PSObject
        Add-Member -InputObject $newEntry -MemberType NoteProperty -Name 'computerName' -Value $computerName
        Add-Member -InputObject $newEntry -MemberType NoteProperty -Name 'macAddress'   -Value $macAddress
        Add-Member -InputObject $newEntry -MemberType NoteProperty -Name 'ipv4Address'  -Value $ipv4Address
        Add-Member -InputObject $newEntry -MemberType NoteProperty -Name 'port'         -Value $port


        # Check for duplicates
        Write-Verbose "Check for duplicates"
        $duplicateFound = $arr | Where { "$_" -eq $newEntry }

        if ( $duplicateFound ) {

            Write-Warning "Entry for $computerName already exists"
            Write-Verbose $duplicateFound
            return

        }

        # Add entry to File
        $arr += $newEntry

        try {

            Write-Verbose "Convert content to JSON"
            $json = $arr |  ConvertTo-Json -ea Stop

        }

        catch {

            Write-Verbose "Error converting content to JSON"
            Write-Error $Error[0]
            Return

        }

        try {

            Write-Verbose "Write new JSON-content to file: $filePath"
            $json | Set-Content $filePath -ea Stop

        }

        catch {

            Write-Verbose "Error writing content to file: $filePath"
            Write-Error $Error[0]
            Return

        }

    }

    End {

        Write-Verbose "End Function ($($MyInvocation.MyCommand))"

    }
    
}

Function Remove-ComputerMacAddressFromFile {


    [CmdletBinding()]

    param(

        [string]$filePath = 'C:\Users\fvd\AppData\Roaming\3-Link\PowerShell\WakeOnLan.json',

        [parameter(mandatory=$true)]
        [string]$computerName,

        [parameter(mandatory=$false)]
        [string]$macAddress = '*',

        [parameter(mandatory=$false)]
        [string]$ipv4Address = '*',

        [parameter(mandatory=$false)]
        [string]$port = '*'

    )

    Begin {

        Write-Verbose "Start Function ($($MyInvocation.MyCommand))"

        # Check filePath
        if ( -not (Test-Path $filePath) ) {

            Write-Verbose "File not found: $filePath"
            Write-Error $Error[0]
            break

        }

        # Read file
        try {
    
            Write-Verbose "Reading content from file: $filePath"
            $content = Get-Content $filePath -ErrorAction Stop
    
        }
    
        catch {
    
            Write-Error "Error reading $filePath"
            break

        }

        # Convert content from JSON
        try {

            Write-Verbose "Convert content from file to JSON"
            [array]$arr = $content | ConvertFrom-Json -ErrorAction Stop

        }

        catch {

            Write-Error "$filePath is not a valid JSON-file"
            break

        }


    }


    Process {

<# # Check mac-address
        Write-Verbose "Check MAC-address: $macAddress"
 
        if ( -not (IsValidMacAddress $macAddress) ) {
 
            Write-Error "$macAddress is not a valid MAC-address"
            break
 
        }
        Write-Verbose "$macAddress is a valid MAC-address"
 
 
        # Check ipv4-Address
        Write-Verbose "Check IPv4-address: $ipv4Address"
        <#
        if ( -not (IsValidIPv4Address $ipv4Address) ) {
 
            Write-Error "$ipv4Address is not a valid IPv4-address"
            return
 
        }
        #>

# Write-Verbose "$ipv4Address is a valid IPv4-address"
#>

        # Define object to remove
        $entry = New-Object PSObject
        Add-Member -InputObject $entry -MemberType NoteProperty -Name 'computerName' -Value $computerName
        Add-Member -InputObject $entry -MemberType NoteProperty -Name 'macAddress'   -Value $macAddress
        Add-Member -InputObject $entry -MemberType NoteProperty -Name 'ipv4Address'  -Value $ipv4Address
        Add-Member -InputObject $entry -MemberType NoteProperty -Name 'port'         -Value $port


        # Find object
        Write-Verbose "Find entry"
        $entryFound = $arr | Where { ($_.computerName -like $computerName) -and ($_.macAddress -like $macAddress) -and ($_.ipv4Address -like $ipv4Address) -and ($_.port -like $port) }
        #"$_" -eq $entry }

        if ( -not $entryFound ) {

            Write-Verbose "Entry not found"

            return

        }
        Write-Verbose "Entry found"

        # Remove entry from array
        foreach ($item in $entryFound) {
            $arr = $arr | Where { "$_" -ne $item }
        }




        try {

            Write-Verbose "Convert content to JSON"
            $json = $arr |  ConvertTo-Json -ea Stop

        }

        catch {

            Write-Verbose "Error converting content to JSON"
            Write-Error $Error[0]
            Return

        }

        try {

            Write-Verbose "Write new JSON-content to file: $filePath"
            if ( $json -eq $null ) {

                $json = ""

            }
            $json | Set-Content $filePath -ea Stop

        }

        catch {

            Write-Verbose "Error writing content to file: $filePath"
            Write-Error $Error[0]
            Return

        }

    }

    End {

        Write-Verbose "End Function ($($MyInvocation.MyCommand))"

    }

}

Function Start-Computer {

    [CmdletBinding()]
    param(

        [parameter(mandatory=$true, position = 0)]
        [string]$computerName

    )

    Get-ComputerMacAddressFromFile -computerName $computerName | foreach {

        Invoke-WakeUpComputer -macAddress $_.macAddress -ipv4Address $_.ipv4Address -port $_.port

    }

}