Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,15 @@ function Test-ReplicationPrequisites {
throw $VmReplicationValidationMessages.HyperVIntegrationServicesNotRunning
}

# Hyper-V VMs should be highly available
if (![string]::IsNullOrEmpty($Machine.ClusterId) -and $Machine.HighAvailability -eq $HighAvailability.NO) {
throw $VmReplicationValidationMessages.VmNotHighlyAvailable
# Hyper-V VMs on cluster should be highly available
if (![string]::IsNullOrEmpty($Machine.ClusterId)) {
if ($Machine.HighAvailability -eq $HighAvailability.NO) {
throw $VmReplicationValidationMessages.VmNotHighlyAvailable
}
elseif ($Machine.HighAvailability -ne $HighAvailability.YES) {
# Unknown or unexpected value
throw $VmReplicationValidationMessages.VmUnknownHighlyAvailable
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ $PowerStatus = @{
}

$HighAvailability = @{
Unknown = "Unknown";
NO = "No";
YES = "Yes";
}
Expand All @@ -111,7 +112,8 @@ $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";
VmNotHighlyAvailable = "VM not highly available. $VmReplicationValidationMessage";
VmNotHighlyAvailable = "The VM is not highly available. $VmReplicationValidationMessage";
VmUnknownHighlyAvailable = "The VM has unknown high availability status. $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.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,6 @@ function Initialize-AzMigrateLocalReplicationInfrastructure {
Import-Module Az.Resources
Import-Module Az.Storage

$hasCacheStorageAccountId = $PSBoundParameters.ContainsKey('CacheStorageAccountId')

$parameterSetName = $PSCmdlet.ParameterSetName
$null = $PSBoundParameters.Remove('ResourceGroupName')
$null = $PSBoundParameters.Remove('ProjectName')
$null = $PSBoundParameters.Remove('CacheStorageAccountId')
Expand All @@ -143,14 +140,19 @@ function Initialize-AzMigrateLocalReplicationInfrastructure {
$null = $PSBoundParameters.Add('ErrorVariable', 'notPresent')
$null = $PSBoundParameters.Add('ErrorAction', 'SilentlyContinue')

# Get subscription Id
# Validate Azure login
$context = Get-AzContext
if ($null -eq $context -or $null -eq $context.Account) {
throw "Not logged in to Azure. Please run 'Connect-AzAccount' before running this command."
}

# Get subscription Id
if ([string]::IsNullOrEmpty($SubscriptionId)) {
Write-Host "No -SubscriptionId provided. Using the one from Get-AzContext."

$SubscriptionId = $context.Subscription.Id
if ([string]::IsNullOrEmpty($SubscriptionId)) {
throw "Please login to Azure to select a subscription."
throw "No subscription selected. Please run 'Set-AzContext -SubscriptionId <id>' or provide -SubscriptionId."
}
}
Write-Host "*Selected Subscription Id: '$($SubscriptionId)'"
Expand All @@ -165,34 +167,6 @@ function Initialize-AzMigrateLocalReplicationInfrastructure {
}
Write-Host "*Selected Resource Group: '$($ResourceGroupName)'"

# Verify user validity
$userObject = Get-AzADUser -UserPrincipalName $context.Subscription.ExtendedProperties.Account

if (-not $userObject) {
$userObject = Get-AzADUser -Mail $context.Subscription.ExtendedProperties.Account
}

if (-not $userObject) {
$mailNickname = "{0}#EXT#" -f $($context.Account.Id -replace '@', '_')

$userObject = Get-AzADUser |
Where-Object { $_.MailNickname -eq $mailNickname }
}

if (-not $userObject) {
if ($context.Account.Id.StartsWith("MSI@")) {
$hostname = $env:COMPUTERNAME
$userObject = Get-AzADServicePrincipal -DisplayName $hostname
}
else {
$userObject = Get-AzADServicePrincipal -ApplicationID $context.Account.Id
}
}

if (-not $userObject) {
throw 'User Object Id Not Found!'
}

# Get Migrate Project with ResourceGroupName, Name
$null = $PSBoundParameters.Add('ResourceGroupName', $ResourceGroupName)
$null = $PSBoundParameters.Add('Name', $ProjectName)
Expand Down Expand Up @@ -760,7 +734,7 @@ function Initialize-AzMigrateLocalReplicationInfrastructure {
-Location $params.location `
-Kind $params.kind `
-Tags $params.tags `
-AllowBlobPublicAccess $true
-AllowBlobPublicAccess $false

if ($null -ne $cacheStorageAccount -and
$null -ne $cacheStorageAccount.ProvisioningState -and
Expand Down Expand Up @@ -799,6 +773,17 @@ function Initialize-AzMigrateLocalReplicationInfrastructure {
throw "Unexpected error occurs during Cache Storage Account selection process. Please re-run this command or contact support if help needed."
}

# Validate Cache Storage Account SKU tier is Standard (not Premium)
if ($cacheStorageAccount.Sku.Tier -ne "Standard") {
throw "Cache Storage Account '$($cacheStorageAccount.StorageAccountName)' uses an unsupported SKU tier '$($cacheStorageAccount.Sku.Tier)'. Only 'Standard' tier storage accounts are supported. Please provide a Standard tier storage account."
}

# Validate public network access should not be disabled even for private endpoint
if (![string]::IsNullOrEmpty($cacheStorageAccount.PublicNetworkAccess) -and
$cacheStorageAccount.PublicNetworkAccess -eq "Disabled") {
throw "Cache Storage Account '$($cacheStorageAccount.StorageAccountName)' does not allow public network access. Please enable 'Public network access' on the storage account and re-run this command."
}
Comment thread
minhsuanlee marked this conversation as resolved.

$params = @{
contributorRoleDefId = [System.Guid]::parse($RoleDefinitionIds.ContributorId);
storageBlobDataContributorRoleDefId = [System.Guid]::parse($RoleDefinitionIds.StorageBlobDataContributorId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,6 @@ function New-AzMigrateLocalServerReplication {
CheckResourceGraphModuleDependency
CheckResourcesModuleDependency

$HasMachineId = $PSBoundParameters.ContainsKey('MachineId')
$HasTargetStoragePathId = $PSBoundParameters.ContainsKey('TargetStoragePathId')
$HasTargetResourceGroupId = $PSBoundParameters.ContainsKey('TargetResourceGroupId')
$HasTargetVMCPUCore = $PSBoundParameters.ContainsKey('TargetVMCPUCore')
$HasIsDynamicMemoryEnabled = $PSBoundParameters.ContainsKey('IsDynamicMemoryEnabled')
if ($HasIsDynamicMemoryEnabled) {
Expand Down Expand Up @@ -212,22 +209,22 @@ function New-AzMigrateLocalServerReplication {
$null = $PSBoundParameters.Add('ErrorAction', 'SilentlyContinue')

# Validate ARM ID format from inputs
if ($HasMachineId -and !(Test-AzureResourceIdFormat -Data $MachineId -Format $IdFormats.MachineArmIdTemplate))
if (!(Test-AzureResourceIdFormat -Data $MachineId -Format $IdFormats.MachineArmIdTemplate))
{
throw New-InvalidResourceIdProvidedException `
-ResourceId $MachineId `
-ResourceType "DiscoveredMachine" `
-Format $IdFormats.MachineArmIdTemplate
}

if ($HasTargetStoragePathId -and !(Test-AzureResourceIdFormat -Data $TargetStoragePathId -Format $IdFormats.StoragePathArmIdTemplate)) {
if (!(Test-AzureResourceIdFormat -Data $TargetStoragePathId -Format $IdFormats.StoragePathArmIdTemplate)) {
throw New-InvalidResourceIdProvidedException `
-ResourceId $TargetStoragePathId `
-ResourceType "StorageContainer" `
-Format $IdFormats.StoragePathArmIdTemplate
}

if ($HasTargetResourceGroupId -and !(Test-AzureResourceIdFormat -Data $TargetResourceGroupId -Format $IdFormats.ResourceGroupArmIdTemplate)) {
if (!(Test-AzureResourceIdFormat -Data $TargetResourceGroupId -Format $IdFormats.ResourceGroupArmIdTemplate)) {
throw New-InvalidResourceIdProvidedException `
-ResourceId $TargetResourceGroupId `
-ResourceType "ResourceGroup" `
Expand Down Expand Up @@ -651,6 +648,34 @@ function New-AzMigrateLocalServerReplication {
$customProperties.FabricDiscoveryMachineId = $machine.Id
$customProperties.RunAsAccountId = $runAsAccountId
$customProperties.SourceFabricAgentName = $sourceDra.Name

# Validate storage path exists and is in a usable state
$storagePath = Get-AzResource `
-ResourceId $TargetStoragePathId `
-ErrorVariable notPresent `
-ErrorAction SilentlyContinue
if ($null -eq $storagePath) {
throw "Storage path with Id '$TargetStoragePathId' not found. Please provide a valid storage path ARM ID."
}

# Creation must have succeeded for the storage path to be usable
$creationStatus = $storagePath.Properties.status.provisioningStatus.status
if ([string]::IsNullOrEmpty($creationStatus)) {
throw "Storage path '$($storagePath.Name)' creation status is unavailable. Please verify the storage path resource is fully provisioned."
}
if ($creationStatus -ne "Succeeded") {
throw "Storage path '$($storagePath.Name)' has a creation provisioning status of '$creationStatus'. Only storage paths with a successful creation can be used. Please select a different storage path or wait for provisioning to complete."
}

# The latest operation (ProvisioningState) must also be Succeeded
$provisioningState = $storagePath.Properties.provisioningState
if ([string]::IsNullOrEmpty($provisioningState)) {
throw "Storage path '$($storagePath.Name)' provisioning state is unavailable. Please verify the storage path resource is fully provisioned."
}
if ($provisioningState -ne "Succeeded") {
throw "Storage path '$($storagePath.Name)' has a provisioning state of '$provisioningState'. Only storage paths with a 'Succeeded' provisioning state can be used. Please resolve the issue or select a different storage path."
Comment thread
minhsuanlee marked this conversation as resolved.
}

$customProperties.StorageContainerId = $TargetStoragePathId
$customProperties.TargetArcClusterCustomLocationId = $arbArgResult.CustomLocation
$customProperties.TargetFabricAgentName = $targetDra.Name
Expand Down
6 changes: 6 additions & 0 deletions src/Migrate/Migrate/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
- Additional information about change #1
-->
## Upcoming Release
* Fixed bugs in `Initialize-AzMigrateLocalReplicationInfrastructure`
- Added early Azure login validation with a clear error message when user is not logged in
- Removed unnecessary caller identity resolution
- Added cache storage account validations to reject unsupported SKU tiers and disabled public network access
Comment thread
minhsuanlee marked this conversation as resolved.
* Updated `New-AzMigrateLocalServerReplication`
- Added storage path health validation before initiating replication

## Version 2.11.0
* Updated DefaultCrashConsistentFrequencyInMinutes and DefaultAppConsistentFrequencyInMinutes to align with Azure Portal UX for Replication Policy
Expand Down
Loading