PSWoL.psm1
#Region '.\Private\Convert-HostnameToIP.ps1' -1 function Convert-HostnameToIP { <# .SYNOPSIS Convert a provided hostname to its resolved IP address .DESCRIPTION We need an IPv4 address in order to get a MAC for WOL. This function attempts to convert a hostname in to an IP address .PARAMETER ComputerName A computer name to target for WOL. Will attempt to resolve the name to an IP address that will then be queried in ARP to determine a MAC. .EXAMPLE Convert-HostnameToIP 'ContosoComp' 192.168.15.10 #> [CmdletBinding()] [OutputType([String])] param ( [String]$ComputerName ) try { [System.Net.Dns]::GetHostEntry($ComputerName).AddressList.IPAddressToString | Where-Object { $_ -notmatch ':' } } catch { return $null } } #EndRegion '.\Private\Convert-HostnameToIP.ps1' 27 #Region '.\Private\Convert-MacToByte.ps1' -1 function Convert-MacToByte { <# .SYNOPSIS Convert provided MAC address string to bytes .DESCRIPTION Accepts MAC addresses in multiple string formats and returns a byte array. This function is necessary to provide compatibility across versions of PowerShell. .PARAMETER MacAddress Mac address to convert to a byte array. Can be in any MAC format. .EXAMPLE Convert-MacToByte '6D-84-01-BC-D2-CD' #> [CmdletBinding()] param ( [String]$MacAddress ) try { $MacString = [System.Net.NetworkInformation.PhysicalAddress]::Parse(($MacAddress.ToUpper() -replace '[^0-9A-F]','')) $MacString.GetAddressBytes() } catch { throw "An invalid physical address was specified: $MacAddress" } } #EndRegion '.\Private\Convert-MacToByte.ps1' 24 #Region '.\Private\Get-SettingsPath.ps1' -1 function Get-SettingsPath { <# .SYNOPSIS Determine the path where saved targets are stored. .DESCRIPTION Depending on the OS, return an appropriate path relative to the user to store a json file containing saved targets. .EXAMPLE PS> Get-SettingsPath C:\Users\Admin\Appdata\Roaming\PSWol\Settings.json #> [CmdletBinding()] [OutputType([String])] param ( # no params ) if ($IsWindows -or $ENV:OS) { $Windows = $true } else { $Windows = $false } if ($Windows) { $SettingsPath = Join-Path -Path $Env:APPDATA -ChildPath "PSWoL\Settings.json" } else { $SettingsPath = Join-Path -Path ([Environment]::GetEnvironmentVariable("HOME")) -ChildPath ".local/share/powershell/Modules/PSWoL/Settings.json" } return $SettingsPath } #EndRegion '.\Private\Get-SettingsPath.ps1' 29 #Region '.\Private\Resolve-IPToMac.ps1' -1 function Resolve-IPToMac { <# .SYNOPSIS Resolve an IP address to a MAC address .DESCRIPTION Attempts to find the corresponding MAC address for a given IP address .PARAMETER IPAddress The IP address to look up in ARP and find a corresponding MAC address to send a WOL packet to. Must be an IPv4 address. .EXAMPLE Resolve-IPToMac '192.168.15.10' 6D-84-01-BC-D2-CD #> [CmdletBinding()] [OutputType([System.Net.NetworkInformation.PhysicalAddress])] param ( [ValidateScript({ if ([IPAddress]::TryParse($_, [ref]0)) { return $true } else { throw "Error: Not a valid IP address." } })] [String]$IPAddress ) # check to see if we're on Windows or not if ($IsWindows -or $ENV:OS) { $Windows = $true } else { $Windows = $false } if ($Windows) { $Result = Get-NetNeighbor -IPAddress $IPAddress -AddressFamily IPv4 | Where-Object { $_.LinkLayerAddress -ne '' } | Select-Object -First 1 [System.Net.NetworkInformation.PhysicalAddress]::Parse(($Result.LinkLayerAddress.ToUpper() -replace '[^0-9A-F]','')) } else { $Arp = Get-Command -Name "arp" | Select-Object -ExpandProperty Source $Regex = '{0}.+{1}' -f $([Regex]::Escape($IPAddress)), '(?<MAC>([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2}))' $ArpResult = & $Arp -a foreach ($Line in $ArpResult) { if ($Line -match $Regex) { [System.Net.NetworkInformation.PhysicalAddress]::Parse(($Matches.MAC.ToUpper() -replace '[^0-9A-F]','')) continue } } } } #EndRegion '.\Private\Resolve-IPToMac.ps1' 49 #Region '.\Private\Resolve-TargetType.ps1' -1 function Resolve-TargetType { <# .SYNOPSIS Determine if a provided string is an IP address, MAC address, or computername (possibly) .DESCRIPTION Takes an input string and returns a string value representing whether the input was an IP address, MAC address, or computername .PARAMETER Target Takes the provided target for a WOL packet and determines if it's a MAC address, IP address or computer name. .EXAMPLE Resolve-TargetType 192.168.15.10 IPAddress #> [CmdletBinding()] [OutputType([String])] param ( [String]$Target ) $MacReg = '^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9a-fA-F]{4}.[0-9a-fA-F]{4}.[0-9a-fA-F]{4})|([0-9a-fA-F]{12})$' if ($Target -match $MacReg) { return "MacAddress" } elseif ([System.Net.IPAddress]::TryParse($Target,[ref]0)) { return "IPAddress" } else { # possibly a computername but it's impossible to tell return "ComputerName" } } #EndRegion '.\Private\Resolve-TargetType.ps1' 29 #Region '.\Public\Get-WolTarget.ps1' -1 function Get-WolTarget { <# .SYNOPSIS Retrieved a saved target from local file .DESCRIPTION Can retrieve a saved target's MAC by memorable name or if called with no -Name parameter will return a table of all saved targets. .PARAMETER Name The name to look up in the saved targets file. Must be an exact match .EXAMPLE PS> Get-WolTarget Name Value ---- ----- gibson2 16:12:EB:E0:32:28 Gibson1 16:12:EB:E0:32:28 # returns all of the saved targets if present. .EXAMPLE PS> Get-WolTarget -Name gibson1 Name MAC ---- --- gibson1 16:12:EB:E0:32:28 # returns a PSCustomobject with the name and MAC of the saved target #> [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [String]$Name ) try { $SettingsFile = Get-SettingsPath } catch { throw "Could not retrieve settings file path" } if (Test-Path $SettingsFile) { # load existing saved targets $JsonData = Get-Content -Path $SettingsFile | ConvertFrom-Json $SavedTargets = @{} $JsonData.PSObject.Properties | Foreach-Object { $SavedTargets.Add($_.Name, $_.Value) } } else { Write-Warning "No saved targets" return $null } if ($Name) { if ($SavedTargets.$Name) { [PSCustomObject]@{ Name = $Name MAC = $SavedTargets.$Name } } else { Write-Warning "No saved target found by name: $Name" } } else { $SavedTargets } } #EndRegion '.\Public\Get-WolTarget.ps1' 62 #Region '.\Public\Remove-WolTarget.ps1' -1 function Remove-WolTarget { <# .SYNOPSIS Remove a saved target from the local settings file .DESCRIPTION Removes a saved target by name from the saved settings file. Optionally can also delete the entire saved settings file. .PARAMETER Name The name used by the saved target. Needs to be an exact match. If you're not sure, run Get-WolTarget first to list all currently saved targets. .PARAMETER DeleteAll Switch parameter to delete the saved targets file entirely .EXAMPLE PS> Remove-WolTarget -Name "Gibson2" # will remove the saved entry with the name "Gibson2" .EXAMPLE PS> Remove-WolTarget -DeleteAll Confirm Are you sure you want to perform this action? Performing the operation "Remove-Item" on target "C:\Users\Admin\AppData\Roaming\PSWoL\Settings.json". [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): y # after confirming this will delete the saved settings file stored locally #> [CmdletBinding(SupportsShouldProcess,ConfirmImpact='High')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'DeleteAll', Justification = 'Switch parameter where variable is not directly called')] param ( [Parameter(Mandatory,Position=0,ParameterSetName="Name")] [String]$Name, [Parameter(ParameterSetName="Delete")] [Switch]$DeleteAll ) try { $SettingsFile = Get-SettingsPath } catch { throw "Could not retrieve settings file path" } if (Test-Path $SettingsFile) { # load existing saved targets $JsonData = Get-Content -Path $SettingsFile | ConvertFrom-Json $SavedTargets = @{} $JsonData.PSObject.Properties | Foreach-Object { $SavedTargets.Add($_.Name, $_.Value) } switch ($PSCmdlet.ParameterSetName) { 'Name' { try { if ($SavedTargets.$Name) { Write-Verbose "Removing saved target Name: $Name MAC: $($SavedTargets.$Name)" $SavedTargets.Remove($Name) if ($SavedTargets.Count -eq 0) { Write-Warning "Last saved entry removed, deleting saved file" Remove-WolTarget -DeleteAll -Confirm:$false } else { Write-Verbose "Saving changes to $($SettingsFile)" $SavedTargets | ConvertTo-Json | Out-File -FilePath $SettingsFile -Force } } else { Write-Warning "No saved target found by name: $Name" } } catch { throw $_ } } 'Delete' { if ($PSCmdlet.ShouldProcess($SettingsFile, 'Remove-Item')) { try { Remove-Item -Path $SettingsFile -Force -ErrorAction Stop } catch { throw $_ } } } } } else { Write-Warning "No saved targets" } } #EndRegion '.\Public\Remove-WolTarget.ps1' 82 #Region '.\Public\Save-WolTarget.ps1' -1 function Save-WolTarget { <# .SYNOPSIS Save a MAC address along with a name for use with Send-WakeOnLan. .DESCRIPTION Save a MAC address along with a name for use with Send-WakeOnLan. The name is purely decorative and does not have to be resolvable. It does have to be unique amongst saved names. .PARAMETER Name A name to refer to the saved target by .PARAMETER MacAddress MAC address of the saved target. Acceptable formats are: 00:1a:1e:12:af:38 00-1a-1e-12-af-38 001A.1E12.AF38 001A1E12AF38 .EXAMPLE PS> Save-WolTarget -Name "TheGibson" -MacAddress "16:12:EB:E0:32:28" # will save the decorative name TheGibson with MAC address 16:12:EB:E0:32:28 to a local settings file #> [CmdletBinding()] param ( [Parameter(Mandatory)] [String]$Name, [Parameter(Mandatory)] [ValidateScript({ if ($_ -match '^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9a-fA-F]{4}.[0-9a-fA-F]{4}.[0-9a-fA-F]{4})|([0-9a-fA-F]{12})$') { return $true } else { throw "Provided MAC not in an acceptable format." } })] [String]$MacAddress ) try { $SettingsFile = Get-SettingsPath } catch { throw "Could not retrieve settings file path" } if (Test-Path $SettingsFile) { # load existing saved targets $JsonData = Get-Content -Path $SettingsFile | ConvertFrom-Json $SavedTargets = @{} $JsonData.PSObject.Properties | Foreach-Object { $SavedTargets.Add($_.Name, $_.Value) } } else { $SavedTargets = @{} } try { $SavedTargets.Add($Name, $MacAddress) } catch [System.Management.Automation.MethodInvocationException] { throw "$Name already exists in saved targets. If you want want to use this name, remove the current entry with Remove-WolTarget -Name $Name" } try { if (Test-Path $SettingsFile) { Write-Verbose "Saving target to $($SettingsFile)" $SavedTargets | ConvertTo-Json | Out-File -FilePath $SettingsFile -Force } else { Write-Verbose "Saving target to $($SettingsFile)" New-Item -Path $SettingsFile -Force | Out-Null $SavedTargets | ConvertTo-Json | Out-File -FilePath $SettingsFile } } catch { throw $_ } } #EndRegion '.\Public\Save-WolTarget.ps1' 72 #Region '.\Public\Send-WakeOnLan.ps1' -1 function Send-WakeOnLan { <# .SYNOPSIS Send a Wake-On-LAN packet to a target address. .DESCRIPTION Sends a Wake-On-LAN packet to a target address. Target address can be a MAC address, IP address or potentially a computer name .PARAMETER TargetAddress Target address to send Wake-On-LAN packet to. Accepts IP address, MAC address or Computer Name. IP must be an IPv4 address. MAC addresses can be '1234.abde.4321', '12:34:AB:DE:43:21', '12-34-AB-DE-32-21' or '1234ABDE4321' format. Computername needs to be able to resolve to a local computer name. If resolution fails, the packet will not send. .PARAMETER SavedTarget If there are currently saved target addresses via Save-WolTarget you can specify their name(s) with -SavedTarget to send a WoL packet to them. .EXAMPLE PS> Send-WakeOnLan -TargetAddress '192.168.15.10' # this will attempt to do an ARP lookup for that IP address and send a WOL packet to it .EXAMPLE PS> 'E2-5D-0E-B5-E2-E8', 'DD-20-C9-FF-EC-79', '6D-84-01-BC-D2-CD' | Send-WakeOnLan # this will send a WOL packet to each of the 3 MAC addresses. .EXAMPLE PS> Send-WakeOnLan -SavedTarget "TheGibson" # this will check the saved targets for the name "TheGibson" and send a WoL packet to the MAC address associated with that name. #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(ValueFromPipeline,Mandatory,ParameterSetName="TargetAddress")] [String[]]$TargetAddress, [Parameter(ValueFromPipeline,Mandatory,ParameterSetName="SavedTarget")] [String[]]$SavedTarget ) begin { $UdpClient = [System.Net.Sockets.UdpClient]::new() [Byte[]]$BasePacket = (,0xFF * 6) $WolTargets = [System.Collections.ArrayList]::new() } process { switch ($PSCmdlet.ParameterSetName) { 'Targetaddress' { foreach ($Target in $TargetAddress) { switch (Resolve-TargetType -Target $Target) { 'IPAddress' { $ResolvedMac = Resolve-IPToMac -IPAddress $Target if ($ResolvedMac) { $MacBytes = Convert-MacToByte -MacAddress $ResolvedMac [Void]$WolTargets.Add( [PSCustomObject]@{ Identifier = $Target MacAddress = $([System.BitConverter]::ToString($MacBytes)) Packet = [Byte[]]$($BasePacket; $MacBytes * 16) } ) } else { Write-Warning "Unable to determine MAC for $Target" } } 'MacAddress' { $MacBytes = Convert-MacToByte -MacAddress $Target if ($MacBytes) { [Void]$WolTargets.Add( [PSCustomObject]@{ Identifier = $Target MacAddress = $([System.BitConverter]::ToString($MacBytes)) Packet = [Byte[]]$($BasePacket; $MacBytes * 16) } ) } else { Write-Warning "Unable to convert MAC to bytes: $Target" } } 'ComputerName' { $ResolvedIP = Convert-HostnameToIP -ComputerName $Target if ($ResolvedIP) { $Mac = Resolve-IPToMac -IPAddress $ResolvedIP if ($Mac) { $MacBytes = Convert-MacToByte -MacAddress $Mac [Void]$WolTargets.Add( [PSCustomObject]@{ Identifier = $Target MacAddress = $([System.BitConverter]::ToString($MacBytes)) Packet = [Byte[]]$($BasePacket; $MacBytes * 16) } ) } else { Write-Warning "Unable to determine MAC for $ResolvedIP" } } else { Write-Warning "Unable to resolve IP for $Target" } } } } } 'SavedTarget' { $SavedTargets = foreach ($Target in $SavedTarget) { Get-WolTarget -Name $Target } foreach ($TargetObj in $SavedTargets) { $MacBytes = Convert-MacToByte -MacAddress $($TargetObj.MAC) [Void]$WolTargets.Add( [PSCustomObject]@{ Identifier = $TargetObj.Name MacAddress = $([System.BitConverter]::ToString($MacBytes)) Packet = [Byte[]]$($BasePacket; $MacBytes * 16) } ) } } } } end { # sent packet to targets foreach ($WolTarget in $WolTargets) { if ($PSCmdlet.ShouldProcess($($WolTarget.Identifier),"Send WOL Packet")) { try { $UdpClient.Connect(([System.Net.IPAddress]::Broadcast),9) [Void]$UdpClient.Send($($WolTarget.Packet), $($WolTarget.Packet.Length)) Write-Verbose "Wake-on-LAN packet sent to target:$($WolTarget.Identifier) MAC: $($WolTarget.MacAddress)" } catch { Write-Warning "Error sending WOL packet" } } } $UdpClient.Close() $UdpClient.Dispose() } } #EndRegion '.\Public\Send-WakeOnLan.ps1' 134 |