Skip to content

Instantly share code, notes, and snippets.

@blakedrumm
Last active May 1, 2025 18:47
Show Gist options
  • Select an option

  • Save blakedrumm/ac877428cf10f887e8262ada0efc94eb to your computer and use it in GitHub Desktop.

Select an option

Save blakedrumm/ac877428cf10f887e8262ada0efc94eb to your computer and use it in GitHub Desktop.
This PowerShell script is designed to migrate runbooks, variables, schedules, modules, certificates, connections, credentials, webhooks, private endpoints, and their tags across tenants. All PS 5 and PS 7 runbooks will be imported as PS 5 runtime; you can change it via the runtime environment later.
<#
.SYNOPSIS
This PowerShell script migrates runbooks, variables, schedules, modules, certificates, connections, credentials, webhooks, private endpoints, and their tags across tenants.
.DESCRIPTION
This PowerShell script is designed to migrate runbooks, variables, schedules, modules, certificates, connections, credentials, webhooks, private endpoints, and their tags across tenants. All PS 5 and PS 7 runbooks will be imported as PS 5 runtime; you can change it via the runtime environment later.
.PARAMETER oldSubscriptionId
Required. Source subscription of the Azure Automation account.
.PARAMETER newSubscriptionId
Required. Target subscription of the Azure Automation account.
.PARAMETER oldRGName
Required. The name of the source resource group of the Azure Automation account.
.PARAMETER newRGName
Required. The name of the target resource group of the Azure Automation account.
.PARAMETER oldAAName
Required. The name of the Azure Automation account under the source subscription.
.PARAMETER newAAName
Required. The name of the Azure Automation account under the target subscription.
.PARAMETER newAALocation
Required. The location for the new Azure Automation account if it needs to be created. (eastus, eastus2, westus, westus2, centralus, northcentralus, southcentralus, westeurope, northeurope, southeastasia, eastasia, australiaeast, australiasoutheast, japaneast, japanwest)
.PARAMETER TempFolder
Required. The temporary folder to store exported runbooks.
.PARAMETER oldTenantId
Required. The tenant ID of the source subscription.
.PARAMETER newTenantId
Required. The tenant ID of the target subscription.
.PARAMETER OnlyEnabledSchedules
Optional. A switch parameter that, if specified, will only migrate enabled schedules.
.NOTES
Author: Blake Drumm (blakedrumm@microsoft.com)
Original Author: Nina Li
Last Edited: August 8th, 2024
EXAMPLE:
.\Migrate-AutomationAccounts.ps1 -oldSubscriptionId <source sub id> -newSubscriptionId <target sub id> -oldRGName <source resource group name> -oldAAName <source AA name> -newRGName <target resource group name> -newAAName <target AA name> -newAALocation <target location> -TempFolder <temp folder path> -oldTenantId <source tenant id> -newTenantId <target tenant id> -OnlyEnabledSchedules
.\Migrate-AutomationAccounts.ps1 -oldSubscriptionId "c9f2b7a1-4821-4f8d-a9e4-6272b0a4f8e6" -newSubscriptionId "f0a1d2c3-95b4-46e9-bf37-7a1d2c3e4b5f" -oldRGName "Old-ResourceGroup" -oldAAName "OldAutomationAccount" -newRGName "New-ResourceGroup" -newAAName "NewAutomationAccount" -newAALocation "eastus" -TempFolder "C:\Temp\AutomationMigration" -oldTenantId "8b7d3a4e-5f6a-4b8c-b9d0-12345678abcd" -newTenantId "1f2e3d4c-5678-90ab-cdef-112233445566" -OnlyEnabledSchedules
--------------------------------------------------------------------------------
Copyright (c) Microsoft Corporation. MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#>
[CmdletBinding()]
param (
[string]$oldSubscriptionId,
[string]$newSubscriptionId,
[string]$oldRGName,
[string]$newRGName,
[string]$oldAAName,
[string]$newAAName,
[string]$newAALocation,
[string]$TempFolder,
[string]$oldTenantId,
[string]$newTenantId,
[switch]$OnlyEnabledSchedules
)
$ErrorActionPreference = "Stop"
$script:varList = @()
$script:schedules = @()
$script:moduleList = @()
$script:certificateList = @()
$script:connectionList = @()
$script:credentialList = @()
$script:webhookList = @()
$script:privateEndpointList = @()
function Set-ExecutionPolicyIfNeeded
{
try
{
$execPolicy = Get-ExecutionPolicy -Scope Process
if ($execPolicy -ne 'Unrestricted' -and $execPolicy -ne 'Bypass')
{
Write-Host "Current execution policy is $execPolicy, setting to Unrestricted temporarily."
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Unrestricted -Force
}
}
catch
{
Write-Error "Failed to set execution policy: $_"
exit 1
}
}
function Login-AzAccountWithSubscription
{
param (
[Parameter(Mandatory = $true)]
[string]$SubscriptionId,
[Parameter(Mandatory = $true)]
[ValidateSet("old", "new")]
[string]$SubscriptionType,
[Parameter(Mandatory = $true)]
[string]$TenantId
)
try
{
Write-Verbose -Verbose "Logging into Azure with $SubscriptionType subscription ID $SubscriptionId..."
$null = Connect-AzAccount -SubscriptionId $SubscriptionId -TenantId $TenantId -ErrorAction Stop
$null = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop
Write-Verbose -Verbose "Set Az Context to $SubscriptionId..."
}
catch
{
Write-Error "Failed to login to Azure with subscription ID $SubscriptionId`: $_"
exit 1
}
}
function Export-Runbooks
{
Write-Verbose "Starting export of runbooks from $oldAAName..."
try
{
Write-Verbose "Retrieving runbooks from $oldAAName in resource group $oldRGName..."
$runbooks = Get-AzAutomationRunbook -AutomationAccountName $oldAAName -ResourceGroupName $oldRGName
Write-Verbose "Successfully retrieved runbooks."
}
catch
{
Write-Error "Failed to get runbooks from $oldAAName`: $_"
exit 1
}
$runbookList = @()
foreach ($runbook in $runbooks)
{
$runbookName = $runbook.Name
$runbookType = $runbook.RunbookType
$runbookTags = $runbook.Tags
$runbookState = $runbook.State
Write-Verbose "Processing runbook: $runbookName"
Write-Verbose "Runbook Type: $runbookType"
Write-Verbose "Runbook Tags: $runbookTags"
switch ($runbookType)
{
"Graph" { $runbookExtension = ".graphrunbook" }
"GraphPowerShell" { $runbookExtension = ".graphrunbook" }
"GraphPowerShellWorkflow" { $runbookExtension = ".graphrunbook" }
"PowerShell" { $runbookExtension = ".ps1" }
"PowerShell72" { $runbookExtension = ".ps1" }
"Python2" { $runbookExtension = ".py" }
"Python3" { $runbookExtension = ".py" }
"PowerShellWorkflow" { $runbookExtension = ".ps1" }
default { $runbookExtension = ".ps1" } # Default to PowerShell script
}
Write-Verbose "Runbook extension: $runbookExtension"
$runbookPath = Join-Path -Path $TempFolder -ChildPath "$runbookName$runbookExtension"
Write-Verbose "Runbook will be exported to: $runbookPath"
try
{
Write-Verbose "Exporting runbook $runbookName..."
$null = Export-AzAutomationRunbook -ResourceGroupName $oldRGName -AutomationAccountName $oldAAName -Name $runbookName -OutputFolder $TempFolder -Force -ErrorAction Stop
Write-Verbose "Successfully exported runbook $runbookName."
}
catch
{
Write-Error "Failed to export runbook $runbookName`: $_"
continue
}
if ($runbookExtension -eq ".py")
{
try
{
Write-Verbose "Renaming Python file to .py for $runbookName..."
Move-Item -Path "$TempFolder\$runbookName.ps1" -Destination "$TempFolder\$runbookName.py" -Force
Write-Verbose "Successfully exported runbook $runbookName."
}
catch
{
Write-Error "Failed to rename runbook script $runbookName`: $_"
continue
}
}
try
{
Write-Verbose "Reading content of runbook $runbookName from $runbookPath..."
$runbookContent = Get-Content -Path $runbookPath -Raw
Write-Verbose "Successfully read content of runbook $runbookName."
}
catch
{
Write-Error "Failed to read content of runbook $runbookName from $runbookPath`: $_"
continue
}
$runbookList += [PSCustomObject]@{
Name = $runbookName
State = $runbookState
Type = $runbookType
Content = $runbookContent
Tags = $runbookTags
Path = $runbookPath
}
}
Write-Verbose "Completed export of runbooks from $oldAAName."
return $runbookList
}
function Import-Runbooks
{
param (
[Parameter(Mandatory = $true)]
[array]$Runbooks
)
Write-Verbose "Starting import of runbooks to $newAAName..."
foreach ($runbook in $Runbooks)
{
$runbookName = [System.IO.Path]::GetFileNameWithoutExtension($runbook.Name)
$runbookState = $runbook.State
$runbookType = $runbook.Type
$runbookContent = $runbook.Content
$runbookTags = $runbook.Tags
$runbookPath = $runbook.Path
Write-Verbose "Processing runbook: $runbookName"
Write-Verbose "Runbook state: $runbookState"
Write-Verbose "Runbook Type: $runbookType"
Write-Verbose "Runbook Path: $runbookPath"
Write-Verbose "Runbook Tags: $runbookTags"
if (-not $runbookType)
{
Write-Error "Runbook $runbookName has a null or empty Type property. Skipping import."
continue
}
switch ($runbookType)
{
"GraphPowerShell" { $runbookType = 'GraphicalPowerShell' }
"GraphPowerShellWorkflow" { $runbookType = 'GraphicalPowerShellWorkflow' }
}
try
{
Write-Verbose "Checking if runbook $runbookName already exists in $newAAName..."
$existingRunbook = Get-AzAutomationRunbook -ResourceGroupName $newRGName -AutomationAccountName $newAAName -Name $runbookName -ErrorAction SilentlyContinue
if ($existingRunbook)
{
# Append old Automation Account name to the runbook name to avoid conflicts
$runbookName = "${runbookName}_$oldAAName"
Write-Verbose "Runbook with the same name exists. New runbook name: $runbookName"
}
Write-Verbose "Importing runbook $runbookName..."
if ($runbookState -eq "Published")
{
Write-Verbose "Publishing runbook $runbookName"
Import-AzAutomationRunbook -ResourceGroupName $newRGName -AutomationAccountName $newAAName -Name $runbookName -Type $runbookType -Path $runbookPath -Tags $runbookTags -Published
}
else
{
Write-Verbose "Runbook $runbookName in $runbookState state.."
Import-AzAutomationRunbook -ResourceGroupName $newRGName -AutomationAccountName $newAAName -Name $runbookName -Type $runbookType -Path $runbookPath -Tags $runbookTags
}
Write-Verbose "Runbook $runbookName imported successfully."
}
catch
{
Write-Error "Failed to import runbook $runbookName`: $_"
}
}
Write-Verbose "Completed import of runbooks to $newAAName."
}
function Export-Variables
{
Write-Verbose "Exporting variables from $oldAAName..."
try
{
$variables = Get-AzAutomationVariable -AutomationAccountName $oldAAName -ResourceGroupName $oldRGName
foreach ($variable in $variables)
{
$script:varList += [PSCustomObject]@{
Name = $variable.Name
Value = $variable.Value
Encrypted = $variable.Encrypted
Description = $variable.Description
}
}
}
catch
{
Write-Error "Failed to export variables from $oldAAName`: $_"
exit 1
}
}
function Import-Variables
{
Write-Verbose "Importing variables to $newAAName..."
foreach ($var in $script:varList)
{
try
{
$existingVariable = Get-AzAutomationVariable -ResourceGroupName $newRGName -AutomationAccountName $newAAName -Name $var.Name -ErrorAction SilentlyContinue
if (-not $existingVariable)
{
New-AzAutomationVariable -AutomationAccountName $newAAName -Name $var.Name -Encrypted $var.Encrypted -Value $var.Value -Description $var.Description -ResourceGroupName $newRGName
}
else
{
Write-Verbose "Variable $($var.Name) already exists in $newAAName. Skipping import."
}
}
catch
{
Write-Error "Failed to import variable $($var.Name): $_"
}
}
}
function Export-Schedules
{
param
(
$OnlyEnabledSchedules
)
Write-Verbose "Exporting schedules from $oldAAName..."
try
{
$script:schedules = Get-AzAutomationSchedule -AutomationAccountName $oldAAName -ResourceGroupName $oldRGName
if ($OnlyEnabledSchedules)
{
$script:schedules = $script:schedules | Where-Object { $_.IsEnabled -eq $true }
Write-Verbose "Filtered to only include enabled schedules."
}
return $script:schedules
}
catch
{
Write-Error "Failed to export schedules from $oldAAName`: $_"
exit 1
}
}
function Import-Schedules
{
Write-Verbose "Importing schedules to $newAAName..."
foreach ($schedule in $script:schedules)
{
try
{
$existingSchedule = Get-AzAutomationSchedule -ResourceGroupName $newRGName -AutomationAccountName $newAAName -Name $schedule.Name -ErrorAction SilentlyContinue
if (-not $existingSchedule)
{
Write-Verbose "Schedule $($schedule.Name) does not exist in $newAAName. Creating it now..."
switch ($schedule.Frequency)
{
"Minute" {
New-AzAutomationSchedule -AutomationAccountName $newAAName -Name $schedule.Name -Description $schedule.Description -StartTime $schedule.StartTime -ExpiryTime $schedule.ExpiryTime -MinuteInterval $schedule.Interval -TimeZone $schedule.TimeZone -ResourceGroupName $newRGName
}
"Hour" {
New-AzAutomationSchedule -AutomationAccountName $newAAName -Name $schedule.Name -Description $schedule.Description -StartTime $schedule.StartTime -ExpiryTime $schedule.ExpiryTime -HourInterval $schedule.Interval -TimeZone $schedule.TimeZone -ResourceGroupName $newRGName
}
"Day" {
New-AzAutomationSchedule -AutomationAccountName $newAAName -Name $schedule.Name -Description $schedule.Description -StartTime $schedule.StartTime -ExpiryTime $schedule.ExpiryTime -DayInterval $schedule.Interval -TimeZone $schedule.TimeZone -ResourceGroupName $newRGName
}
"Week" {
New-AzAutomationSchedule -AutomationAccountName $newAAName -Name $schedule.Name -Description $schedule.Description -StartTime $schedule.StartTime -ExpiryTime $schedule.ExpiryTime -WeekInterval $schedule.Interval -TimeZone $schedule.TimeZone -ResourceGroupName $newRGName
}
"Month" {
New-AzAutomationSchedule -AutomationAccountName $newAAName -Name $schedule.Name -Description $schedule.Description -StartTime $schedule.StartTime -ExpiryTime $schedule.ExpiryTime -MonthInterval $schedule.Interval -TimeZone $schedule.TimeZone -ResourceGroupName $newRGName
}
default {
Write-Error "Unsupported frequency type: $($schedule.Frequency)."
}
}
Write-Verbose "Schedule $($schedule.Name) created successfully."
}
else
{
Write-Verbose "Schedule $($schedule.Name) already exists in $newAAName. Skipping import."
}
}
catch
{
Write-Error "Failed to import schedule $($schedule.Name): $_"
}
}
Write-Verbose "Completed importing schedules to $newAAName."
}
function Export-Modules
{
$script:moduleList = @() # Properly initialize as an empty array
Write-Verbose "Exporting custom modules from $oldAAName..."
try
{
$modules = Get-AzAutomationModule -AutomationAccountName $oldAAName -ResourceGroupName $oldRGName | Where-Object { $_.IsGlobal -eq $false }
foreach ($module in $modules)
{
# Convert size to a human-readable format
$sizeInBytes = $module.SizeInBytes
if ($sizeInBytes -ge 1GB)
{
$size = "{0:N2} GB" -f ($sizeInBytes / 1GB)
}
elseif ($sizeInBytes -ge 1MB)
{
$size = "{0:N2} MB" -f ($sizeInBytes / 1MB)
}
elseif ($sizeInBytes -ge 1KB)
{
$size = "{0:N2} KB" -f ($sizeInBytes / 1KB)
}
else
{
$size = "{0:N2} Bytes" -f $sizeInBytes
}
$script:moduleList += [PSCustomObject]@{
Name = $module.Name
Version = $module.Version
Custom = $true
Size = $size
SizeInBytes = $sizeInBytes
CreationTime = $module.CreationTime
LastModifiedTime = $module.LastModifiedTime
ProvisioningState = $module.ProvisioningState
}
}
}
catch
{
Write-Error "Failed to export modules from $oldAAName`: $_"
exit 1
}
}
function Get-ModuleDetails
{
[CmdletBinding()]
param (
# The name of a package to find
[Parameter(Mandatory = $true)]
$Name,
# The repository api URL -- like https://www.powershellgallery.com/api/v2/ or https://www.nuget.org/api/v2/
$PackageSourceUrl = 'https://www.powershellgallery.com/api/v2/',
#If specified takes precedence over version
[switch]$Latest,
[version]$Version
)
if ($Version)
{
$CheckVersion = $true
}
else
{
$CheckVersion = $false
}
switch ($true)
{
$Latest {
Write-Verbose "Searching for latest [$Name] module"
$URI = "${PackageSourceUrl}Packages?`$filter=Id eq '$Name' and IsLatestVersion"
}
$CheckVersion {
Write-Verbose "Searching for version [$Version] of [$Name]"
$URI = "${PackageSourceUrl}Packages?`$filter=Id eq '$Name' and Version eq '$Version'"
}
default {
Write-Verbose "Searching for all versions of [$Name] module"
$URI = "${PackageSourceUrl}Packages?`$filter=Id eq '$Name'"
}
}
Invoke-RestMethod $URI |
Select-Object @{ n = 'Name'; ex = { $_.title.('#text') } },
@{ n = 'Author'; ex = { $_.author.name } },
@{ n = 'Version'; ex = { $_.properties.NormalizedVersion } },
@{ n = 'Url'; ex = { $_.Content.src } },
@{ n = 'Description'; ex = { $_.properties.Description } },
@{ n = 'Properties'; ex = { $_.properties } }
}
function Import-Modules
{
Write-Verbose "Importing modules to $newAAName..."
foreach ($module in $script:moduleList)
{
try
{
$existingModule = Get-AzAutomationModule -ResourceGroupName $newRGName -AutomationAccountName $newAAName -Name $module.Name -ErrorAction SilentlyContinue
if (-not $existingModule)
{
$PSGalleryDetails = Get-ModuleDetails -Name $module.Name -Version $module.Version
New-AzAutomationModule -AutomationAccountName $newAAName -Name $module.Name -ResourceGroupName $newRGName -ContentLinkUri $PSGalleryDetails.Url
}
else
{
Write-Verbose "Module $($module.Name) already exists in $newAAName. Skipping import."
}
}
catch
{
Write-Error "Failed to import module $($module.Name): $_"
}
}
}
function Export-Certificates
{
Write-Verbose "Exporting certificates from $oldAAName..."
try
{
$certificates = Get-AzAutomationCertificate -AutomationAccountName $oldAAName -ResourceGroupName $oldRGName
foreach ($certificate in $certificates)
{
$script:certificateList += [PSCustomObject]@{
Name = $certificate.Name
Thumbprint = $certificate.Thumbprint
IsExportable = $certificate.IsExportable
ExpiryTime = $certificate.ExpiryTime
Description = $certificate.Description
# Assuming you store the certificate file path and password somewhere secure for re-import
Path = $certificate.Path
Password = $certificate.Password
}
}
}
catch
{
Write-Error "Failed to export certificates from $oldAAName`: $_"
exit 1
}
}
function Import-Certificates
{
Write-Verbose "Importing certificates to $newAAName..."
foreach ($certificate in $script:certificateList)
{
try
{
$existingCertificate = Get-AzAutomationCertificate -ResourceGroupName $newRGName -AutomationAccountName $newAAName -Name $certificate.Name -ErrorAction SilentlyContinue
if (-not $existingCertificate)
{
# Validate and convert password
if (-not $certificate.Password)
{
Write-Warning "Password for certificate $($certificate.Name) is null. Skipping import."
continue
}
$password = ConvertTo-SecureString -String $certificate.Password -AsPlainText -Force
# Validate path
if (-not $certificate.Path)
{
Write-Warning "Path for certificate $($certificate.Name) is null. Skipping import."
continue
}
New-AzAutomationCertificate -AutomationAccountName $newAAName -Name $certificate.Name -Path $certificate.Path -Password $password -Exportable:$certificate.IsExportable -Description $certificate.Description -ResourceGroupName $newRGName
Write-Verbose "Certificate $($certificate.Name) imported successfully."
}
else
{
Write-Verbose "Certificate $($certificate.Name) already exists in $newAAName. Skipping import."
}
}
catch
{
Write-Error "Failed to import certificate $($certificate.Name): $_"
}
}
}
function Export-Connections
{
Write-Verbose "Exporting connections from $oldAAName..."
try
{
$connections = Get-AzAutomationConnection -AutomationAccountName $oldAAName -ResourceGroupName $oldRGName
foreach ($connection in $connections)
{
$script:connectionList += [PSCustomObject]@{
Name = $connection.Name
ConnectionTypeName = $connection.ConnectionTypeName
FieldDefinitionValues = $connection.FieldDefinitionValues
}
}
}
catch
{
Write-Error "Failed to export connections from $oldAAName`: $_"
exit 1
}
}
function Import-Connections
{
Write-Verbose "Importing connections to $newAAName..."
foreach ($connection in $script:connectionList)
{
try
{
$existingConnection = Get-AzAutomationConnection -ResourceGroupName $newRGName -AutomationAccountName $newAAName -Name $connection.Name -ErrorAction SilentlyContinue
if (-not $existingConnection)
{
if ($connection.FieldDefinitionValues)
{
New-AzAutomationConnection -AutomationAccountName $newAAName -Name $connection.Name -ConnectionTypeName $connection.ConnectionTypeName -FieldDefinitionValues $connection.FieldDefinitionValues -ResourceGroupName $newRGName
}
else
{
New-AzAutomationConnection -AutomationAccountName $newAAName -Name $connection.Name -ConnectionTypeName $connection.ConnectionTypeName -ResourceGroupName $newRGName
}
}
else
{
Write-Verbose "Connection $($connection.Name) already exists in $newAAName. Skipping import."
}
}
catch
{
Write-Error "Failed to import connection $($connection.Name): $_"
}
}
}
function Export-Credentials
{
Write-Verbose "Exporting credentials from $oldAAName..."
try
{
$credentials = Get-AzAutomationCredential -AutomationAccountName $oldAAName -ResourceGroupName $oldRGName
foreach ($credential in $credentials)
{
$script:credentialList += [PSCustomObject]@{
Name = $credential.Name
UserName = $credential.UserName
Description = $credential.Description
Tags = $credential.Tags
}
}
}
catch
{
Write-Error "Failed to export credentials from $oldAAName`: $_"
exit 1
}
}
function Import-Credentials
{
Write-Verbose "Importing credentials to $newAAName..."
foreach ($credential in $script:credentialList)
{
try
{
$existingCredential = Get-AzAutomationCredential -ResourceGroupName $newRGName -AutomationAccountName $newAAName -Name $credential.Name -ErrorAction SilentlyContinue
if (-not $existingCredential)
{
New-AzAutomationCredential -AutomationAccountName $newAAName -Name $credential.Name -Value (Get-Credential $credential.UserName) -Description $credential.Description -ResourceGroupName $newRGName
}
else
{
Write-Verbose "Credential $($credential.Name) already exists in $newAAName. Skipping import."
}
}
catch
{
Write-Error "Failed to import credential $($credential.Name): $_"
}
}
}
function Export-Webhooks
{
Write-Verbose "Exporting webhooks from $oldAAName..."
try
{
$webhooks = Get-AzAutomationWebhook -AutomationAccountName $oldAAName -ResourceGroupName $oldRGName
foreach ($webhook in $webhooks)
{
$script:webhookList += [PSCustomObject]@{
Name = $webhook.Name
Uri = $webhook.Uri
IsEnabled = $webhook.IsEnabled
ExpiryTime = $webhook.ExpiryTime
RunbookName = $webhook.RunbookName
Parameters = $webhook.Parameters
Tags = $webhook.Tags
}
}
}
catch
{
Write-Error "Failed to export webhooks from $oldAAName`: $_"
exit 1
}
}
function Import-Webhooks
{
Write-Verbose "Importing webhooks to $newAAName..."
foreach ($webhook in $script:webhookList)
{
try
{
$existingWebhook = Get-AzAutomationWebhook -ResourceGroupName $newRGName -AutomationAccountName $newAAName -Name $webhook.Name -ErrorAction SilentlyContinue
if (-not $existingWebhook)
{
New-AzAutomationWebhook -AutomationAccountName $newAAName -Name $webhook.Name -Uri $webhook.Uri -IsEnabled $webhook.IsEnabled -ExpiryTime $webhook.ExpiryTime -RunbookName $webhook.RunbookName -Parameters $webhook.Parameters -ResourceGroupName $newRGName
Set-AzAutomationWebhook -AutomationAccountName $newAAName -Name $webhook.Name -Tags $webhook.Tags -ResourceGroupName $newRGName
}
else
{
Write-Verbose "Webhook $($webhook.Name) already exists in $newAAName. Skipping import."
}
}
catch
{
Write-Error "Failed to import webhook $($webhook.Name): $_"
}
}
}
function Export-PrivateEndpoints
{
Write-Verbose "Exporting private endpoints from $oldAAName..."
try
{
$privateEndpoints = Get-AzPrivateEndpointConnection -ResourceGroupName $oldRGName -Name $oldAAName -PrivateLinkResourceType 'Microsoft.Automation/automationAccounts'
foreach ($privateEndpoint in $privateEndpoints)
{
$script:privateEndpointList += [PSCustomObject]@{
Name = $privateEndpoint.Name
PrivateLinkServiceConnectionState = $privateEndpoint.PrivateLinkServiceConnectionState
GroupIds = $privateEndpoint.GroupIds
RequestMessage = $privateEndpoint.RequestMessage
Tags = $privateEndpoint.Tags
}
}
}
catch
{
Write-Error "Failed to export private endpoints from $oldAAName`: $_"
exit 1
}
}
function Import-PrivateEndpoints
{
Write-Verbose "Importing private endpoints to $newAAName..."
foreach ($privateEndpoint in $script:privateEndpointList)
{
try
{
$existingPrivateEndpoint = Get-AzPrivateEndpointConnection -ResourceGroupName $newRGName -Name $newAAName -PrivateLinkResourceType 'Microsoft.Automation/automationAccounts' -ErrorAction SilentlyContinue
if (-not $existingPrivateEndpoint)
{
New-AzPrivateEndpoint -ResourceGroupName $newRGName -Name $privateEndpoint.Name -Tag $privateEndpoint.Tags -PrivateLinkServiceConnectionState $privateEndpoint.PrivateLinkServiceConnectionState -GroupIds $privateEndpoint.GroupIds -RequestMessage $privateEndpoint.RequestMessage -PrivateLinkResourceType 'Microsoft.Automation/automationAccounts'
}
else
{
Write-Verbose "Private endpoint $($privateEndpoint.Name) already exists in $newAAName. Skipping import."
}
}
catch
{
Write-Error "Failed to import private endpoint $($privateEndpoint.Name): $_"
}
}
}
function Create-AutomationAccount
{
param (
[Parameter(Mandatory = $true)]
[string]$AccountName,
[Parameter(Mandatory = $true)]
[string]$ResourceGroupName,
[Parameter(Mandatory = $true)]
[string]$Location
)
Write-Verbose "Creating Azure Automation account $AccountName in resource group $ResourceGroupName at location $Location..."
try
{
New-AzAutomationAccount -ResourceGroupName $ResourceGroupName -Name $AccountName -Location $Location
}
catch
{
Write-Error "Failed to create automation account $AccountName in resource group $ResourceGroupName`: $_"
exit 1
}
}
function Main
{
Set-ExecutionPolicyIfNeeded
Write-Host "Please verify if you have sufficient permissions to access both old subscription $oldSubscriptionId and new subscription $newSubscriptionId"
Login-AzAccountWithSubscription -SubscriptionId $oldSubscriptionId -SubscriptionType old -TenantId $oldTenantId
try
{
$oldAAinfo = Get-AzAutomationAccount -ResourceGroupName $oldRGName -Name $oldAAName
if (-not $oldAAinfo)
{
Write-Error "Automation account $oldAAName under resource group $oldRGName does not exist in subscription $oldSubscriptionId. Please check the name and restart the script!"
return
}
}
catch
{
Write-Error "Failed to get automation account $oldAAName in subscription $oldSubscriptionId`: $_"
exit 1
}
$runbooks = Export-Runbooks
Export-Variables
Export-Schedules -OnlyEnabledSchedules:$OnlyEnabledSchedules
Export-Modules
#Export-Certificates
#Export-Connections
Export-Credentials
Export-Webhooks
#Export-PrivateEndpoints
function Get-ErrorDetails
{
param (
[Parameter(Mandatory = $true)]
[System.Management.Automation.ErrorRecord]$ErrorRecord
)
return @{
ErrorMessage = $ErrorRecord.Exception.Message
ErrorType = $ErrorRecord.Exception.GetType().FullName
ScriptStackTrace = $ErrorRecord.ScriptStackTrace
}
}
Login-AzAccountWithSubscription -SubscriptionId $newSubscriptionId -SubscriptionType new -TenantId $newTenantId
try
{
$newAAinfo = Get-AzAutomationAccount -ResourceGroupName $newRGName -Name $newAAName
if (-not $newAAinfo)
{
Write-Host "Automation account $newAAName does not exist in resource group $newRGName. Creating it now..."
Create-AutomationAccount -AccountName $newAAName -ResourceGroupName $newRGName -Location $newAALocation
}
}
catch
{
$errorDetails = Get-ErrorDetails -ErrorRecord $_
if ($errorDetails.ErrorType -eq "Microsoft.Azure.Commands.Automation.Common.ResourceNotFoundException")
{
Write-Warning "The Automation account $newAAName was not found in resource group $newRGName. Attempting to create it."
try
{
Create-AutomationAccount -AccountName $newAAName -ResourceGroupName $newRGName -Location $newAALocation
}
catch
{
Write-Error "Failed to create automation account $newAAName in resource group $newRGName`: $_"
exit 1
}
}
else
{
Write-Error "Failed to get automation account $newAAName in subscription $newSubscriptionId`: $_"
exit 1
}
}
Import-Variables
Import-Runbooks -Runbooks $runbooks
Import-Schedules
Import-Modules
#Import-Certificates
#Import-Connections
Import-Credentials
Import-Webhooks
#Import-PrivateEndpoints
Write-Host "All done! Please check your automation account $newAAName under subscription $newSubscriptionId to verify the imported runbooks, variables, schedules, modules, certificates, connections, credentials, webhooks, and private endpoints."
}
Main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment