SetIoTCentralPropsForDeviceGroup/Src/SetIoTCentralPropsForDeviceGroup/Program.cs (229 lines of code) (raw):
/* Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License. */
using Microsoft.Identity.Client;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
namespace SetIoTCentralPropsForDeviceGroup
{
class Program
{
/// <summary>
/// Azure Sphere Public API resource URI
/// </summary>
private static readonly List<string> Scopes = new List<string>() { "https://sphere.azure.net/api/user_impersonation" };
/// <summary>
/// Azure Sphere Public API client application ID.
/// </summary>
private const string ClientApplicationId = "0B1C8F7E-28D2-4378-97E2-7D7D63F7C87F";
/// <summary>
/// Azure Tenant Tenant ID for Azure Sphere.
/// </summary>
public const string Tenant = "7d71c83c-ccdf-45b7-b3c9-9c41b94406d9";
/// <summary>
/// Azure Sphere Public API URI
/// </summary>
private const string AzureSphereApiUri = "https://prod.core.sphere.azure.net";
/// <summary>
/// Program entry-point.
/// </summary>
/// <returns>Zero on success, otherwise non-zero.</returns>
public static async Task<int> Main(string[] args)
{
if (!validateCmdArgs(args))
return -1;
// args are.
// args[0] : Azure IoT Central app URL (example: https://myapp.azureiotcentral.com)
// args[1] : Azure IoT Central API Token (quoted, since there's probably a space)
// args[2] : Azure Sphere Device Group GUID (containing the list of devices to apply the property change)
// args[3] : JSON file containing the property to change - example: {"thermometerTelemetryUploadEnabled": true}
// Note: the JSON containing the property to change needs to be a read/write property in the IoT Central Device Template.
// The DeviceUpdateList will contain the Device IDs for devices in the supplied Device Group
// Get JSON from args[3]
string json = LoadJsonFromFile(args[3]);
if (!IsValidJson(json))
{
Console.WriteLine("The JSON you supplied doesn't appear to be correctly formatted, please fix");
return -1;
}
List<string> DeviceUpdateList = new List<string>();
string token = await GetTokenAsync().ConfigureAwait(false);
if (string.IsNullOrEmpty(token))
{
Console.WriteLine("Failed to get token");
return -1;
}
string result = GetData("tenants", token);
List<Tenant> tenantList = JsonSerializer.Deserialize<List<Tenant>>(result);
if (tenantList == null || tenantList.Count == 0)
{
Console.WriteLine("No tenants found\n");
return -1;
}
Console.WriteLine($"I found {tenantList.Count} tenant(s)");
string tenantId = string.Empty;
string tenantName = string.Empty;
foreach (Tenant tenant in tenantList)
{
result = GetData($"tenants/{tenant.Id}/devices", token);
Devices devices = JsonSerializer.Deserialize<Devices>(result);
foreach (Item item in devices.Items)
{
if (item.DeviceGroupId != null && item.DeviceGroupId == args[2])
{
DeviceUpdateList.Add(item.DeviceId);
}
}
}
if (DeviceUpdateList.Count == 0)
{
Console.WriteLine("No devices found for the Device Group guid you provided");
return -1;
}
Console.WriteLine($"{DeviceUpdateList.Count} devices will have their properties updated");
string iotcAppUrl = args[0].TrimEnd('/');
foreach(string s in DeviceUpdateList)
{
if (isValidIoTCDevice(iotcAppUrl, s.ToLower(), args[1]))
{
Console.Write($"Setting device id: {s} - ");
if (setIoTCDeviceProperties(iotcAppUrl, s.ToLower(), args[1], json))
{
Console.WriteLine("Success");
}
else
{
Console.WriteLine("Failed");
}
}
else
{
Debug.WriteLine($"Device not found in IoTC: {s}");
}
}
return 0;
}
/// <summary>
/// Validate the CmdArgs
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private static bool validateCmdArgs(string [] args)
{
Debug.WriteLine($"{args.Count()} args passed in");
foreach(string s in args)
{
Debug.WriteLine(s);
}
if (args.Count() != 4)
{
Console.WriteLine("App requires the following command line arguments:");
Console.WriteLine("Azure IoT Central Application URL i.e. https://myapp.azureiotcentral.com");
Console.WriteLine("Azure IoT Central Application API Token");
Console.WriteLine("Azure Sphere Device Group Id (guid)");
Console.WriteLine("JSON file path containing properties to be set - i.e. {\"thermometerTelemetryUploadEnabled\": true}");
return false;
}
// check for valid URL
Uri uriResult;
bool result = Uri.TryCreate(args[0], UriKind.Absolute, out uriResult)
&& uriResult.Scheme == Uri.UriSchemeHttps && args[0].ToLower().Contains("azureiotcentral.com");
if (!result)
{
Console.WriteLine("The Azure IoT Central URL you provided doesn't look valid, please fix");
return false;
}
if (!args[1].StartsWith("SharedAccessSignature"))
{
Console.WriteLine("The API token you provided doesn't look valid, please fix");
return false;
}
Guid guidOutput;
bool isValidGuid = Guid.TryParse(args[2], out guidOutput);
if (!isValidGuid)
{
Console.WriteLine("The guid you supplied doesn't look right, please fix");
return false;
}
if (!File.Exists(args[3]))
{
Console.WriteLine($"File '{args[3]}' not found, please fix");
}
return true;
}
/// <summary>
/// setIoTCDeviceProperties - sets the user supplied JSON to the device in IoTC
/// </summary>
/// <param name="baseURI"></param> This is the IoTC App URL, example: https://myapp.azureiotcentral.com
/// <param name="deviceId"></param> The device id that to apply settings to
/// <param name="token"></param> The IoTC API Token
/// <param name="JsonContent"></param> Content to be applied to the device
/// <returns></returns>
// PUT https://appsubdomain.azureiotcentral.com/api/devices/{deviceId}/properties
static bool setIoTCDeviceProperties(string baseURI, string deviceId, string token, string JsonContent)
{
RestClient client = new RestClient(baseURI);
var request = new RestRequest($"api/devices/{deviceId.ToLower()}/properties?api-version=2022-05-31", Method.Patch).AddJsonBody(JsonContent);
request.AddParameter("Authorization", token, ParameterType.HttpHeader);
var response = client.Execute(request);
bool retVal = false;
if (response.IsSuccessful)
{
retVal = true;
Debug.WriteLine($"Success: {response.Content}");
}
else
{
Debug.WriteLine($"Failed to set property: {response.Content}");
}
return retVal;
}
/// <summary>
/// Determine whether the Azure Sphere Device Id exists in the IoTC app
/// </summary>
/// <param name="baseURI"></param> Azure IoT Central base App URL
/// <param name="deviceId"></param> Azure Sphere Device Id
/// <param name="token"></param> Azure Sphere API Token
/// <returns></returns>
// GET https://appsubdomain.azureiotcentral.com/api/devices/{deviceId}
private static bool isValidIoTCDevice(string baseURI, string deviceId, string token)
{
bool retVal = false;
RestClient client = new RestClient(baseURI);
var request = new RestRequest($"api/devices/{deviceId}?api-version=2022-05-31", Method.Get);
request.AddParameter("Authorization", token, ParameterType.HttpHeader);
var response = client.Execute(request);
if (response.IsSuccessful)
{
retVal = true;
Debug.WriteLine($"Device Get Success: {response.Content}");
}
return retVal;
}
/// <summary>
/// Execute an Http GET on the Azure Sphere public API
/// </summary>
/// <param name="relativeUrl"></param> API path to execute
/// <param name="token"></param> Azure Sphere API Token
/// <returns></returns>
private static string GetData(string relativeUrl, string token)
{
var client = new RestClient(AzureSphereApiUri);
var request = new RestRequest($"/v2/{relativeUrl}", Method.Get);
request.AddParameter("Authorization", string.Format("Bearer " + token), ParameterType.HttpHeader);
var response = client.Execute(request);
if (response.IsSuccessful)
{
return response.Content;
}
return string.Empty;
}
/// <summary>
/// Get an Azure Sphere access token based on user login credentials
/// </summary>
/// <returns></returns>
private static async Task<string> GetTokenAsync()
{
IPublicClientApplication publicClientApp = PublicClientApplicationBuilder
.Create(ClientApplicationId)
.WithAuthority(AzureCloudInstance.AzurePublic, Tenant)
.WithRedirectUri("http://localhost")
.Build();
AuthenticationResult authResult = await publicClientApp.AcquireTokenInteractive(Scopes).ExecuteAsync();
return authResult.AccessToken;
}
/// <summary>
/// Function to load JSON from a file
/// </summary>
/// <param name="JsonFilePath"></param>
/// <returns>String.Empty on failure, or string containing Json on success</returns>
private static string LoadJsonFromFile(string JsonFilePath)
{
string result = String.Empty;
if (File.Exists(JsonFilePath))
{
result = File.ReadAllText(JsonFilePath);
}
else
{
Console.WriteLine($"File '{JsonFilePath}' not found.");
}
return result;
}
/// <summary>
/// Check to see if user input Json is valid
/// </summary>
/// <param name="strInput"></param>
/// <returns></returns>
private static bool IsValidJson(string strInput)
{
bool isValid = true;
try
{
JsonDocument doc = JsonDocument.Parse(strInput);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
isValid = false;
}
return isValid;
}
}
}
// Tenant helper for JSON deserialization
public class Tenant
{
public string Id { get; set; }
public string Name { get; set; }
public string[] Roles { get; set; }
}
// Devices helper for JSON deserialization
public class Devices
{
public Item[] Items { get; set; }
public object ContinuationToken { get; set; }
}
public class Item
{
public string DeviceId { get; set; }
public string TenantId { get; set; }
public string ProductId { get; set; }
public string DeviceGroupId { get; set; }
public int ChipSku { get; set; }
}