Zbiorcze importowanie i eksportowanie tożsamości urządzeń usługi IoT Hub
Każde centrum IoT ma rejestr tożsamości, którego można użyć do tworzenia zasobów urządzeń w usłudze. Rejestr tożsamości umożliwia również kontrolowanie dostępu do punktów końcowych przeznaczonych dla urządzeń. W tym artykule opisano sposób zbiorczego importowania i eksportowania tożsamości urządzeń do i z rejestru tożsamości przy użyciu przykładu ImportExportDeviceSample dołączonego do zestawu MICROSOFT Azure IoT SDK dla platformy .NET. Aby uzyskać więcej informacji na temat sposobu korzystania z tej funkcji podczas migrowania centrum IoT Hub do innego regionu, zobacz Jak ręcznie migrować centrum Azure IoT Hub przy użyciu szablonu usługi Azure Resource Manager.
Uwaga
Usługa IoT Hub niedawno dodała obsługę sieci wirtualnej w ograniczonej liczbie regionów. Ta funkcja zabezpiecza operacje importowania i eksportowania oraz eliminuje konieczność przekazywania kluczy do uwierzytelniania. Obecnie obsługa sieci wirtualnej jest dostępna tylko w następujących regionach: WestUS2, EastUS i SouthCentralUS. Aby dowiedzieć się więcej o obsłudze sieci wirtualnej i wywołaniach interfejsu API w celu jej zaimplementowania, zobacz Obsługa usługi IoT Hub dla sieci wirtualnych.
Operacje importowania i eksportowania odbywają się w kontekście zadań , które umożliwiają wykonywanie operacji usługi zbiorczej w centrum IoT.
Klasa RegistryManager w zestawie SDK zawiera metody ExportDevicesAsync i ImportDevicesAsync korzystające z platformy Job. Te metody umożliwiają eksportowanie, importowanie i synchronizowanie całego rejestru tożsamości centrum IoT Hub.
W tym artykule omówiono używanie klasy RegistryManager i systemu zadań do przeprowadzania importowania zbiorczego i eksportowania urządzeń do i z rejestru tożsamości centrum IoT. Możesz również użyć usługi Azure IoT Hub Device Provisioning, aby umożliwić bezobsługową aprowizację just in time w co najmniej jednym centrum IoT. Aby dowiedzieć się więcej, zobacz dokumentację usługi aprowizacji.
Uwaga
Niektóre fragmenty kodu w tym artykule znajdują się w przykładzie usługi ImportExportDevicesSample dostarczonej z zestawem MICROSOFT Azure IoT SDK dla platformy .NET. Przykład znajduje się w /iothub/service/samples/how to guides/ImportExportDevicesSample
folderze zestawu SDK i, gdzie określono, fragmenty kodu są dołączone do pliku dla tego przykładu ImportExportDevicesSample.cs
zestawu SDK. Aby uzyskać więcej informacji na temat przykładu ImportExportDevicesSample i innych przykładów usług zawartych w zestawie SDK usługi Azure IoT for.NET, zobacz Przykłady usługi Azure IoT Hub dla języka C#.
Co to są zadania?
Operacje rejestru tożsamości używają systemu zadań podczas operacji:
Ma potencjalnie długi czas wykonywania w porównaniu z standardowymi operacjami czasu wykonywania.
Zwraca użytkownikowi dużą ilość danych.
Zamiast jednego wywołania interfejsu API oczekuje lub blokuje wynik operacji, operacja asynchronicznie tworzy zadanie dla tego centrum IoT. Następnie operacja natychmiast zwraca obiekt JobProperties .
Ważne
Ten artykuł zawiera kroki nawiązywania połączenia z usługą przy użyciu sygnatury dostępu współdzielonego. Ta metoda uwierzytelniania jest wygodna do testowania i oceny, ale uwierzytelnianie w usłudze przy użyciu identyfikatora Entra firmy Microsoft lub tożsamości zarządzanych jest bardziej bezpieczne. Aby dowiedzieć się więcej, zobacz Security best practices Cloud security (Najlepsze rozwiązania > dotyczące zabezpieczeń w chmurze).
Poniższy fragment kodu w języku C# pokazuje, jak utworzyć zadanie eksportu:
// Call an export job on the IoT hub to retrieve all devices
JobProperties exportJob = await
registryManager.ExportDevicesAsync(containerSasUri, false);
Uwaga
Aby użyć klasy RegistryManager w kodzie języka C#, dodaj pakiet NuGet Microsoft.Azure.Devices do projektu. Klasa RegistryManager znajduje się w przestrzeni nazw Microsoft.Azure.Devices .
Klasa RegistryManager umożliwia wykonywanie zapytań o stan zadania przy użyciu zwróconych metadanych JobProperties. Aby utworzyć wystąpienie klasy RegistryManager , użyj metody CreateFromConnectionString .
RegistryManager registryManager =
RegistryManager.CreateFromConnectionString("{your IoT Hub connection string}");
Aby znaleźć parametry połączenia dla centrum IoT Hub, w witrynie Azure Portal:
Przejdź do centrum IoT Hub.
Wybierz pozycję Zasady dostępu współdzielonego.
Wybierz zasady, biorąc pod uwagę potrzebne uprawnienia.
Skopiuj parametry połączenia dla tych zasad.
Poniższy fragment kodu języka C# z metody WaitForJobAsync w przykładzie zestawu SDK pokazuje, jak sondować co pięć sekund, aby sprawdzić, czy zadanie zostało zakończone:
// Wait until job is finished
while (true)
{
job = await registryManager.GetJobAsync(job.JobId);
if (job.Status == JobStatus.Completed
|| job.Status == JobStatus.Failed
|| job.Status == JobStatus.Cancelled)
{
// Job has finished executing
break;
}
Console.WriteLine($"\tJob status is {job.Status}...");
await Task.Delay(TimeSpan.FromSeconds(5));
}
Uwaga
Jeśli twoje konto magazynu ma konfiguracje zapory ograniczające łączność usługi IoT Hub, rozważ użycie wyjątku zaufanej firmy Microsoft (dostępnego w wybranych regionach dla centrów IoT z tożsamością usługi zarządzanej).
Limity zadań importowania/eksportowania urządzeń
Tylko jedno aktywne zadanie importowania lub eksportowania urządzeń jest dozwolone naraz dla wszystkich warstw usługi IoT Hub. Usługa IoT Hub ma również limity liczby operacji zadań. Aby dowiedzieć się więcej, zobacz te,at Limity przydziału i ograniczanie przepustowości w usłudze IoT Hub.
Eksportowanie urządzeń
Użyj metody ExportDevicesAsync, aby wyeksportować cały rejestr tożsamości centrum IoT do kontenera obiektów blob usługi Azure Storage przy użyciu sygnatury dostępu współdzielonego (SAS). Ta metoda umożliwia tworzenie niezawodnych kopii zapasowych informacji o urządzeniu w kontrolanym kontenerze obiektów blob.
Metoda ExportDevicesAsync wymaga dwóch parametrów:
Ciąg zawierający identyfikator URI kontenera obiektów blob. Ten identyfikator URI musi zawierać token SAS, który udziela dostępu do zapisu w kontenerze. Zadanie tworzy blokowy obiekt blob w tym kontenerze do przechowywania serializowanego eksportu danych urządzenia. Token SAS musi zawierać następujące uprawnienia:
SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Delete
Wartość logiczna wskazująca, czy chcesz wykluczyć klucze uwierzytelniania z danych eksportu. Jeśli wartość false, klucze uwierzytelniania są uwzględniane w danych wyjściowych eksportu. W przeciwnym razie klucze są eksportowane jako null.
Poniższy fragment kodu w języku C# pokazuje, jak zainicjować zadanie eksportu zawierające klucze uwierzytelniania urządzenia w danych eksportu, a następnie sondować pod kątem ukończenia:
// Call an export job on the IoT Hub to retrieve all devices
JobProperties exportJob =
await registryManager.ExportDevicesAsync(containerSasUri, false);
// Wait until job is finished
while(true)
{
exportJob = await registryManager.GetJobAsync(exportJob.JobId);
if (exportJob.Status == JobStatus.Completed ||
exportJob.Status == JobStatus.Failed ||
exportJob.Status == JobStatus.Cancelled)
{
// Job has finished executing
break;
}
await Task.Delay(TimeSpan.FromSeconds(5));
}
Podobny kod można znaleźć w metodzie ExportDevicesAsync z przykładu zestawu SDK. Zadanie przechowuje dane wyjściowe w podanym kontenerze obiektów blob jako blokowy obiekt blob o nazwie devices.txt. Dane wyjściowe składają się z danych z serializowanego urządzenia w formacie JSON z jednym urządzeniem na wiersz.
W poniższym przykładzie przedstawiono dane wyjściowe:
{"id":"Device1","eTag":"MA==","status":"enabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device2","eTag":"MA==","status":"enabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device3","eTag":"MA==","status":"disabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device4","eTag":"MA==","status":"disabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device5","eTag":"MA==","status":"enabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
Jeśli urządzenie ma dane bliźniaczej reprezentacji bliźniaczej, dane bliźniaczej reprezentacji są również eksportowane wraz z danymi urządzenia. W poniższym przykładzie pokazano ten format. Wszystkie dane z wiersza "twinETag" do końca to dane reprezentacji bliźniaczej.
{
"id":"export-6d84f075-0",
"eTag":"MQ==",
"status":"enabled",
"authentication":null,
"twinETag":"AAAAAAAAAAI=",
"tags":{
"Location":"LivingRoom"
},
"properties":{
"desired":{
"Thermostat":{
"Temperature":75.1,
"Unit":"F"
},
},
"reported":{}
}
}
Jeśli potrzebujesz dostępu do tych danych w kodzie, możesz wykonać deserializacji tych danych przy użyciu klasy ExportImportDevice . Poniższy fragment kodu języka C# z metody ReadFromBlobAsync w przykładzie zestawu SDK pokazuje, jak odczytywać informacje o urządzeniu, które zostały wcześniej wyeksportowane z funkcji ExportImportDevice do wystąpienia obiektu BlobClient:
private static async Task<List<string>> ReadFromBlobAsync(BlobClient blobClient)
{
// Read the blob file of devices, import each row into a list.
var contents = new List<string>();
using Stream blobStream = await blobClient.OpenReadAsync();
using var streamReader = new StreamReader(blobStream, Encoding.UTF8);
while (streamReader.Peek() != -1)
{
string line = await streamReader.ReadLineAsync();
contents.Add(line);
}
return contents;
}
Importowanie urządzeń
Metoda ImportDevicesAsync w klasie RegistryManager umożliwia wykonywanie operacji importowania zbiorczego i synchronizacji w rejestrze tożsamości centrum IoT. Podobnie jak metoda ExportDevicesAsync, metoda ImportDevicesAsync używa struktury Job.
Należy zachować ostrożność przy użyciu metody ImportDevicesAsync , ponieważ oprócz aprowizacji nowych urządzeń w rejestrze tożsamości może również aktualizować i usuwać istniejące urządzenia.
Ostrzeżenie
Nie można cofnąć operacji importowania. Zawsze utwórz kopię zapasową istniejących danych przy użyciu metody ExportDevicesAsync do innego kontenera obiektów blob przed wprowadzeniem zbiorczych zmian w rejestrze tożsamości.
Metoda ImportDevicesAsync przyjmuje dwa parametry:
Ciąg zawierający identyfikator URI kontenera obiektów blob usługi Azure Storage do użycia jako dane wejściowe zadania. Ten identyfikator URI musi zawierać token SAS, który udziela dostępu do odczytu do kontenera. Ten kontener musi zawierać obiekt blob o nazwie devices.txt , który zawiera serializowane dane urządzenia do zaimportowania do rejestru tożsamości. Dane importu muszą zawierać informacje o urządzeniu w tym samym formacie JSON używanym przez zadanie ExportImportDevice podczas tworzenia devices.txt obiektu blob. Token SAS musi zawierać następujące uprawnienia:
SharedAccessBlobPermissions.Read
Ciąg zawierający identyfikator URI kontenera obiektów blob usługi Azure Storage do użycia jako dane wyjściowe zadania. Zadanie tworzy blokowy obiekt blob w tym kontenerze w celu przechowywania wszelkich informacji o błędach z ukończonego zadania importowania. Token SAS musi zawierać następujące uprawnienia:
SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Delete
Uwaga
Dwa parametry mogą wskazywać ten sam kontener obiektów blob. Oddzielne parametry po prostu umożliwiają większą kontrolę nad danymi, ponieważ kontener wyjściowy wymaga dodatkowych uprawnień.
Poniższy fragment kodu w języku C# pokazuje, jak zainicjować zadanie importowania:
JobProperties importJob =
await registryManager.ImportDevicesAsync(containerSasUri, containerSasUri);
Tej metody można również użyć do zaimportowania danych bliźniaczej reprezentacji urządzenia. Format danych wejściowych jest taki sam jak format pokazany w sekcji ExportDevicesAsync . W ten sposób można ponownie importować wyeksportowane dane.
Zachowanie importowania
Możesz użyć metody ImportDevicesAsync , aby wykonać następujące operacje zbiorcze w rejestrze tożsamości:
- Zbiorcza rejestracja nowych urządzeń
- Zbiorcze usuwanie istniejących urządzeń
- Zmiany stanu zbiorczego (włączanie lub wyłączanie urządzeń)
- Zbiorcze przypisywanie nowych kluczy uwierzytelniania urządzeń
- Zbiorcze automatyczne ponowne generowanie kluczy uwierzytelniania urządzenia
- Zbiorcza aktualizacja danych bliźniaczych reprezentacji
W ramach pojedynczego wywołania ImportDevicesAsync można wykonać dowolną kombinację poprzednich operacji. Możesz na przykład zarejestrować nowe urządzenia i jednocześnie usunąć lub zaktualizować istniejące urządzenia. W przypadku użycia wraz z metodą ExportDevicesAsync można całkowicie zmigrować wszystkie urządzenia z jednego centrum IoT do innego.
Użyj opcjonalnej właściwości importMode w danych serializacji importu dla każdego urządzenia, aby kontrolować proces importowania dla poszczególnych urządzeń. Właściwość importMode ma następujące opcje:
- Utwórz
- CreateOrUpdate (ustawienie domyślne)
- CreateOrUpdateIfMatchETag
- Usuń
- DeleteIfMatchETag
- Update
- UpdateIfMatchETag
- UpdateTwin
- UpdateTwinIfMatchETag
Aby uzyskać szczegółowe informacje o każdej z tych opcji trybu importu, zobacz ImportMode
Rozwiązywanie problemów z zadaniami importowania
Użycie zadania importu do utworzenia urządzeń może zakończyć się niepowodzeniem z powodu problemu z limitem przydziału, gdy zbliża się do limitu liczby urządzeń centrum IoT. Ten błąd może wystąpić nawet wtedy, gdy łączna liczba urządzeń jest nadal niższa niż limit przydziału. Błąd IotHubQuotaExceeded (403002) jest zwracany z następującym komunikatem o błędzie: "Łączna liczba urządzeń w usłudze IotHub przekroczyła przydzielony limit przydziału".
Jeśli wystąpi ten błąd, możesz użyć następującego zapytania, aby zwrócić całkowitą liczbę urządzeń zarejestrowanych w centrum IoT:
SELECT COUNT() as totalNumberOfDevices FROM devices
Aby uzyskać informacje o łącznej liczbie urządzeń, które można zarejestrować w centrum IoT Hub, zobacz Limity usługi IoT Hub.
Jeśli nadal jest dostępny limit przydziału, możesz sprawdzić wyjściowy obiekt blob zadania dla urządzeń, które zakończyły się niepowodzeniem z powodu błędu IotHubQuotaExceeded (403002). Następnie możesz spróbować dodać te urządzenia indywidualnie do centrum IoT. Można na przykład użyć metod AddDeviceAsync lub AddDeviceWithTwinAsync . Nie próbuj dodawać urządzeń przy użyciu innego zadania, ponieważ może wystąpić ten sam błąd.
Przykład importowania urządzeń — zbiorcze aprowizowanie urządzeń
Poniższy fragment kodu języka C# z metody GenerateDevicesAsync w przykładzie zestawu SDK ilustruje sposób generowania wielu tożsamości urządzeń, które:
- Uwzględnij klucze uwierzytelniania.
- Zapisz informacje o urządzeniu w blokowym obiekcie blob.
- Zaimportuj urządzenia do rejestru tożsamości.
private async Task GenerateDevicesAsync(RegistryManager registryManager, int numToAdd)
{
var stopwatch = Stopwatch.StartNew();
Console.WriteLine($"Creating {numToAdd} devices for the source IoT hub.");
int interimProgressCount = 0;
int displayProgressCount = 1000;
int totalProgressCount = 0;
// generate reference for list of new devices we're going to add, will write list to this blob
BlobClient generateDevicesBlob = _blobContainerClient.GetBlobClient(_generateDevicesBlobName);
// define serializedDevices as a generic list<string>
var serializedDevices = new List<string>(numToAdd);
for (int i = 1; i <= numToAdd; i++)
{
// Create device name with this format: Hub_00000000 + a new guid.
// This should be large enough to display the largest number (1 million).
string deviceName = $"Hub_{i:D8}_{Guid.NewGuid()}";
Debug.Print($"Adding device '{deviceName}'");
// Create a new ExportImportDevice.
var deviceToAdd = new ExportImportDevice
{
Id = deviceName,
Status = DeviceStatus.Enabled,
Authentication = new AuthenticationMechanism
{
SymmetricKey = new SymmetricKey
{
PrimaryKey = GenerateKey(32),
SecondaryKey = GenerateKey(32),
}
},
// This indicates that the entry should be added as a new device.
ImportMode = ImportMode.Create,
};
// Add device to the list as a serialized object.
serializedDevices.Add(JsonConvert.SerializeObject(deviceToAdd));
// Not real progress as you write the new devices, but will at least show *some* progress.
interimProgressCount++;
totalProgressCount++;
if (interimProgressCount >= displayProgressCount)
{
Console.WriteLine($"Added {totalProgressCount}/{numToAdd} devices.");
interimProgressCount = 0;
}
}
// Now have a list of devices to be added, each one has been serialized.
// Write the list to the blob.
var sb = new StringBuilder();
serializedDevices.ForEach(serializedDevice => sb.AppendLine(serializedDevice));
// Write list of serialized objects to the blob.
using Stream stream = await generateDevicesBlob.OpenWriteAsync(overwrite: true);
byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());
for (int i = 0; i < bytes.Length; i += BlobWriteBytes)
{
int length = Math.Min(bytes.Length - i, BlobWriteBytes);
await stream.WriteAsync(bytes.AsMemory(i, length));
}
await stream.FlushAsync();
Console.WriteLine("Running a registry manager job to add the devices.");
// Should now have a file with all the new devices in it as serialized objects in blob storage.
// generatedListBlob has the list of devices to be added as serialized objects.
// Call import using the blob to add the new devices.
// Log information related to the job is written to the same container.
// This normally takes 1 minute per 100 devices (according to the docs).
// First, initiate an import job.
// This reads in the rows from the text file and writes them to IoT Devices.
// If you want to add devices from a file, you can create a file and use this to import it.
// They have to be in the exact right format.
try
{
// The first URI is the container to import from; the file defaults to devices.txt, but may be specified.
// The second URI points to the container to write errors to as a blob.
// This lets you import the devices from any file name. Since we wrote the new
// devices to [devicesToAdd], need to read the list from there as well.
var importGeneratedDevicesJob = JobProperties.CreateForImportJob(
_containerUri,
_containerUri,
_generateDevicesBlobName);
importGeneratedDevicesJob = await registryManager.ImportDevicesAsync(importGeneratedDevicesJob);
await WaitForJobAsync(registryManager, importGeneratedDevicesJob);
}
catch (Exception ex)
{
Console.WriteLine($"Adding devices failed due to {ex.Message}");
}
stopwatch.Stop();
Console.WriteLine($"GenerateDevices, time elapsed = {stopwatch.Elapsed}.");
}
Przykład importowania urządzeń — zbiorcze usuwanie
Poniższy fragment kodu języka C# z metody DeleteFromHubAsync w przykładzie zestawu SDK pokazuje, jak usunąć wszystkie urządzenia z centrum IoT:
private async Task DeleteFromHubAsync(RegistryManager registryManager, bool includeConfigurations)
{
var stopwatch = Stopwatch.StartNew();
Console.WriteLine("Deleting all devices from an IoT hub.");
Console.WriteLine("Exporting a list of devices from IoT hub to blob storage.");
// Read from storage, which contains serialized objects.
// Write each line to the serializedDevices list.
BlobClient devicesBlobClient = _blobContainerClient.GetBlobClient(_destHubDevicesImportBlobName);
Console.WriteLine("Reading the list of devices in from blob storage.");
List<string> serializedDevices = await ReadFromBlobAsync(devicesBlobClient);
// Step 1: Update each device's ImportMode to be Delete
Console.WriteLine("Updating ImportMode to be 'Delete' for each device and writing back to the blob.");
var sb = new StringBuilder();
serializedDevices.ForEach(serializedEntity =>
{
// Deserialize back to an ExportImportDevice and change import mode.
ExportImportDevice device = JsonConvert.DeserializeObject<ExportImportDevice>(serializedEntity);
device.ImportMode = ImportMode.Delete;
// Reserialize the object now that we've updated the property.
sb.AppendLine(JsonConvert.SerializeObject(device));
});
// Step 2: Write the list in memory to the blob.
BlobClient deleteDevicesBlobClient = _blobContainerClient.GetBlobClient(_hubDevicesCleanupBlobName);
await WriteToBlobAsync(deleteDevicesBlobClient, sb.ToString());
// Step 3: Call import using the same blob to delete all devices.
Console.WriteLine("Running a registry manager job to delete the devices from the IoT hub.");
var importJob = JobProperties.CreateForImportJob(
_containerUri,
_containerUri,
_hubDevicesCleanupBlobName);
importJob = await registryManager.ImportDevicesAsync(importJob);
await WaitForJobAsync(registryManager, importJob);
// Step 4: delete configurations
if (includeConfigurations)
{
BlobClient configsBlobClient = _blobContainerClient.GetBlobClient(_srcHubConfigsExportBlobName);
List<string> serializedConfigs = await ReadFromBlobAsync(configsBlobClient);
foreach (string serializedConfig in serializedConfigs)
{
try
{
Configuration config = JsonConvert.DeserializeObject<Configuration>(serializedConfig);
await registryManager.RemoveConfigurationAsync(config.Id);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to deserialize or remove a config.\n\t{serializedConfig}\n\n{ex.Message}");
}
}
}
stopwatch.Stop();
Console.WriteLine($"Deleted IoT hub devices and configs: time elapsed = {stopwatch.Elapsed}");
}
Pobieranie identyfikatora URI sygnatury dostępu współdzielonego kontenera
Poniższy przykładowy kod pokazuje, jak wygenerować identyfikator URI sygnatury dostępu współdzielonego z uprawnieniami do odczytu, zapisu i usuwania dla kontenera obiektów blob:
static string GetContainerSasUri(CloudBlobContainer container)
{
// Set the expiry time and permissions for the container.
// In this case no start time is specified, so the
// shared access signature becomes valid immediately.
var sasConstraints = new SharedAccessBlobPolicy();
sasConstraints.SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24);
sasConstraints.Permissions =
SharedAccessBlobPermissions.Write |
SharedAccessBlobPermissions.Read |
SharedAccessBlobPermissions.Delete;
// Generate the shared access signature on the container,
// setting the constraints directly on the signature.
string sasContainerToken = container.GetSharedAccessSignature(sasConstraints);
// Return the URI string for the container,
// including the SAS token.
return container.Uri + sasContainerToken;
}
Następne kroki
W tym artykule przedstawiono sposób wykonywania operacji zbiorczych względem rejestru tożsamości w centrum IoT. Wiele z tych operacji, w tym sposób przenoszenia urządzeń z jednego centrum do drugiego, jest używanych w sekcji Zarządzanie urządzeniami zarejestrowanymi w centrum IoT w temacie Jak ręcznie migrować centrum Azure IoT Hub przy użyciu szablonu usługi Azure Resource Manager.