Find-NanoLeaf.ps1
function Find-NanoLeaf { <# .Synopsis Finds NanoLeaf controllers .Description Finds NanoLeaf controllers on your local area network, using SSDP. .Link Connect-NanoLeaf .Example Find-NanoLeaf | Connect-NanoLeaf #> [OutputType('Roku.BasicInfo')] [CmdletBinding()] param( # The search timeout, in seconds. Increase this number on slower networks. [Parameter(ValueFromPipelineByPropertyName)] [int]$SearchTimeout = 5, # If set, will force a rescan of the network. # Otherwise, the most recent cached result will be returned. [Parameter(ValueFromPipelineByPropertyName)] [switch] $Force, # The type of the device to find. By default, ssdp:all. # Changing this value is unlikely to find any NanoLeaf controllers, but you can see other devices with -Verbose. [Parameter(ValueFromPipelineByPropertyName)] [string]$DeviceType = 'ssdp:all' ) begin { #region Embedded C# SSDP Finder if (-not ('StartAutomating.NanoLeafFinder' -as [type])) { Add-Type -TypeDefinition @' namespace StartAutomating { using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Timers; using System.Collections.Generic; public class NanoLeafFinder { public List<string> FindDevices(string deviceType = "ssdp:all", int searchTimeOut = 5) { List<string> results = new List<string>(); const int MaxResultSize = 8096; const string MulticastIP = "239.255.255.250"; const int multicastPort = 1900; byte[] multiCastData = Encoding.UTF8.GetBytes(string.Format(@"M-SEARCH * HTTP/1.1 HOST: {0}:{1} MAN: ""ssdp:discover"" MX: {2} ST: {3} ", MulticastIP, multicastPort, searchTimeOut, deviceType)); Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); socket.SendBufferSize = multiCastData.Length; SocketAsyncEventArgs sendEvent = new SocketAsyncEventArgs(); sendEvent.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(MulticastIP), multicastPort); sendEvent.SetBuffer(multiCastData, 0, multiCastData.Length); sendEvent.Completed += (sender, e) => { if (e.SocketError != SocketError.Success) { return; } switch (e.LastOperation) { case SocketAsyncOperation.SendTo: // When the initial multicast is done, get ready to receive responses e.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); byte[] receiveBuffer = new byte[MaxResultSize]; socket.ReceiveBufferSize = receiveBuffer.Length; e.SetBuffer(receiveBuffer, 0, MaxResultSize); socket.ReceiveFromAsync(e); break; case SocketAsyncOperation.ReceiveFrom: // Got a response, so decode it string result = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred); if (result.StartsWith("HTTP/1.1 200 OK", StringComparison.InvariantCultureIgnoreCase)) { if (! results.Contains(result)) { results.Add(result); } } if (socket != null)// and kick off another read socket.ReceiveFromAsync(e); break; default: break; } }; Timer t = new Timer(TimeSpan.FromSeconds(searchTimeOut + 1).TotalMilliseconds); t.Elapsed += (e, s) => { try { socket.Dispose(); socket = null; } catch {}}; // Kick off the initial Send socket.SetSocketOption(SocketOptionLevel.IP,SocketOptionName.MulticastInterface, IPAddress.Parse(MulticastIP).GetAddressBytes()); socket.SendToAsync(sendEvent); t.Start(); DateTime endTime = DateTime.Now.AddSeconds(searchTimeOut); do { System.Threading.Thread.Sleep(100); } while (DateTime.Now < endTime); return results; } } } '@ } #endregion Embedded C# SSDP Finder } process { # If -Force was sent, invalidate the cache if ($Force) { $script:CachedDiscoveredNanoLeafs = $null } if (-not $script:CachedDiscoveredNanoLeafs) { # If there is no cache, repopulate it. $script:CachedDiscoveredNanoLeafs = @([StartAutomating.NanoLeafFinder]::new().FindDevices($DeviceType, $SearchTimeout)) | Where-Object { # Write all devices found to Verbose Write-Verbose $_ $_ -like '*nanoleaf*' # but only pass down devices that could be rokus. } | ForEach-Object { $headerLines = @($_ -split '\r\n') # Split the header lines [PSCustomObject][Ordered]@{ # The IPAddress will be within the Location: header IPAddress = $( $( $headerLines -like 'LOCATION:*' -replace '^Location:\s{1,}' ) -as [uri] # We can force this into a URI ).DnsSafeHost # At which point the DNSSafeHost will be the IP MACAddress = $( @($headerLines -like 'nl-deviceid:*' -split ': ')[1] ) DeviceName = $( @($headerLines -like 'nl-devicename:*' -split ': ')[1] ) # Decorate the return type, so it formats correctly. PSTypeName = 'NanoLeaf.Discovery' } # Just doing a quick sanity check here # so we don't emit objects we can't accurately map the IP if ($out.IPAddress -like '*.*') { $out.IPAddress = [IPAddress]$out.IPAddress $out } } } $script:CachedDiscoveredNanoLeafs # Output the cached value. } } |