Internet of PowerShell
Published Oct 09 2022 04:12 PM 4,501 Views



Hi folks! My name is Felipe Binotto, Cloud Solution Architect, based in Australia.

I’m excited to write this post because I bumped into this nice little app which connects to an IoT Hub and lets us run scripts on-premises or in the cloud from anywhere in the world without any requirement for inbound ports.

The credits go to @Scott Holden (AUSTRALIA)  who wrote the app (I’m also borrowing his app name as the title of this post) and @Marc Kean who told me about it. I have forked his project for this demonstration, but you can find the original project HERE.

I have recently used its functionality as part of a Start/Stop VM solution based on Automation Account. The customer was using SCOM on-premises and had the requirement to put the server in maintenance mode before the server could be stopped. The Automation Account can’t access on-premises resources and I would have to use a Hybrid Worker; however, this is much cooler and simpler.

I won’t get in the details around SCOM, but I will demonstrate how you can set it up and run the script from anywhere with a simple call to the IoT Hub.




The following are the prerequisites which I will not cover in this post, and you should already have them in place before you start:


  • Azure Subscription
  • Automation Account


High-Level Steps


The following are the high-level steps on what we will do and the order we will do it:


  • Create a new IoT Hub
  • Download the IoPS app from Scott’s Github
  • Set it up on a server
  • Create a simple script which will run on the server
  • Make the API call to IoT Hub
  • Check the results


Getting Started


Coding time! Let’s start by creating a Resource Group with a IoT Hub inside. I recommend you run these commands on the server where you want the scripts to be executed.



# Creates Resource Group
New-AzResourceGroup -Name IoTHub-RG -Location "Australia East"

# Creates IoT Hub
New-AzIotHub -ResourceGroupName IoTHub-RG -Name IoTHub-Bridge -Location "Australia East" -Sku F1 -Units 1

# Retrieves IoT Hub Key
$IoTKey = (Get-AzIotHubKey -ResourceGroupName IoTHub-RG -Name IoTHub-Bridge | ? KeyName -eq "iothubowner").PrimaryKey



OK, so now you have a free IoT Hub in the IoTHub-RG located in Australia East. I named it IoTHub-Bridge because I consider it a bridge to the server where we are going to execute the script. Make sure you change the name as it must be unique. We also retrieve the IoT Hub key which will be used to make the call to IoT Hub.


Next, we will create a Device in IoT Hub which will represent our server where we are going to run the IoPS app and execute the scripts. We also retrieve the connection string which will be used to the config file of the IoPS app.



# Creates IoT Hub Device
Add-AzIotHubDevice -ResourceGroupName IoTHub-RG -IotHubName Bridge -DeviceId Runner -AuthMethod shared_private_key

# Retrieves IoT Hub Device Connection String
$connectionString = Get-AzIotHubDeviceConnectionString -ResourceGroupName IoTHub-RG -IotHubName IoTHub-Bridge -DeviceId Runner



I named the device Runner because it makes sense to me – it will run the scripts.

Now it is time to download the IoPS app from my repo and extract it to the same directory where you are downloading the ZIP file. It will extract to the IoPS subfolder. You can also clone my repo and build the app using Visual Studio.



# Downloads IoPS
Invoke-WebRequest -Uri -OutFile

# Unzips IoPS
Expand-Archive -Path -DestinationPath IoPS



Next step is to make some changes to the IoPS.exe.config file so that the app knows how to connect to the IoT Hub. 



# Modifies IoPS Config File
cd IoPS
$file = (Get-Content .\IoPS.exe.config)
$file[3] = $file[3].replace('""','"C:\Temp\MyScripts"')
$file[4] = $file[4].replace('""',"`"$($connectionString.ConnectionString)`"")
$file | Set-Content .\IoPS.exe.config



In the code above, we are saying that our scripts will be in the C:\Temp\MyScripts folder and we are passing the connection string which we retrieved earlier in the $connectionString variable.


Run the application.



# Runs IoPS



You should see the below. It means we have connected to the IoT Hub and it is listening.




Now, create the script which we will use for testing.



# Creates test script
New-Item -Path C:\Temp\MyScripts -ItemType Directory
New-Item -Path C:\Temp\MyScripts -Name Test.ps1 -ItemType File -Value '"param($computerName); "Hello from $computerName" | Out-File C:\Temp\MyScripts\HelloWorld.txt"'



When we call this script, it should create a new text file with the message “Hello from the computer which made the call”. I’m doing this to demonstrate how you can pass parameters when you make the call to the IoT Hub.


The script will all the steps up to this point can be viewed HERE.


We are almost there! Now, in the computer where you will make the call to IoT Hub, create the script with the required content.



$script = @"
# IoT Connection details
`$iotHubName = "IotHub-Bridge"
`$policyName = "iothubowner"
`$deviceId = "Runner"
`$directMethodUrl = "https://`$(`$iotHubName)`$(`$deviceId)/methods?api-version=2018-06-30"
`$authResource = "`$(`$iotHubName)"
`$epoch = New-Object System.DateTime @(1970,1,1,0,0,0,[DateTimeKind]::Utc)
`$authExpiry = [int]([DateTime]::UtcNow.AddHours(1).Subtract(`$epoch).TotalSeconds)
`$authPayload = `"`$authResource``n`$authExpiry`"
`$Key = [Convert]::FromBase64String(`$IoTKey)
`$hashobj = New-Object System.Security.Cryptography.HMACSHA256 (,`$Key)
`$authHash = [Convert]::ToBase64String(`$hashobj.ComputeHash([Text.Encoding]::ASCII.GetBytes(`$authPayload)))

`$parameters = @{
    "computerName" = "$Env:COMPUTERNAME";

`$payload = @{
    "ScriptName" = "Test.ps1";
    "Parameters" = `$parameters
    "ExecutionPolicy" = 'Bypass'

`$jsonPayload = @{
    "methodName" = "ExecuteScript";
    "responseTimeoutInSeconds" = 200;
    "payload" = `$payload
} | ConvertTo-Json -Compress

`$authSig = "SharedAccessSignature sr=`$(`$authResource)&sig=`$([System.Uri]::EscapeDataString(`$authHash))&se=`$(`$authExpiry)&skn=`$(`$policyName)"

`$response = Start-Job -ScriptBlock {Invoke-WebRequest -Method POST ``
                -UseBasicParsing ``
                -Uri `$args[0] ``
                -ContentType "application/json" ``
                -Body `$args[1] ``
                -Headers @{ "Authorization" = `$args[2] }} ``
                -ArgumentList `$directMethodUrl, `$jsonPayload, `$authSig

$script | Out-File C:\Temp\MyScripts\IoT.ps1



With the code above, we are creating a script in C:\Temp\MyScripts, named IoT.ps1. Make sure you replace the $iotHubName with your IoT Hub name and if you changed the $deviceId, change that too.


You can also view the code above HERE.


Time to execute the script to call the IoT Hub.






If you followed all the steps correctly, you should see the below which means the script executed successfully.




You should also see a new text file in the same folder, named HelloWorld.txt with the following content.

Hello from TheComputerNameThatMadeTheCall




I don’t think about you, but I can think of many ways of leveraging this. Super, super cool!

Worst case scenario you will get a bit better in coding and will understand a bit more about IoT Hub.

I hope this was informative to you and thanks for reading!




The sample scripts are not supported under any Microsoft standard support program or service. The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.


Version history
Last update:
‎Oct 09 2022 04:14 PM
Updated by: