Skip to content

Commit 887fab0

Browse files
authored
Merge pull request #11425 from anusapan/iot-device-twin
[IoT Hub] Manage IoT device twin configuration.
2 parents a3af51a + 70039f5 commit 887fab0

File tree

15 files changed

+1757
-428
lines changed

15 files changed

+1757
-428
lines changed

src/IotHub/IotHub.Test/ScenarioTests/IotHubDPDeviceTests.ps1

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,30 @@ function Test-AzureRmIotHubDeviceLifecycle
6363
Assert-True { $newDevice3.Authentication.Type -eq 'CertificateAuthority' }
6464
Assert-False { $newDevice3.Capabilities.IotEdge }
6565

66+
# Get device twin
67+
$device1twin = Get-AzIotHubDeviceTwin -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1
68+
Assert-True { $device1twin.DeviceId -eq $device1}
69+
70+
# Partial update device twin
71+
$tags1 = @{}
72+
$tags1.Add('Test1', '1')
73+
$updateddevice1twin1 = Update-AzIotHubDeviceTwin -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -tag $tags1 -Partial
74+
Assert-True { $updateddevice1twin1.DeviceId -eq $device1}
75+
Assert-True { $updateddevice1twin1.tags.Count -eq 1}
76+
77+
$tags2 = @{}
78+
$tags2.Add('Test2', '2')
79+
$updateddevice1twin2 = Update-AzIotHubDeviceTwin -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -tag $tags2 -Partial
80+
Assert-True { $updateddevice1twin2.DeviceId -eq $device1}
81+
Assert-True { $updateddevice1twin2.tags.Count -eq 2}
82+
83+
# Update device twin
84+
$tags3 = @{}
85+
$tags3.Add('Test3', '3')
86+
$updateddevice1twin3 = Update-AzIotHubDeviceTwin -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -tag $tags3
87+
Assert-True { $updateddevice1twin3.DeviceId -eq $device1}
88+
Assert-True { $updateddevice1twin3.tags.Count -eq 1}
89+
6690
# Get all devices
6791
$devices = Get-AzIotHubDevice -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName
6892
Assert-True { $devices.Count -eq 3}

src/IotHub/IotHub.Test/SessionRecords/Microsoft.Azure.Commands.IotHub.Test.ScenarioTests.IotHubDPDeviceTests/TestAzureIotHubDeviceLifecycle.json

Lines changed: 930 additions & 426 deletions
Large diffs are not rendered by default.

src/IotHub/IotHub/Az.IotHub.psd1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ CmdletsToExport = 'Add-AzIotHubKey', 'Get-AzIotHubEventHubConsumerGroup',
100100
'Get-AzIotHubModuleConnectionString', 'Get-AzIotHubDeviceParent',
101101
'Set-AzIotHubDeviceParent', 'Add-AzIotHubDeviceChildren',
102102
'Remove-AzIotHubDeviceChildren', 'Get-AzIotHubDeviceChildren',
103-
'Get-AzIotHubDistributedTracing', 'Set-AzIotHubDistributedTracing'
103+
'Get-AzIotHubDistributedTracing', 'Set-AzIotHubDistributedTracing',
104+
'Get-AzIotHubDeviceTwin', 'Update-AzIotHubDeviceTwin'
104105
# Variables to export from this module
105106
# VariablesToExport = @()
106107

src/IotHub/IotHub/ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
* Added support to manage distributed settings per-device. New Cmdlets are:
2222
- `Get-AzIotHubDistributedTracing`
2323
- `Set-AzIotHubDistributedTracing`
24+
* Manage IoT device twin configuration, New cmdlets are:
25+
- `Get-AzIotHubDeviceTwin`
26+
- `Update-AzIotHubDeviceTwin`
2427

2528
## Version 2.2.0
2629
* Added support to manage devices in an Iot Hub. New Cmdlets are:

src/IotHub/IotHub/Common/IotHubDataPlaneUtils.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ public static IEnumerable<PSModules> ToPSModules(IEnumerable<Module> modules)
7070
return IotHubUtils.ConvertObject<IEnumerable<Module>, IEnumerable<PSModules>>(modules.ToList());
7171
}
7272

73+
public static PSDeviceTwin ToPSDeviceTwin(Twin deviceTwin)
74+
{
75+
return IotHubUtils.ConvertObject<Twin, PSDeviceTwin>(deviceTwin);
76+
}
77+
7378
public static void ValidateDeviceTracing(string DeviceId, string Sku, string Location, bool IsEdgeDevice)
7479
{
7580
if (!TracingAllowedForLocation.Any(location => location.Equals(Location, StringComparison.OrdinalIgnoreCase)))

src/IotHub/IotHub/Common/IotHubUtils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ public static string GetSubscriptionId(string Id)
385385
public static string GetIotHubName(string Id)
386386
{
387387
if (string.IsNullOrEmpty(Id)) return null;
388-
Regex r = new Regex(@"(.*?)/IotHubs/(?<iothubname>\S+)/certificates/(.*?)", RegexOptions.IgnoreCase);
388+
Regex r = new Regex(@"(.*?)/IotHubs/(?<iothubname>[^\s/]+)", RegexOptions.IgnoreCase);
389389
Match m = r.Match(Id);
390390
return m.Success ? m.Groups["iothubname"].Value : null;
391391
}

src/IotHub/IotHub/IotHub.format.ps1xml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,5 +928,42 @@
928928
</ListEntries>
929929
</ListControl>
930930
</View>
931+
<View>
932+
<Name>
933+
Microsoft.Azure.Commands.Management.IotHub.Models.PSDeviceTwin
934+
</Name>
935+
<ViewSelectedBy>
936+
<TypeName>Microsoft.Azure.Commands.Management.IotHub.Models.PSDeviceTwin</TypeName>
937+
</ViewSelectedBy>
938+
<ListControl>
939+
<ListEntries>
940+
<ListEntry>
941+
<ListItems>
942+
<ListItem>
943+
<PropertyName>DeviceId</PropertyName>
944+
</ListItem>
945+
<ListItem>
946+
<Label>Tags</Label>
947+
<ScriptBlock>$_.Tags</ScriptBlock>
948+
</ListItem>
949+
<ListItem>
950+
<Label>Desired</Label>
951+
<ScriptBlock>$_.Properties.Desired</ScriptBlock>
952+
</ListItem>
953+
<ListItem>
954+
<Label>Reported</Label>
955+
<ScriptBlock>$_.Properties.Reported</ScriptBlock>
956+
</ListItem>
957+
<ListItem>
958+
<PropertyName>ETag</PropertyName>
959+
</ListItem>
960+
<ListItem>
961+
<PropertyName>Version</PropertyName>
962+
</ListItem>
963+
</ListItems>
964+
</ListEntry>
965+
</ListEntries>
966+
</ListControl>
967+
</View>
931968
</ViewDefinitions>
932969
</Configuration>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
namespace Microsoft.Azure.Commands.Management.IotHub
16+
{
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Management.Automation;
20+
using Microsoft.Azure.Commands.Management.IotHub.Common;
21+
using Microsoft.Azure.Commands.Management.IotHub.Models;
22+
using Microsoft.Azure.Devices;
23+
using Microsoft.Azure.Devices.Shared;
24+
using Microsoft.Azure.Management.IotHub;
25+
using Microsoft.Azure.Management.IotHub.Models;
26+
using ResourceManager.Common.ArgumentCompleters;
27+
28+
[Cmdlet("Get", ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "IotHubDeviceTwin", DefaultParameterSetName = ResourceParameterSet)]
29+
[OutputType(typeof(PSDeviceTwin))]
30+
public class GetAzIotHubDeviceTwin : IotHubBaseCmdlet
31+
{
32+
private const string ResourceIdParameterSet = "ResourceIdSet";
33+
private const string ResourceParameterSet = "ResourceSet";
34+
private const string InputObjectParameterSet = "InputObjectSet";
35+
36+
[Parameter(Position = 0, Mandatory = true, ParameterSetName = InputObjectParameterSet, ValueFromPipeline = true, HelpMessage = "IotHub object")]
37+
[ValidateNotNullOrEmpty]
38+
public PSIotHub InputObject { get; set; }
39+
40+
[Parameter(Position = 0, Mandatory = true, ParameterSetName = ResourceParameterSet, HelpMessage = "Name of the Resource Group")]
41+
[ValidateNotNullOrEmpty]
42+
[ResourceGroupCompleter]
43+
public string ResourceGroupName { get; set; }
44+
45+
[Parameter(Position = 0, Mandatory = true, ParameterSetName = ResourceIdParameterSet, ValueFromPipelineByPropertyName = true, HelpMessage = "IotHub Resource Id")]
46+
[ValidateNotNullOrEmpty]
47+
[ResourceIdCompleter("Microsoft.Devices/IotHubs")]
48+
public string ResourceId { get; set; }
49+
50+
[Parameter(Position = 1, Mandatory = true, ParameterSetName = ResourceParameterSet, HelpMessage = "Name of the Iot Hub")]
51+
[ValidateNotNullOrEmpty]
52+
public string IotHubName { get; set; }
53+
54+
[Parameter(Position = 1, Mandatory = true, ParameterSetName = InputObjectParameterSet, HelpMessage = "Target Device Id.")]
55+
[Parameter(Position = 1, Mandatory = true, ParameterSetName = ResourceIdParameterSet, HelpMessage = "Target Device Id.")]
56+
[Parameter(Position = 2, Mandatory = true, ParameterSetName = ResourceParameterSet, HelpMessage = "Target Device Id.")]
57+
[ValidateNotNullOrEmpty]
58+
public string DeviceId { get; set; }
59+
60+
public override void ExecuteCmdlet()
61+
{
62+
IotHubDescription iotHubDescription;
63+
if (ParameterSetName.Equals(InputObjectParameterSet))
64+
{
65+
this.ResourceGroupName = this.InputObject.Resourcegroup;
66+
this.IotHubName = this.InputObject.Name;
67+
iotHubDescription = IotHubUtils.ConvertObject<PSIotHub, IotHubDescription>(this.InputObject);
68+
}
69+
else
70+
{
71+
if (ParameterSetName.Equals(ResourceIdParameterSet))
72+
{
73+
this.ResourceGroupName = IotHubUtils.GetResourceGroupName(this.ResourceId);
74+
this.IotHubName = IotHubUtils.GetIotHubName(this.ResourceId);
75+
}
76+
77+
iotHubDescription = this.IotHubClient.IotHubResource.Get(this.ResourceGroupName, this.IotHubName);
78+
}
79+
80+
IEnumerable<SharedAccessSignatureAuthorizationRule> authPolicies = this.IotHubClient.IotHubResource.ListKeys(this.ResourceGroupName, this.IotHubName);
81+
SharedAccessSignatureAuthorizationRule policy = IotHubUtils.GetPolicy(authPolicies, PSAccessRights.RegistryWrite);
82+
PSIotHubConnectionString psIotHubConnectionString = IotHubUtils.ToPSIotHubConnectionString(policy, iotHubDescription.Properties.HostName);
83+
RegistryManager registryManager = RegistryManager.CreateFromConnectionString(psIotHubConnectionString.PrimaryConnectionString);
84+
85+
Twin deviceTwin = registryManager.GetTwinAsync(this.DeviceId).GetAwaiter().GetResult();
86+
87+
if (deviceTwin == null)
88+
{
89+
throw new ArgumentException($"The entered device \"{this.DeviceId}\" doesn't exist.");
90+
}
91+
92+
this.WriteObject(IotHubDataPlaneUtils.ToPSDeviceTwin(deviceTwin));
93+
}
94+
}
95+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
namespace Microsoft.Azure.Commands.Management.IotHub
16+
{
17+
using System;
18+
using System.Collections;
19+
using System.Collections.Generic;
20+
using System.Management.Automation;
21+
using Microsoft.Azure.Commands.Management.IotHub.Common;
22+
using Microsoft.Azure.Commands.Management.IotHub.Models;
23+
using Microsoft.Azure.Devices;
24+
using Microsoft.Azure.Devices.Shared;
25+
using Microsoft.Azure.Management.IotHub;
26+
using Microsoft.Azure.Management.IotHub.Models;
27+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
28+
using Newtonsoft.Json;
29+
using ResourceManager.Common.ArgumentCompleters;
30+
31+
[Cmdlet("Update", ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "IotHubDeviceTwin", DefaultParameterSetName = ResourceParameterSet, SupportsShouldProcess = true)]
32+
[OutputType(typeof(PSDeviceTwin))]
33+
public class UpdateAzIotHubDeviceTwin : IotHubBaseCmdlet
34+
{
35+
private const string ResourceIdParameterSet = "ResourceIdSet";
36+
private const string ResourceParameterSet = "ResourceSet";
37+
private const string InputObjectParameterSet = "InputObjectSet";
38+
39+
[Parameter(Position = 0, Mandatory = true, ParameterSetName = InputObjectParameterSet, ValueFromPipeline = true, HelpMessage = "IotHub object")]
40+
[ValidateNotNullOrEmpty]
41+
public PSIotHub InputObject { get; set; }
42+
43+
[Parameter(Position = 0, Mandatory = true, ParameterSetName = ResourceParameterSet, HelpMessage = "Name of the Resource Group")]
44+
[ValidateNotNullOrEmpty]
45+
[ResourceGroupCompleter]
46+
public string ResourceGroupName { get; set; }
47+
48+
[Parameter(Position = 0, Mandatory = true, ParameterSetName = ResourceIdParameterSet, ValueFromPipelineByPropertyName = true, HelpMessage = "IotHub Resource Id")]
49+
[ValidateNotNullOrEmpty]
50+
[ResourceIdCompleter("Microsoft.Devices/IotHubs")]
51+
public string ResourceId { get; set; }
52+
53+
[Parameter(Position = 1, Mandatory = true, ParameterSetName = ResourceParameterSet, HelpMessage = "Name of the Iot Hub")]
54+
[ValidateNotNullOrEmpty]
55+
public string IotHubName { get; set; }
56+
57+
[Parameter(Position = 1, Mandatory = true, ParameterSetName = InputObjectParameterSet, HelpMessage = "Target Device Id.")]
58+
[Parameter(Position = 1, Mandatory = true, ParameterSetName = ResourceIdParameterSet, HelpMessage = "Target Device Id.")]
59+
[Parameter(Position = 2, Mandatory = true, ParameterSetName = ResourceParameterSet, HelpMessage = "Target Device Id.")]
60+
[ValidateNotNullOrEmpty]
61+
public string DeviceId { get; set; }
62+
63+
[Parameter(Mandatory = false, ParameterSetName = InputObjectParameterSet, HelpMessage = "Add or update the tags property in a device twin.")]
64+
[Parameter(Mandatory = false, ParameterSetName = ResourceIdParameterSet, HelpMessage = "Add or update the tags property in a device twin.")]
65+
[Parameter(Mandatory = false, ParameterSetName = ResourceParameterSet, HelpMessage = "Add or update the tags property in a device twin.")]
66+
[ValidateNotNullOrEmpty]
67+
public Hashtable Tag { get; set; }
68+
69+
[Parameter(Mandatory = false, ParameterSetName = InputObjectParameterSet, HelpMessage = "Add or update the desired property in a device twin.")]
70+
[Parameter(Mandatory = false, ParameterSetName = ResourceIdParameterSet, HelpMessage = "Add or update the desired property in a device twin.")]
71+
[Parameter(Mandatory = false, ParameterSetName = ResourceParameterSet, HelpMessage = "Add or update the desired property in a device twin.")]
72+
[ValidateNotNullOrEmpty]
73+
public Hashtable Desired { get; set; }
74+
75+
[Parameter(Mandatory = false, HelpMessage = "Allows to only partially update the tags and desired properties of a device twin.")]
76+
public SwitchParameter Partial { get; set; }
77+
78+
public override void ExecuteCmdlet()
79+
{
80+
if (ShouldProcess(this.DeviceId, Properties.Resources.UpdateIotHubDeviceTwin))
81+
{
82+
IotHubDescription iotHubDescription;
83+
if (ParameterSetName.Equals(InputObjectParameterSet))
84+
{
85+
this.ResourceGroupName = this.InputObject.Resourcegroup;
86+
this.IotHubName = this.InputObject.Name;
87+
iotHubDescription = IotHubUtils.ConvertObject<PSIotHub, IotHubDescription>(this.InputObject);
88+
}
89+
else
90+
{
91+
if (ParameterSetName.Equals(ResourceIdParameterSet))
92+
{
93+
this.ResourceGroupName = IotHubUtils.GetResourceGroupName(this.ResourceId);
94+
this.IotHubName = IotHubUtils.GetIotHubName(this.ResourceId);
95+
}
96+
97+
iotHubDescription = this.IotHubClient.IotHubResource.Get(this.ResourceGroupName, this.IotHubName);
98+
}
99+
100+
IEnumerable<SharedAccessSignatureAuthorizationRule> authPolicies = this.IotHubClient.IotHubResource.ListKeys(this.ResourceGroupName, this.IotHubName);
101+
SharedAccessSignatureAuthorizationRule policy = IotHubUtils.GetPolicy(authPolicies, PSAccessRights.RegistryWrite);
102+
PSIotHubConnectionString psIotHubConnectionString = IotHubUtils.ToPSIotHubConnectionString(policy, iotHubDescription.Properties.HostName);
103+
RegistryManager registryManager = RegistryManager.CreateFromConnectionString(psIotHubConnectionString.PrimaryConnectionString);
104+
105+
Twin deviceTwin = registryManager.GetTwinAsync(this.DeviceId).GetAwaiter().GetResult();
106+
107+
if (deviceTwin == null)
108+
{
109+
throw new ArgumentException($"The entered device \"{this.DeviceId}\" doesn't exist.");
110+
}
111+
112+
if (this.IsParameterBound(c => c.Tag))
113+
{
114+
deviceTwin.Tags = new TwinCollection(JsonConvert.SerializeObject(this.Tag));
115+
}
116+
117+
if (this.IsParameterBound(c => c.Desired))
118+
{
119+
deviceTwin.Properties.Desired = new TwinCollection(JsonConvert.SerializeObject(this.Desired));
120+
}
121+
122+
if (this.Partial.IsPresent)
123+
{
124+
this.WriteObject(registryManager.UpdateTwinAsync(this.DeviceId, deviceTwin, deviceTwin.ETag).GetAwaiter().GetResult());
125+
}
126+
else
127+
{
128+
this.WriteObject(registryManager.ReplaceTwinAsync(this.DeviceId, deviceTwin, deviceTwin.ETag).GetAwaiter().GetResult());
129+
}
130+
}
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)