Skip to content

[Az.Migrate] To Azure Local - additional validations #28183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 29, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 6 additions & 1 deletion src/Migrate/Migrate.Autorest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ directive:
- from: Microsoft.OffAzure/stable/2020-01-01/migrate.json
where:
verb: Get
subject: ^HyperV(Cluster|Host|Job|OperationsStatus)$
subject: ^HyperV(Job|OperationsStatus)$
remove: true
- from: Microsoft.OffAzure/stable/2020-01-01/migrate.json
where:
Expand Down Expand Up @@ -477,6 +477,11 @@ directive:
verb: Get$
subject: ^VCenter$
hide: true
- from: Microsoft.OffAzure/stable/2020-01-01/migrate.json
where:
verb: Get$
subject: ^HyperV(Cluster|Host)$
hide: true
- where:
verb: New$|Update$
variant: ^(Update|Create)(?!.*?Expanded)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function CheckStorageModuleDependency {
}
}

function GetHCIClusterARGQuery {
function GetARGQueryForArcResourceBridge {
[Microsoft.Azure.PowerShell.Cmdlets.Migrate.DoNotExportAttribute()]
param(
[Parameter(Mandatory)]
Expand Down Expand Up @@ -282,25 +282,37 @@ function ValidateReplication {
throw $VmReplicationValidationMessages.VmPoweredOff
}

# Hyper-V scenario checks
if ($MigrationType -eq $AzLocalInstanceTypes.HyperVToAzLocal) {
# Hyper-V VMs with 'otherguestfamily' OS type and missing OS name could also mean Hyper-V Integration Services are not running
if ([string]::IsNullOrEmpty($Machine.OperatingSystemDetailOSType) -or
($Machine.OperatingSystemDetailOSType -eq $OsType.OtherGuestFamily -and [string]::IsNullOrEmpty($Machine.GuestOSDetailOsname))) {
throw $VmReplicationValidationMessages.OsTypeNotFound
($Machine.OperatingSystemDetailOSType -eq $OsTypes.OtherGuestFamily -and [string]::IsNullOrEmpty($Machine.GuestOSDetailOsname)))
{
throw $VmReplicationValidationMessages.HyperVIntegrationServicesNotRunning
}

if ($Machine.ClusterId -and $Machine.HighAvailability -eq $HighAvailability.NO) {
# Hyper-V VMs should be highly available
if (![string]::IsNullOrEmpty($Machine.ClusterId) -and $Machine.HighAvailability -eq $HighAvailability.NO) {
throw $VmReplicationValidationMessages.VmNotHighlyAvailable
}
}

# VMware scenario checks
if ($MigrationType -eq $AzLocalInstanceTypes.VMwareToAzLocal) {
# Once VMware tools are installed, OS type should be available.
# VMware tools should be running to support static ip migration
if ($Machine.VMwareToolsStatus -eq $VMwareToolsStatus.NotRunning) {
throw $VmReplicationValidationMessages.VmWareToolsNotRunning
Write-Warning $VmReplicationValidationMessages.VmWareToolsNotRunning
}

if ($Machine.VMwareToolsStatus -eq $VMwareToolsStatus.NotInstalled) {
throw $VmReplicationValidationMessages.VmWareToolsNotInstalled
Write-Warning $VmReplicationValidationMessages.VmWareToolsNotInstalled
}
}

# Only OS type of windowsguest and linuxguest are supported for Hyper-V and VMware scenarios
if ($Machine.OperatingSystemDetailOSType -ne $OsTypes.WindowsGuest -and
$Machine.OperatingSystemDetailOSType -ne $OsTypes.LinuxGuest)
{
Write-Warning $VmReplicationValidationMessages.OsTypeNotSupported
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ $VMwareToolsStatus = @{
NotInstalled = "NotInstalled";
}

$OsType = @{
$OsTypes = @{
LinuxGuest = "linuxguest";
WindowsGuest = "windowsguest";
OtherGuestFamily = "otherguestfamily";
Expand All @@ -111,8 +111,14 @@ $VmReplicationValidationMessage = "Replication could not be initiated. Please en
$VmReplicationValidationMessages = @{
VmPoweredOff = "The VM is currently powered off. $VmReplicationValidationMessage";
AlreadyInReplication = "The VM is already in replication. $VmReplicationValidationMessage";
VmWareToolsNotInstalled = "VMware tools not installed on VM. $VmReplicationValidationMessage";
VmWareToolsNotRunning = "VMware tools not running on VM. $VmReplicationValidationMessage";
VmNotHighlyAvailable = "VM not highly available. $VmReplicationValidationMessage";
OsTypeNotFound = "Hyper-V Integration Services not running on VM. $VmReplicationValidationMessage";
HyperVIntegrationServicesNotRunning = "Hyper-V Integration Services are not running on VM. $VmReplicationValidationMessage";
VmWareToolsNotInstalled = "VMware Tools are not installed on the VM. To preserve static IPs during migration, install VMware Tools and wait up to 30 minutes for the system to detect the changes.";
VmWareToolsNotRunning = "VMware Tools are not running on the VM. To preserve static IPs during migration, ensure VMware Tools are running and wait up to 30 minutes for the system to detect the changes.";
OsTypeNotSupported = "The VM OS type could not be identified. For custom Windows or Linux builds, run: `Set-AzMigrateLocalServerReplication -TargetObjectID <ProtectedItemId> -OsType <OsType>` to specify the OS type before migration.";
}

$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."
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ function Initialize-AzMigrateLocalReplicationInfrastructure {

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

CheckResourcesModuleDependency
CheckStorageModuleDependency
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ function New-AzMigrateLocalServerReplication {

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

CheckResourceGraphModuleDependency
CheckResourcesModuleDependency
Expand Down Expand Up @@ -200,6 +200,8 @@ function New-AzMigrateLocalServerReplication {

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

# Get Hyper-V machine
$machine = InvokeAzMigrateGetCommandWithRetries `
-CommandName 'Az.Migrate.Internal\Get-AzMigrateHyperVMachine' `
-Parameters @{
Expand All @@ -209,16 +211,68 @@ function New-AzMigrateLocalServerReplication {
} `
-ErrorMessage "Machine '$MachineName' not found in resource group '$ResourceGroupName' and site '$SiteName'."

# Get Hyper-V site
$siteObject = InvokeAzMigrateGetCommandWithRetries `
-CommandName 'Az.Migrate.Internal\Get-AzMigrateHyperVSite' `
-Parameters @{
'ResourceGroupName' = $ResourceGroupName;
'SiteName' = $SiteName;
} `
-ErrorMessage "Machine site '$SiteName' with Type '$SiteType' not found."

# Get RunAsAccount
if (![string]::IsNullOrEmpty($machine.HostId))
{
# machine is on a single Hyper-V host
$hostIdArray = $machine.HostId.Split("/")
if ($hostIdArray.Length -lt 11) {
throw "Invalid Hyper-V Host ARM ID '$($machine.HostId)'"
}

$hostResourceGroupName = $hostIdArray[4]
$hostSiteName = $hostIdArray[8]
$hostName = $hostIdArray[10]

$hyperVHost = InvokeAzMigrateGetCommandWithRetries `
-CommandName 'Az.Migrate.Internal\Get-AzMigrateHyperVHost' `
-Parameters @{
'ResourceGroupName' = $hostResourceGroupName;
'SiteName' = $hostSiteName;
'HostName' = $hostName;
} `
-ErrorMessage "Hyper-V host '$hostName' not found in resource group '$hostResourceGroupName' and site '$hostSiteName'."

$runAsAccountId = $hyperVHost.RunAsAccountId
}
elseif(![string]::IsNullOrEmpty($machine.ClusterId))
{
# machine is on a Hyper-V cluster
$clusterIdArray = $machine.ClusterId.Split("/")
if ($clusterIdArray.Length -lt 11) {
throw "Invalid Hyper-V Cluster ARM ID '$($machine.ClusterId)'"
}

$clusterResourceGroupName = $clusterIdArray[4]
$clusterSiteName = $clusterIdArray[8]
$clusterName = $clusterIdArray[10]

$hyperVCluster = InvokeAzMigrateGetCommandWithRetries `
-CommandName 'Az.Migrate.Internal\Get-AzMigrateHyperVCluster' `
-Parameters @{
'ResourceGroupName' = $clusterResourceGroupName;
'SiteName' = $clusterSiteName;
'ClusterName' = $clusterName;
} `
-ErrorMessage "Hyper-V cluster '$clusterName' not found in resource group '$clusterResourceGroupName' and site '$clusterSiteName'."

$runAsAccountId = $hyperVCluster.RunAsAccountId
}
}
else {
elseif ($SiteType -eq $SiteTypes.VMwareSites)
{
$instanceType = $AzLocalInstanceTypes.VMwareToAzLocal

# Get VMware machine
$machine = InvokeAzMigrateGetCommandWithRetries `
-CommandName 'Az.Migrate.Internal\Get-AzMigrateMachine' `
-Parameters @{
Expand All @@ -228,13 +282,47 @@ function New-AzMigrateLocalServerReplication {
} `
-ErrorMessage "Machine '$MachineName' not found in resource group '$ResourceGroupName' and site '$SiteName'."

# Get VMware site
$siteObject = InvokeAzMigrateGetCommandWithRetries `
-CommandName 'Az.Migrate\Get-AzMigrateSite' `
-Parameters @{
'ResourceGroupName' = $ResourceGroupName;
'SiteName' = $SiteName;
} `
-ErrorMessage "Machine site '$SiteName' with Type '$SiteType' not found."

# Get RunAsAccount
if (![string]::IsNullOrEmpty($machine.VCenterId))
{
# machine is on a single vCenter
$vCenterIdArray = $machine.VCenterId.Split("/")
if ($vCenterIdArray.Length -lt 11) {
throw "Invalid VMware vCenter ARM ID '$($machine.VCenterId)'"
}

$vCenterResourceGroupName = $vCenterIdArray[4]
$vCenterSiteName = $vCenterIdArray[8]
$vCenterName = $vCenterIdArray[10]

$vmwareVCenter = InvokeAzMigrateGetCommandWithRetries `
-CommandName 'Az.Migrate.Internal\Get-AzMigrateVCenter' `
-Parameters @{
'ResourceGroupName' = $vCenterResourceGroupName;
'SiteName' = $vCenterSiteName;
'Name' = $vCenterName;
} `
-ErrorMessage "VMware vCenter '$vCenterName' not found in resource group '$vCenterResourceGroupName' and site '$vCenterSiteName'."

$runAsAccountId = $vmwareVCenter.RunAsAccountId
}
}
else
{
throw "Unsupported site type '$SiteType'. Only Hyper-V and VMware sites are supported."
}

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

# Validate the VM
Expand Down Expand Up @@ -266,6 +354,9 @@ function New-AzMigrateLocalServerReplication {
"Name" = $replicationVaultName
} `
-ErrorMessage "No Replication Vault '$replicationVaultName' found in Resource Group '$ResourceGroupName'. Please verify your Azure Migrate project setup."
if ($replicationVault.Property.ProvisioningState -ne [ProvisioningState]::Succeeded) {
throw "The Replication Vault '$replicationVaultName' is not in a valid state. The provisioning state is '$($replicationVault.Property.ProvisioningState)'. Please verify your Azure Migrate project setup."
}

# Access Discovery Service
$discoverySolutionName = "Servers-Discovery-ServerDiscovery"
Expand Down Expand Up @@ -420,38 +511,17 @@ function New-AzMigrateLocalServerReplication {
throw "The replication extension '$replicationExtensionName' is not in a valid state. The provisioning state is '$($replicationExtension.Property.ProvisioningState)'. Re-run the Initialize-AzMigrateLocalReplicationInfrastructure command."
}

# Get Target cluster
# Get ARC Resource Bridge info
$targetClusterId = $targetFabric.Property.CustomProperty.Cluster.ResourceName
$targetClusterIdArray = $targetClusterId.Split("/")
$targetSubscription = $targetClusterIdArray[2]
$hciClusterArgQuery = GetHCIClusterARGQuery -HCIClusterID $targetClusterId
$targetCluster = Az.ResourceGraph\Search-AzGraph -Query $hciClusterArgQuery -Subscription $targetSubscription
if ($null -eq $targetCluster) {
throw "Validate target cluster with id '$targetClusterId' exists. Check ARC resource bridge is running on this cluster."
$arbArgQuery = GetARGQueryForArcResourceBridge -HCIClusterID $targetClusterId
$arbArgResult = Az.ResourceGraph\Search-AzGraph -Query $arbArgQuery -Subscription $targetSubscription
if ($null -eq $arbArgResult) {
throw "$($ArcResourceBridgeValidationMessages.NoClusters). Validate target cluster with id '$targetClusterId' exists."
}

# Get source appliance RunAsAccount
if ($SiteType -eq $SiteTypes.HyperVSites) {
$runAsAccounts = InvokeAzMigrateGetCommandWithRetries `
-CommandName 'Az.Migrate.Internal\Get-AzMigrateHyperVRunAsAccount' `
-Parameters @{
ResourceGroupName = $ResourceGroupName;
SiteName = $SiteName;
} `
-ErrorMessage "No run as account found for site '$SiteName'."

$runAsAccount = $runAsAccounts | Where-Object { $_.CredentialType -eq $RunAsAccountCredentialTypes.HyperVFabric }
}
elseif ($SiteType -eq $SiteTypes.VMwareSites) {
$runAsAccounts = InvokeAzMigrateGetCommandWithRetries `
-CommandName 'Az.Migrate\Get-AzMigrateRunAsAccount' `
-Parameters @{
ResourceGroupName = $ResourceGroupName;
SiteName = $SiteName;
} `
-ErrorMessage "No run as account found for site '$SiteName'."

$runAsAccount = $runAsAccounts | Where-Object { $_.CredentialType -eq $RunAsAccountCredentialTypes.VMwareFabric }
elseif ($arbArgResult.statusOfTheBridge -ne "Running") {
throw "$($ArcResourceBridgeValidationMessages.NotRunning). Make sure the Arc Resource Bridge is online before retrying."
}

# Validate TargetVMName
Expand Down Expand Up @@ -480,12 +550,12 @@ function New-AzMigrateLocalServerReplication {
}

$customProperties.InstanceType = $instanceType
$customProperties.CustomLocationRegion = $targetCluster.CustomLocationRegion
$customProperties.CustomLocationRegion = $arbArgResult.CustomLocationRegion
$customProperties.FabricDiscoveryMachineId = $machine.Id
$customProperties.RunAsAccountId = $runAsAccount.Id
$customProperties.RunAsAccountId = $runAsAccountId
$customProperties.SourceFabricAgentName = $sourceDra.Name
$customProperties.StorageContainerId = $TargetStoragePathId
$customProperties.TargetArcClusterCustomLocationId = $targetCluster.CustomLocation
$customProperties.TargetArcClusterCustomLocationId = $arbArgResult.CustomLocation
$customProperties.TargetFabricAgentName = $targetDra.Name
$customProperties.TargetHciClusterId = $targetClusterId
$customProperties.TargetResourceGroupId = $TargetResourceGroupId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ function Set-AzMigrateLocalServerReplication {
# Specifies the nics on the source server to be included for replication.
${NicToInclude},

[Parameter()]
[ValidateSet("WindowsGuest" , "LinuxGuest")]
[ArgumentCompleter( { "WindowsGuest" , "LinuxGuest" })]
[Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Path')]
[System.String]
# Specifies the OS type of the VM, either WindowsGuest or LinuxGuest.
${OsType},

[Parameter()]
[Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Path')]
[Microsoft.Azure.PowerShell.Cmdlets.Migrate.Runtime.DefaultInfo(Script = '(Get-AzContext).Subscription.Id')]
Expand Down Expand Up @@ -121,7 +129,7 @@ function Set-AzMigrateLocalServerReplication {

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

CheckResourcesModuleDependency

Expand All @@ -133,13 +141,15 @@ function Set-AzMigrateLocalServerReplication {
if ($HasIsDynamicMemoryEnabled) {
$isDynamicRamEnabled = [System.Convert]::ToBoolean($IsDynamicMemoryEnabled)
}
$HasOsType = $PSBoundParameters.ContainsKey('OsType')

$null = $PSBoundParameters.Remove('TargetVMCPUCore')
$null = $PSBoundParameters.Remove('IsDynamicMemoryEnabled')
$null = $PSBoundParameters.Remove('DynamicMemoryConfig')
$null = $PSBoundParameters.Remove('TargetVMRam')
$null = $PSBoundParameters.Remove('NicToInclude')
$null = $PSBoundParameters.Remove('TargetObjectID')
$null = $PSBoundParameters.Remove('OsType')
$null = $PSBoundParameters.Remove('WhatIf')
$null = $PSBoundParameters.Remove('Confirm')

Expand Down Expand Up @@ -301,6 +311,11 @@ function Set-AzMigrateLocalServerReplication {
}
}

# Update OS type
if ($HasOsType) {
$customPropertiesUpdate.OsType = $OsType
}

$protectedItemPropertiesUpdate = [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Models.Api20240901.ProtectedItemModelPropertiesUpdate]::new()
$protectedItemPropertiesUpdate.CustomProperty = $customPropertiesUpdate

Expand Down
Loading