Created
November 4, 2025 03:16
-
-
Save keithga/e2ef180a837c214e5c00bb1dd9acc650 to your computer and use it in GitHub Desktop.
PowerShell module for Belkin WeMo switches
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .Synopsis | |
| Belkin WeMo Library | |
| .DESCRIPTION | |
| Powershell library for WeMo Switches (Model F7C063) | |
| .NOTES | |
| Now that We-Mo switches are NO LONGER being supported by belkin (boo), | |
| I will need a way to Reset, Join, and control my We-Mo switches Model F7C063. | |
| .EXAMPLE | |
| RESET is easy - Just Hold down the Power Switch while plugging into a power socket. | |
| .EXAMPLE | |
| To Configure a WeMo switch, you will need a PC with a Wi-Fi card. | |
| Simply run the cmdlet with the SSID and Password: | |
| Find-WeMoSwitches -Password "MySecret123" -SSID "MyHomeNetwork" | |
| This script will automatically connect to the first 'WeMo.Mini.XXX' SSID. | |
| It will connect with the device, ensure that the SSID is avaiable. | |
| It will then bind the device using the SSID and Password. | |
| This fuction REQUIRES openssl.exe from the git desktop program. | |
| .EXAMPLE | |
| get-wemoBasicEvent -ipaddress '192.168.1.111' | |
| will get the current state of the wemo switch at this IP address. | |
| 1 = On, 0 = Off | |
| .EXAMPLE | |
| Set-WeMoBasicEvent -ipaddress '192.168.1.111' -data 0 | |
| Will Set the current state of the wemo switch at this IP address. | |
| Where Data 1 = On, 0 = Off | |
| .EXAMPLE | |
| switch-WeMoBasicEvent -ipaddress '192.168.1.111' | |
| Will toggle the switch, turn off if on, turn on if off. | |
| .NOTES | |
| https://github.com/pywemo/pywemo | |
| https://github.com/vadimkantorov/wemosetup/blob/master/wemosetup.py | |
| More operations at: http://$($IPAddress):$($Port)/setup.xml | |
| Future: Add a command to find all WeMo switches on the local network. | |
| #> | |
| function Invoke-WeMoSoapCommand { | |
| [cmdletbinding()] | |
| param( | |
| [Parameter(Mandatory=$true)] | |
| [String] $Uri, | |
| [Parameter(Mandatory=$true)] | |
| [String] $SoapAction, | |
| [String] $SoapBody, | |
| [String] $Method = 'POST' | |
| ) | |
| $Soapactions = $SoapAction.trim("""") -split "#" | |
| $WebArgs = @{ | |
| Uri = $Uri | |
| Headers = @{ SOAPACTION = """$SoapAction"""; 'Content-Type' = 'text/xml' } | |
| Method = $Method | |
| Body = @" | |
| <?xml version="1.0" encoding="utf-8"?> | |
| <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> | |
| <s:Body> | |
| <u:$($SoapActions[1]) xmlns:u="$($SoapActions[0])"> | |
| $($SoapBody) | |
| </u:$($SoapActions[1])> | |
| </s:Body> | |
| </s:Envelope> | |
| "@ | |
| } | |
| $webArgs | convertto-json -Depth 2 | write-verbose | |
| $result = Invoke-WebRequest @WebArgs | |
| if ($Result.StatusCode -ne 200) { | |
| $result | out-string | Write-Warning | |
| } | |
| (($result | % content ) -as [xml] | % envelope | % body).innerxml | Write-verbose | |
| (($result | % content ) -as [xml] | % envelope | % body) | % childnodes | % Childnodes | % '#text' | Write-Output | |
| } | |
| function Get-WeMoBasicEvent { | |
| param( | |
| [Parameter(Mandatory=$true)] | |
| [string] $IPaddress, | |
| $Port | |
| ) | |
| if ( $port -eq $null ) { | |
| $port = Find-WeMoPort $IPaddress | |
| } | |
| $WeMoCommand = @{ | |
| Uri = "http://$($ipAddress):$($Port)/upnp/control/basicevent1" | |
| SoapAction = 'urn:Belkin:service:basicevent:1#GetBinaryState' | |
| } | |
| Invoke-WeMoSoapCommand @WeMoCommand | write-output | |
| } | |
| function Find-WeMoPort { | |
| param( | |
| [Parameter(Mandatory=$true)] | |
| [string] $IPaddress | |
| ) | |
| while ( -not ( Test-NetConnection -ComputerName $IPaddress -InformationLevel Quiet ) ) { | |
| write-verbose "Trying to connect..." | |
| } | |
| $foundPort = -1 | |
| foreach ( $port in (49153, 49152, 49154, 49151, 49155, 49156, 49157, 49158, 49159) ) { | |
| if ( Test-NetConnection -ComputerName $IPaddress -port $port -InformationLevel Quiet ) { | |
| write-verbose "Found: $Port" | |
| return $port | |
| } | |
| } | |
| throw "Unable to find responding port on device $IPAddress" | |
| } | |
| function Get-WeMoStatus { | |
| param( | |
| [Parameter(Mandatory=$true)] | |
| [string] $IPaddress, | |
| $Port | |
| ) | |
| if ( $port -eq $null ) { | |
| $port = Find-WeMoPort $IPaddress | |
| } | |
| (iwr "http://$($IPaddress):$($Port)/setup.xml" | % Content ) -as [XML] | % root | % device | write-output | |
| } | |
| function Set-WeMoBasicEvent { | |
| param( | |
| [Parameter(Mandatory=$true)] | |
| [string] $IPaddress, | |
| [Parameter(Mandatory=$true)] | |
| [int] $data, | |
| $Port | |
| ) | |
| if ( $port -eq $null ) { | |
| $port = Find-WeMoPort $IPaddress | |
| } | |
| $WeMoCommand = @{ | |
| Uri = "http://$($ipAddress):$($Port)/upnp/control/basicevent1" | |
| SoapAction = 'urn:Belkin:service:basicevent:1#SetBinaryState' | |
| SoapBody = "<BinaryState>{0}</BinaryState>" -f $data | |
| } | |
| Invoke-WeMoSoapCommand @WeMoCommand | select-object -first 1 | write-output | |
| } | |
| function switch-WeMoBasicEvent { | |
| param( | |
| [Parameter(Mandatory=$true)] | |
| [string] $IPaddress, | |
| $Port | |
| ) | |
| $Data = Get-WeMoBasicEvent @PSBoundParameters | |
| write-verbose "Data is: $Data" | |
| Set-WeMoBasicEvent @PSBoundParameters -data ( !($data -as [int]) -as [int] ) | |
| } | |
| Function Find-WeMoSwitches { | |
| [cmdletbinding()] | |
| param( | |
| [Parameter(Mandatory=$true)] | |
| [string] $Password, | |
| [string] $SSID | |
| ) | |
| $IPAddress = "10.22.22.1" | |
| #region Find WeMo Device and Connect | |
| $found = (netsh wlan show networks) | | |
| Where-Object { $_ -like "*SSID*" } | | |
| ForEach-Object { $_.Split(':')[1].Trim() } | | |
| Where-Object { $_ -like "Wemo.Mini.*" } | |
| if ( $Found -eq $null ) { | |
| throw "No WeMo devices found on Wi-Fi" | |
| } | |
| if ( $Found.count -gt 1 ) { | |
| $found = $found | Out-GridView -Title "Select the WeMo Switch to prepare" -OutputMode Single | |
| } | |
| @" | |
| <?xml version="1.0"?> | |
| <WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1"> | |
| <name>$Found</name> | |
| <SSIDConfig> | |
| <SSID> | |
| <name>$Found</name> | |
| </SSID> | |
| </SSIDConfig> | |
| <connectionType>ESS</connectionType> | |
| <connectionMode>manual</connectionMode> | |
| <MSM> | |
| <security> | |
| <authEncryption> | |
| <authentication>open</authentication> | |
| <encryption>none</encryption> | |
| <useOneX>false</useOneX> | |
| </authEncryption> | |
| </security> | |
| </MSM> | |
| <MacRandomization xmlns="http://www.microsoft.com/networking/WLAN/profile/v3"> | |
| <enableRandomization>true</enableRandomization> | |
| <randomizationSeed>1347463800</randomizationSeed> | |
| </MacRandomization> | |
| </WLANProfile> | |
| "@ | out-file "$env:temp\SSID$($Found).xml" -Encoding utf8 | |
| if ( -not ( netsh wlan show networks | where-object { $_ -like "*$($Found)*" } ) ) { | |
| netsh wlan add profile "FileName=$env:temp\SSID$($Found).xml" | |
| netsh wlan connect Name=$found | |
| } | |
| #endregion | |
| #region Test Device: | |
| $Port = Find-WeMoPort $IPAddress | |
| write-host "Found Device $IPAddress port: $Port" | |
| #endregion | |
| #region Get the AP List | |
| $WeMoCommand = @{ | |
| Uri = "http://$($ipAddress):$($Port)/upnp/control/WiFiSetup1" | |
| SoapAction = 'urn:Belkin:service:WiFiSetup:1#GetApList' | |
| } | |
| $APList = Invoke-WeMoSoapCommand @WeMoCommand | |
| $AP = $null | |
| if ( $SSID ) { | |
| $AP = $aplist -split "`n" | Where-Object { $_.startswith($SSID) } | select-object -first 1 | |
| } | |
| if ( $AP -eq $null ) { | |
| $AP = $aplist -split "`n" | Out-GridView -OutputMode Single -Title "Select your Wi-Fi Access Point" | |
| } | |
| if ( $AP -eq $null ) { | |
| throw "Need an Access Point selected" | |
| } | |
| $AP | write-verbose | |
| ` | |
| #endregion | |
| #region Get MetaInfo | |
| $WeMoCommand = @{ | |
| Uri = "http://$($ipAddress):$($Port)/upnp/control/metainfo1" | |
| SoapAction = 'urn:Belkin:service:metainfo:1#GetMetaInfo' | |
| } | |
| $MetaArray = Invoke-WeMoSoapCommand @WeMoCommand | |
| #endregion | |
| #region Get Encrypted String | |
| # Written under protest. | |
| $keydata = $metaarray.Substring(0,6) + $MetaArray.SubString(13,14) + $MetaArray.SubString(6,6) | |
| $keydata | write-verbose | |
| $salt = ([BitConverter]::ToString([Text.Encoding]::UTF8.GetBytes($keydata.Substring(0,8)))).Replace("-","").ToLower() | |
| $iv = ([BitConverter]::ToString([Text.Encoding]::UTF8.GetBytes($keydata.Substring(0,16)))).Replace("-","").ToLower() | |
| $password | out-file -Encoding ascii -NoNewline $env:TEMP\pw.txt | |
| echo "'C:\Program Files\git\usr\bin\openssl.exe' enc -aes-128-cbc -md md5 -S $salt -iv $iv -pass 'pass:$keydata' -v -a -in $env:TEMP\pw.txt" | |
| $encryptedPassword = & 'C:\Program Files\git\usr\bin\openssl.exe' enc -aes-128-cbc -md md5 -S $salt -iv $iv -pass "pass:$keydata" -v -a -in "$env:TEMP\pw.txt" | |
| del $env:temp\pw.txt | |
| $encryptedPassword += "{0:x2}" -f $encryptedPassword.Length | |
| $encryptedPassword += "{0:x2}" -f $password.Length | |
| write-verbose "encpw: $encryptedPassword $($encryptedPassword.length)" | |
| #endregion | |
| #region Set SSID and Password | |
| $APData = $ap.trim("""").split("|") | |
| $WeMoCommand = @{ | |
| Uri = "http://$($ipAddress):$($Port)/upnp/control/WiFiSetup1" | |
| SoapAction = 'urn:Belkin:service:WiFiSetup:1#ConnectHomeNetwork' | |
| SoapBody = @" | |
| <ssid>{0}</ssid> | |
| <channel>{1}</channel> | |
| <auth>{2}</auth> | |
| <encrypt>{3}</encrypt> | |
| <password>{4}</password> | |
| "@ -f $APData[0],$APData[1],$APData[3].Split("/")[0],$APData[3].Split("/")[1].trim(','),$encryptedPassword | |
| } | |
| $WeMoCommand | convertto-json -depth 5 | write-verbose | |
| $Status = Invoke-WeMoSoapCommand @WeMoCommand | |
| #endregion | |
| Write-Verbose "We may loose Wi-Fi connectivity with the device after this point." | |
| write-verbose "This is a good thing, look for the device on the regular network." | |
| sleep 10 | |
| #region Get Network stauts | |
| $WeMoCommand = @{ | |
| Uri = "http://$($ipAddress):$($Port)/upnp/control/WiFiSetup1" | |
| SoapAction = 'urn:Belkin:service:WiFiSetup:1#GetNetworkStatus' | |
| } | |
| $Status = Invoke-WeMoSoapCommand @WeMoCommand | |
| if ( $status -notin (1,3) ) { | |
| write-warning "Status $Status did not return 1,3 after setting Wi-Fi" | |
| } | |
| #endregion | |
| #region Done | |
| $WeMoCommand = @{ | |
| Uri = "http://$($ipAddress):$($Port)/upnp/control/WiFiSetup1" | |
| SoapAction = 'urn:Belkin:service:WiFiSetup:1#CloseSetup' | |
| } | |
| $Status = Invoke-WeMoSoapCommand @WeMoCommand | |
| if ( $Status -ne "success" ) { | |
| throw "CloseSetup() did not return Success" | |
| } | |
| #endregion | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment