Skip to content

Commit ef6da90

Browse files
authored
[IoT Hub] Manage IoT device module twin configuration. (#11480)
* Manage IoT device module twin configuration * Record test session
1 parent 77b01bd commit ef6da90

File tree

15 files changed

+1508
-273
lines changed

15 files changed

+1508
-273
lines changed

src/IotHub/IotHub.Test/ScenarioTests/IotHubDPModuleTests.ps1

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,34 @@ function Test-AzureRmIotHubModuleLifecycle
6161
Assert-True { $newModule2.DeviceId -eq $device1 }
6262
Assert-True { $newModule2.Authentication.Type -eq 'SelfSigned' }
6363

64+
# Get module twin
65+
$module1twin = Get-AzIotHubModuleTwin -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -ModuleId $module1
66+
Assert-True { $module1twin.DeviceId -eq $device1}
67+
Assert-True { $module1twin.ModuleId -eq $module1}
68+
69+
# Partial update module twin
70+
$tags1 = @{}
71+
$tags1.Add('Test1', '1')
72+
$updatedmodule1twin1 = Update-AzIotHubModuleTwin -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -ModuleId $module1 -tag $tags1 -Partial
73+
Assert-True { $updatedmodule1twin1.DeviceId -eq $device1}
74+
Assert-True { $updatedmodule1twin1.ModuleId -eq $module1}
75+
Assert-True { $updatedmodule1twin1.tags.Count -eq 1}
76+
77+
$tags2 = @{}
78+
$tags2.Add('Test2', '2')
79+
$updatedmodule1twin2 = Update-AzIotHubModuleTwin -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -ModuleId $module1 -tag $tags2 -Partial
80+
Assert-True { $updatedmodule1twin2.DeviceId -eq $device1}
81+
Assert-True { $updatedmodule1twin2.ModuleId -eq $module1}
82+
Assert-True { $updatedmodule1twin2.tags.Count -eq 2}
83+
84+
# Update module twin
85+
$tags3 = @{}
86+
$tags3.Add('Test3', '3')
87+
$updatedmodule1twin3 = Update-AzIotHubModuleTwin -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1 -ModuleId $module1 -tag $tags3
88+
Assert-True { $updatedmodule1twin3.DeviceId -eq $device1}
89+
Assert-True { $updatedmodule1twin3.ModuleId -eq $module1}
90+
Assert-True { $updatedmodule1twin3.tags.Count -eq 1}
91+
6492
# Get all modules
6593
$modules = Get-AzIotHubModule -ResourceGroupName $ResourceGroupName -IotHubName $IotHubName -DeviceId $device1
6694
Assert-True { $modules.Count -eq 2}

src/IotHub/IotHub.Test/SessionRecords/Microsoft.Azure.Commands.IotHub.Test.ScenarioTests.IotHubDPModuleTests/TestAzureIotHubModuleLifecycle.json

Lines changed: 717 additions & 270 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
@@ -101,7 +101,8 @@ CmdletsToExport = 'Add-AzIotHubKey', 'Get-AzIotHubEventHubConsumerGroup',
101101
'Set-AzIotHubDeviceParent', 'Add-AzIotHubDeviceChildren',
102102
'Remove-AzIotHubDeviceChildren', 'Get-AzIotHubDeviceChildren',
103103
'Get-AzIotHubDistributedTracing', 'Set-AzIotHubDistributedTracing',
104-
'Get-AzIotHubDeviceTwin', 'Update-AzIotHubDeviceTwin', 'Invoke-AzIotHubDeviceMethod'
104+
'Get-AzIotHubDeviceTwin', 'Update-AzIotHubDeviceTwin', 'Invoke-AzIotHubDeviceMethod',
105+
'Get-AzIotHubModuleTwin', 'Update-AzIotHubModuleTwin'
105106
# Variables to export from this module
106107
# VariablesToExport = @()
107108

src/IotHub/IotHub/ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
- `Get-AzIotHubDeviceTwin`
2323
- `Update-AzIotHubDeviceTwin`
2424
* Added cmdlet to invoke direct method on a device in an Iot Hub.
25+
* Manage IoT device module twin configuration, New cmdlets are:
26+
- `Get-AzIotHubModuleTwin`
27+
- `Update-AzIotHubModuleTwin`
2528

2629
## Version 2.3.0
2730
* Added support to manage distributed settings per-device. New Cmdlets are:

src/IotHub/IotHub/Common/IotHubDataPlaneUtils.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ public static PSDeviceTwin ToPSDeviceTwin(Twin deviceTwin)
7575
return IotHubUtils.ConvertObject<Twin, PSDeviceTwin>(deviceTwin);
7676
}
7777

78+
public static PSModuleTwin ToPSModuleTwin(Twin moduleTwin)
79+
{
80+
return IotHubUtils.ConvertObject<Twin, PSModuleTwin>(moduleTwin);
81+
}
82+
7883
public static void ValidateDeviceTracing(string DeviceId, string Sku, string Location, bool IsEdgeDevice)
7984
{
8085
if (!TracingAllowedForLocation.Any(location => location.Equals(Location, StringComparison.OrdinalIgnoreCase)))

src/IotHub/IotHub/IotHub.format.ps1xml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,5 +987,45 @@
987987
</ListEntries>
988988
</ListControl>
989989
</View>
990+
<View>
991+
<Name>
992+
Microsoft.Azure.Commands.Management.IotHub.Models.PSModuleTwin
993+
</Name>
994+
<ViewSelectedBy>
995+
<TypeName>Microsoft.Azure.Commands.Management.IotHub.Models.PSModuleTwin</TypeName>
996+
</ViewSelectedBy>
997+
<ListControl>
998+
<ListEntries>
999+
<ListEntry>
1000+
<ListItems>
1001+
<ListItem>
1002+
<PropertyName>ModuleId</PropertyName>
1003+
</ListItem>
1004+
<ListItem>
1005+
<PropertyName>DeviceId</PropertyName>
1006+
</ListItem>
1007+
<ListItem>
1008+
<Label>Tags</Label>
1009+
<ScriptBlock>$_.Tags</ScriptBlock>
1010+
</ListItem>
1011+
<ListItem>
1012+
<Label>Desired</Label>
1013+
<ScriptBlock>$_.Properties.Desired</ScriptBlock>
1014+
</ListItem>
1015+
<ListItem>
1016+
<Label>Reported</Label>
1017+
<ScriptBlock>$_.Properties.Reported</ScriptBlock>
1018+
</ListItem>
1019+
<ListItem>
1020+
<PropertyName>ETag</PropertyName>
1021+
</ListItem>
1022+
<ListItem>
1023+
<PropertyName>Version</PropertyName>
1024+
</ListItem>
1025+
</ListItems>
1026+
</ListEntry>
1027+
</ListEntries>
1028+
</ListControl>
1029+
</View>
9901030
</ViewDefinitions>
9911031
</Configuration>

src/IotHub/IotHub/IotHub/DataPlane/Device/UpdateAzIotHubDeviceTwin.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,11 @@ public override void ExecuteCmdlet()
121121

122122
if (this.Partial.IsPresent)
123123
{
124-
this.WriteObject(registryManager.UpdateTwinAsync(this.DeviceId, deviceTwin, deviceTwin.ETag).GetAwaiter().GetResult());
124+
this.WriteObject(IotHubDataPlaneUtils.ToPSDeviceTwin(registryManager.UpdateTwinAsync(this.DeviceId, deviceTwin, deviceTwin.ETag).GetAwaiter().GetResult()));
125125
}
126126
else
127127
{
128-
this.WriteObject(registryManager.ReplaceTwinAsync(this.DeviceId, deviceTwin, deviceTwin.ETag).GetAwaiter().GetResult());
128+
this.WriteObject(IotHubDataPlaneUtils.ToPSDeviceTwin(registryManager.ReplaceTwinAsync(this.DeviceId, deviceTwin, deviceTwin.ETag).GetAwaiter().GetResult()));
129129
}
130130
}
131131
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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.Models
16+
{
17+
/// <summary>
18+
/// Device's Module Twin Representation.
19+
/// </summary>
20+
public class PSModuleTwin : PSDeviceTwin
21+
{
22+
/// <summary>
23+
/// Module ID
24+
/// </summary>
25+
public string ModuleId { get; set; }
26+
}
27+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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 + "IotHubModuleTwin", DefaultParameterSetName = ResourceParameterSet)]
29+
[OutputType(typeof(PSModuleTwin))]
30+
public class GetAzIotHubModuleTwin : 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+
[Parameter(Mandatory = true, HelpMessage = "Target Module Id.")]
61+
[ValidateNotNullOrEmpty]
62+
public string ModuleId { get; set; }
63+
64+
public override void ExecuteCmdlet()
65+
{
66+
IotHubDescription iotHubDescription;
67+
if (ParameterSetName.Equals(InputObjectParameterSet))
68+
{
69+
this.ResourceGroupName = this.InputObject.Resourcegroup;
70+
this.IotHubName = this.InputObject.Name;
71+
iotHubDescription = IotHubUtils.ConvertObject<PSIotHub, IotHubDescription>(this.InputObject);
72+
}
73+
else
74+
{
75+
if (ParameterSetName.Equals(ResourceIdParameterSet))
76+
{
77+
this.ResourceGroupName = IotHubUtils.GetResourceGroupName(this.ResourceId);
78+
this.IotHubName = IotHubUtils.GetIotHubName(this.ResourceId);
79+
}
80+
81+
iotHubDescription = this.IotHubClient.IotHubResource.Get(this.ResourceGroupName, this.IotHubName);
82+
}
83+
84+
IEnumerable<SharedAccessSignatureAuthorizationRule> authPolicies = this.IotHubClient.IotHubResource.ListKeys(this.ResourceGroupName, this.IotHubName);
85+
SharedAccessSignatureAuthorizationRule policy = IotHubUtils.GetPolicy(authPolicies, PSAccessRights.RegistryWrite);
86+
PSIotHubConnectionString psIotHubConnectionString = IotHubUtils.ToPSIotHubConnectionString(policy, iotHubDescription.Properties.HostName);
87+
RegistryManager registryManager = RegistryManager.CreateFromConnectionString(psIotHubConnectionString.PrimaryConnectionString);
88+
89+
Twin moduleTwin = registryManager.GetTwinAsync(this.DeviceId, this.ModuleId).GetAwaiter().GetResult();
90+
91+
if (moduleTwin == null)
92+
{
93+
throw new ArgumentException($"The entered module \"{this.ModuleId}\" doesn't exist.");
94+
}
95+
96+
this.WriteObject(IotHubDataPlaneUtils.ToPSModuleTwin(moduleTwin));
97+
}
98+
}
99+
}
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 + "IotHubModuleTwin", DefaultParameterSetName = ResourceParameterSet, SupportsShouldProcess = true)]
32+
[OutputType(typeof(PSModuleTwin))]
33+
public class UpdateAzIotHubModuleTwin : 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 = true, HelpMessage = "Target Module Id.")]
64+
[ValidateNotNullOrEmpty]
65+
public string ModuleId { get; set; }
66+
67+
[Parameter(Mandatory = false, HelpMessage = "Add or update the tags property in a module twin.")]
68+
[ValidateNotNullOrEmpty]
69+
public Hashtable Tag { get; set; }
70+
71+
[Parameter(Mandatory = false, HelpMessage = "Add or update the desired property in a module 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 module twin.")]
76+
public SwitchParameter Partial { get; set; }
77+
78+
public override void ExecuteCmdlet()
79+
{
80+
if (ShouldProcess(this.IotHubName, Properties.Resources.UpdateIotHubModuleTwin))
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 moduleTwin = registryManager.GetTwinAsync(this.DeviceId, this.ModuleId).GetAwaiter().GetResult();
106+
107+
if (moduleTwin == null)
108+
{
109+
throw new ArgumentException($"The entered module \"{this.ModuleId}\" doesn't exist.");
110+
}
111+
112+
if (this.IsParameterBound(c => c.Tag))
113+
{
114+
moduleTwin.Tags = new TwinCollection(JsonConvert.SerializeObject(this.Tag));
115+
}
116+
117+
if (this.IsParameterBound(c => c.Desired))
118+
{
119+
moduleTwin.Properties.Desired = new TwinCollection(JsonConvert.SerializeObject(this.Desired));
120+
}
121+
122+
if (this.Partial.IsPresent)
123+
{
124+
this.WriteObject(IotHubDataPlaneUtils.ToPSModuleTwin(registryManager.UpdateTwinAsync(this.DeviceId, this.ModuleId, moduleTwin, moduleTwin.ETag).GetAwaiter().GetResult()));
125+
}
126+
else
127+
{
128+
this.WriteObject(IotHubDataPlaneUtils.ToPSModuleTwin(registryManager.ReplaceTwinAsync(this.DeviceId, this.ModuleId, moduleTwin, moduleTwin.ETag).GetAwaiter().GetResult()));
129+
}
130+
}
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)