Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Migrate/Migrate.Autorest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ input-file:
- $(repo)/specification/recoveryservicessiterecovery/resource-manager/Microsoft.RecoveryServices/stable/2024-01-01/service.json
- $(repo)/specification/recoveryservicesdatareplication/resource-manager/Microsoft.DataReplication/stable/2024-09-01/recoveryservicesdatareplication.json

module-version: 3.0.12
module-version: 3.0.13
title: Migrate
subject-prefix: 'Migrate'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ function Get-AzMigrateLocalServerReplication {

process {
Import-Module $PSScriptRoot\Helper\AzLocalCommonSettings.ps1
Import-Module $PSScriptRoot\Helper\AZLocalCommonHelper.ps1

$hasTargetObjectId = $PSBoundParameters.ContainsKey('TargetObjectID')
$hasDiscoveredMachineId = $PSBoundParameters.ContainsKey('DiscoveredMachineId')
$hasResourceGroupId = $PSBoundParameters.ContainsKey('ResourceGroupID')
$hasProjectId = $PSBoundParameters.ContainsKey('ProjectID')

$parameterSet = $PSCmdlet.ParameterSetName
$null = $PSBoundParameters.Remove('TargetObjectID')
Expand All @@ -143,12 +149,26 @@ function Get-AzMigrateLocalServerReplication {
$null = $PSBoundParameters.Remove('ResourceGroupID')
$null = $PSBoundParameters.Remove('ProjectID')
$null = $PSBoundParameters.Remove('MachineName')

# Validate ARM ID format from inputs
if ($hasTargetObjectId -and !(Test-AzureResourceIdFormat -Data $TargetObjectID -Format $IdFormats.ProtectedItemArmIdTemplate)) {
throw "Invalid -TargetObjectID '$TargetObjectID'. A valid protected item ARM ID should follow the format '$($IdFormats.ProtectedItemArmIdTemplate)'."
}

if ($hasDiscoveredMachineId -and !(Test-AzureResourceIdFormat -Data $DiscoveredMachineId -Format $IdFormats.MachineArmIdTemplate)) {
throw "Invalid -DiscoveredMachineId '$DiscoveredMachineId'. A valid machine ARM ID should follow the format '$($IdFormats.MachineArmIdTemplate)'."
}

if ($hasResourceGroupId -and !(Test-AzureResourceIdFormat -Data $ResourceGroupID -Format $IdFormats.ResourceGroupArmIdTemplate)) {
throw "Invalid -ResourceGroupID '$ResourceGroupID'. A valid resource group ARM ID should follow the format '$($IdFormats.ResourceGroupArmIdTemplate)'."
}

if ($hasProjectId -and !(Test-AzureResourceIdFormat -Data $ProjectID -Format $IdFormats.MigrateProjectArmIdTemplate)) {
throw "Invalid -ProjectID '$ProjectID'. A valid migrate project ARM ID should follow the format '$($IdFormats.MigrateProjectArmIdTemplate)'."
}

if ($parameterSet -eq 'GetBySDSID') {
$machineIdArray = $DiscoveredMachineId.Split("/")
if ($machineIdArray.Length -lt 11) {
throw "Invalid machine ARM ID '$DiscoveredMachineId'"
}
$siteType = $machineIdArray[7]
$siteName = $machineIdArray[8]
$ResourceGroupName = $machineIdArray[4]
Expand Down Expand Up @@ -208,17 +228,8 @@ function Get-AzMigrateLocalServerReplication {
# Retrieve ResourceGroupName, ProjectName if ListByID
if ($parameterSet -eq 'ListByID') {
$resourceGroupIdArray = $ResourceGroupID.Split('/')
if ($resourceGroupIdArray.Length -lt 5) {
throw "Invalid resource group Id '$ResourceGroupID'."
}

$ResourceGroupName = $resourceGroupIdArray[4]

$projectIdArray = $ProjectID.Split('/')
if ($projectIdArray.Length -lt 9) {
throw "Invalid migrate project Id '$ProjectID'."
}

$ProjectName = $projectIdArray[8]
}

Expand Down Expand Up @@ -255,10 +266,6 @@ function Get-AzMigrateLocalServerReplication {
$TargetObjectID = $InputObject.Id
}
$objectIdArray = $TargetObjectID.Split("/")
if ($objectIdArray.Length -lt 11) {
throw "Invalid target object ID '$TargetObjectID'."
}

$ResourceGroupName = $objectIdArray[4]
$VaultName = $objectIdArray[8]
$ProtectedItemName = $objectIdArray[10]
Expand Down
77 changes: 77 additions & 0 deletions src/Migrate/Migrate.Autorest/custom/Helper/AzLocalCommonHelper.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -315,4 +315,81 @@ function ValidateReplication {
{
Write-Warning $VmReplicationValidationMessages.OsTypeNotSupported
}
}

function Test-AzureResourceIdFormat {
[Microsoft.Azure.PowerShell.Cmdlets.Migrate.DoNotExportAttribute()]
param(
[Parameter(Mandatory = $true)]
[string] $Data,

[Parameter(Mandatory = $true)]
[string] $Format
)

try {
if ([string]::IsNullOrWhiteSpace($Data)) {
return $false
}

# Find where format string starts (after first /)
$firstTokenEnd = $Format.IndexOf("/", 1)
if ($firstTokenEnd -eq -1) {
return $false
}

$formatPrefix = $Format.Substring(0, $firstTokenEnd)
$matchIndex = $Data.ToLower().IndexOf($formatPrefix.ToLower())

if ($matchIndex -eq -1) {
return $false
}

$processData = $Data.Substring($matchIndex)
$processFormat = $Format
$tokens = @()

$counter = 0
while ($true) {
$markerPattern = "{$counter}"
$markerStartIndex = $processFormat.IndexOf($markerPattern)

if ($markerStartIndex -eq -1) {
break
}

$markerEndIndex = $processData.IndexOf("/", $markerStartIndex)

if ($markerEndIndex -eq -1) {
$token = $processData.Substring($markerStartIndex)
if ([string]::IsNullOrWhiteSpace($token)) {
return $false
}
$tokens += $token
}
else {
$token = $processData.Substring($markerStartIndex, $markerEndIndex - $markerStartIndex)
if ([string]::IsNullOrWhiteSpace($token)) {
return $false
}
$tokens += $token
$processData = $processData.Substring($markerEndIndex)
$processFormat = $processFormat.Substring($markerStartIndex + $markerPattern.Length)
}

$counter++
}

# Verify format matches with the extracted tokens
$formatWithTokens = $Format
for ($i = 0; $i -lt $tokens.Count; $i++) {
$formatWithTokens = $formatWithTokens -replace "\{$i\}", $tokens[$i]
}

return $Data.ToLower() -like $formatWithTokens.ToLower()
}
catch
{
return $false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,13 @@ $VmReplicationValidationMessages = @{
$ArcResourceBridgeValidationMessages = @{
NotRunning = "Arc Resource Bridge is offline. To continue, bring the Arc Resource Bridge online. Wait a few minutes for the status to update and retry.";
NoClusters = "There are no Azure Local clusters found in the selected resource group."
}

$IdFormats = @{
ResourceGroupArmIdTemplate = "/subscriptions/{0}/resourceGroups/{1}"
MachineArmIdTemplate = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.OffAzure/{2}/{3}/machines/{4}"
StoragePathArmIdTemplate = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.AzureStackHCI/storageContainers/{2}"
LogicalNetworkArmIdTemplate = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.AzureStackHCI/logicalnetworks/{2}"
MigrateProjectArmIdTemplate = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Migrate/MigrateProjects/{2}"
ProtectedItemArmIdTemplate = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.DataReplication/replicationVaults/{2}/protectedItems/{3}"
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ function New-AzMigrateLocalDiskMappingObject {
process {
$isDynamicDisk = [System.Convert]::ToBoolean($IsDynamic)
$osDisk = [System.Convert]::ToBoolean($IsOSDisk)
$hasPhysicalSectorSize = $PSBoundParameters.ContainsKey('PhysicalSectorSize')

if ($Format -eq "VHD" -and $PhysicalSectorSize -ne 512) {
throw "PhysicalSectorSize must be 512 for VHD format."
if ($Format -eq "VHD" -and $hasPhysicalSectorSize -and $PhysicalSectorSize -ne 512) {
throw "PhysicalSectorSize must be 512 for VHD format but $PhysicalSectorSize is given."
}

$DiskObject = [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Models.Api20240901.AzLocalDiskInput]::new(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function New-AzMigrateLocalServerReplication {
[Parameter(Mandatory)]
[Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Path')]
[System.String]
# Specifies the target Resource Group Id where the migrated VM resources will reside.
# Specifies the target resource group ARM ID where the migrated VM resources will reside.
${TargetResourceGroupId},

[Parameter(Mandatory)]
Expand All @@ -98,7 +98,7 @@ function New-AzMigrateLocalServerReplication {
[Parameter(ParameterSetName = 'ByIdDefaultUser', Mandatory)]
[Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Path')]
[System.String]
# Specifies the Operating System disk for the source server to be migrated.
# Specifies the operating system disk for the source server to be migrated.
${OSDiskID},

[Parameter(Mandatory)]
Expand Down Expand Up @@ -181,23 +181,39 @@ function New-AzMigrateLocalServerReplication {
$isDynamicRamEnabled = [System.Convert]::ToBoolean($IsDynamicMemoryEnabled)
}
$HasTargetVMRam = $PSBoundParameters.ContainsKey('TargetVMRam')
$HasTargetVirtualSwitchId = $PSBoundParameters.ContainsKey('TargetVirtualSwitchId')
$HasTargetTestVirtualSwitchId = $PSBoundParameters.ContainsKey('TargetTestVirtualSwitchId')
$parameterSet = $PSCmdlet.ParameterSetName

$MachineIdArray = $MachineId.Split("/")
if ($MachineIdArray.Length -lt 11) {
throw "Invalid machine ARM ID '$MachineId'"

# Validate ARM ID format from inputs
if (!(Test-AzureResourceIdFormat -Data $MachineId -Format $IdFormats.MachineArmIdTemplate)) {
throw "Invalid -MachineId '$MachineId'. A valid machine ARM ID should follow the format '$($IdFormats.MachineArmIdTemplate)'."
}

if (!(Test-AzureResourceIdFormat -Data $TargetStoragePathId -Format $IdFormats.StoragePathArmIdTemplate)) {
throw "Invalid -TargetStoragePathId '$TargetStoragePathId'. A valid storage path ARM ID should follow the format '$($IdFormats.StoragePathArmIdTemplate)'."
}

if (!(Test-AzureResourceIdFormat -Data $TargetResourceGroupId -Format $IdFormats.ResourceGroupArmIdTemplate)) {
throw "Invalid -TargetResourceGroupId '$TargetResourceGroupId'. A valid resource group ARM ID should follow the format '$($IdFormats.ResourceGroupArmIdTemplate)'."
}

if ($HasTargetVirtualSwitchId -and !(Test-AzureResourceIdFormat -Data $TargetVirtualSwitchId -Format $IdFormats.LogicalNetworkArmIdTemplate)) {
throw "Invalid -TargetVirtualSwitchId '$TargetVirtualSwitchId'. A valid logical network ARM ID should follow the format '$($IdFormats.LogicalNetworkArmIdTemplate)'."
}

if ($HasTargetTestVirtualSwitchId -and !(Test-AzureResourceIdFormat -Data $TargetTestVirtualSwitchId -Format $IdFormats.LogicalNetworkArmIdTemplate)) {
throw "Invalid -TargetTestVirtualSwitchId '$TargetTestVirtualSwitchId'. A valid logical network ARM ID should follow the format '$($IdFormats.LogicalNetworkArmIdTemplate)'."
}

# Extract information from $MachineId
$MachineIdArray = $MachineId.Split("/")
$SiteType = $MachineIdArray[7]
$SiteName = $MachineIdArray[8]
$ResourceGroupName = $MachineIdArray[4]
$MachineName = $MachineIdArray[10]

# Get the source site and the discovered machine
if (($SiteType -ne $SiteTypes.HyperVSites) -and ($SiteType -ne $SiteTypes.VMwareSites)) {
throw "Site type is not supported. Site type '$SiteType'. Check MachineId provided."
}

if ($SiteType -eq $SiteTypes.HyperVSites) {
$instanceType = $AzLocalInstanceTypes.HyperVToAzLocal

Expand Down Expand Up @@ -318,11 +334,11 @@ function New-AzMigrateLocalServerReplication {
}
else
{
throw "Unsupported site type '$SiteType'. Only Hyper-V and VMware sites are supported."
throw "Site type of '$SiteType' in -MachineId is not supported. Only '$($SiteTypes.HyperVSites)' and '$($SiteTypes.VMwareSites)' are supported."
}

if ([string]::IsNullOrEmpty($runAsAccountId)) {
throw "Unable to determine RunAsAccount for site '$SiteName' from machine '$MachineName'. Please verify your appliance setup."
throw "Unable to determine RunAsAccount for site '$SiteName' from machine '$MachineName'. Please verify your appliance setup and provided -MachineId."
}

# Validate the VM
Expand Down Expand Up @@ -560,7 +576,6 @@ function New-AzMigrateLocalServerReplication {
$customProperties.TargetHciClusterId = $targetClusterId
$customProperties.TargetResourceGroupId = $TargetResourceGroupId
$customProperties.TargetVMName = $TargetVMName
$customProperties.TargetCpuCore = if ($HasTargetVMCPUCore) { $TargetVMCPUCore } else { $machine.NumberOfProcessorCore }
$customProperties.IsDynamicRam = if ($HasIsDynamicMemoryEnabled) { $isDynamicRamEnabled } else { $isSourceDynamicMemoryEnabled }

# Determine target VM Hyper-V Generation
Expand All @@ -573,11 +588,37 @@ function New-AzMigrateLocalServerReplication {
$customProperties.HyperVGeneration = if ($machine.Firmware -ieq "BIOS") { "1" } else { "2" }
}

# Validate TargetVMCPUCore
if ($HasTargetVMCPUCore)
{
if ($TargetVMCPUCore -lt 1 -or $TargetVMCPUCore -gt 64)
{
throw "Specify -TargetVMCPUCore between 1 and 64."
}
$customProperties.TargetCpuCore = $TargetVMCPUCore
}
else
{
$customProperties.TargetCpuCore = $machine.NumberOfProcessorCore
}

# Validate TargetVMRam
if ($HasTargetVMRam) {
# TargetVMRam needs to be greater than 0
if ($TargetVMRam -le 0) {
throw "Specify a target RAM that is greater than 0"
if ($HasTargetVMRam)
{
if ($customProperties.HyperVGeneration -eq "1") {
# Between 512 MB and 1 TB
if ($TargetVMRam -lt 512 -or $TargetVMRam -gt 1048576)
{
throw "Specify -TargetVMRAM between 512 and 1048576 MB (i.e., 1 TB) for Hyper-V Generation 1 VM."
}
}
else # Hyper-V Generation 2
{
# Between 32 MB and 12 TB
if ($TargetVMRam -lt 32 -or $TargetVMRam -gt 12582912)
{
throw "Specify -TargetVMRAM between 32 and 12582912 MB (i.e., 12 TB) for Hyper-V Generation 2 VM."
}
}

$customProperties.TargetMemoryInMegaByte = $TargetVMRam
Expand All @@ -604,22 +645,12 @@ function New-AzMigrateLocalServerReplication {
if ($null -eq $osDisk) {
throw "No Disk found with InstanceId $OSDiskID from discovered machine disks."
}

$diskName = Split-Path $osDisk.Path -leaf
if (IsReservedOrTrademarked($diskName)) {
throw "The disk name $diskName or part of the name is a trademarked or reserved word."
}
}
elseif ($SiteType -eq $SiteTypes.VMwareSites) {
$osDisk = $machine.Disk | Where-Object { $_.Uuid -eq $OSDiskID }
if ($null -eq $osDisk) {
throw "No Disk found with Uuid $OSDiskID from discovered machine disks."
}

$diskName = Split-Path $osDisk.Path -leaf
if (IsReservedOrTrademarked($diskName)) {
throw "The disk name $diskName or part of the name is a trademarked or reserved word."
}
}

foreach ($sourceDisk in $machine.Disk) {
Expand Down Expand Up @@ -663,14 +694,14 @@ function New-AzMigrateLocalServerReplication {
# Validate DiskToInclude
[PSCustomObject[]]$uniqueDisks = @()
foreach ($disk in $DiskToInclude) {
# VHD is not supported in Gen2 VMs
# Enforce VHDX for Gen2 VMs
if ($customProperties.HyperVGeneration -eq "2" -and $disk.DiskFileFormat -eq "VHD") {
throw "VHD disks are not supported in Hyper-V Generation 2 VMs. Please replace disk with id '$($disk.DiskId)' in -DiskToInclude by re-running New-AzMigrateLocalDiskMappingObject with 'VHDX' as Format."
throw "Please specify 'VHDX' as Format for the disk with id '$($disk.DiskId)' in -DiskToInclude by re-running New-AzMigrateLocalDiskMappingObject."
}

# PhysicalSectorSize must be 512 for VHD format
if ($disk.DiskFileFormat -eq "VHD" -and $disk.DiskPhysicalSectorSize -ne 512) {
throw "PhysicalSectorSize must be 512 for VHD format. Please replace disk with id '$($disk.DiskId)' in -DiskToInclude by re-running New-AzMigrateLocalDiskMappingObject with 512 as PhysicalSectorSize."
# PhysicalSectorSize must be 512 for VHD format if it is set
if ($disk.DiskFileFormat -eq "VHD" -and $null -ne $disk.DiskPhysicalSectorSize -and $disk.DiskPhysicalSectorSize -ne 512) {
throw "Invalid Physical sector size of $($disk.DiskPhysicalSectorSize) is found for VHD format. Please replace disk with id '$($disk.DiskId)' in -DiskToInclude by re-running New-AzMigrateLocalDiskMappingObject with 512 as -PhysicalSectorSize."
}

if ($SiteType -eq $SiteTypes.HyperVSites) {
Expand All @@ -686,11 +717,6 @@ function New-AzMigrateLocalServerReplication {
}
}

$diskName = Split-Path -Path $discoveredDisk.Path -Leaf
if (IsReservedOrTrademarked($diskName)) {
throw "The disk name $diskName or part of the name is a trademarked or reserved word."
}

if ($uniqueDisks.Contains($disk.DiskId)) {
throw "The disk id '$($disk.DiskId)' is already taken."
}
Expand Down
Loading
Loading