Migrate.Autorest/custom/Get-AzMigrateServerMigrationStatus.ps1
|
# ---------------------------------------------------------------------------------- # # Copyright Microsoft Corporation # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ---------------------------------------------------------------------------------- <# .Synopsis Retrieves the details of the replicating server status. .Description The Get-AzMigrateServerMigrationStatus cmdlet retrieves the replication status for the replicating server. .Link https://learn.microsoft.com/powershell/module/az.migrate/get-azmigrateservermigrationstatus #> function Get-AzMigrateServerMigrationStatus { [OutputType([PSCustomObject[]])] [CmdletBinding(DefaultParameterSetName = 'ListByName', PositionalBinding = $false)] param( [Parameter(ParameterSetName = 'ListByName', Mandatory)] [Parameter(ParameterSetName = 'GetByMachineName', Mandatory)] [Parameter(ParameterSetName = 'GetHealthByMachineName', Mandatory)] [Parameter(ParameterSetName = 'GetByApplianceName', Mandatory)] [Parameter(ParameterSetName = 'GetByPrioritiseServer', Mandatory)] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Path')] [System.String] # Specifies the Resource Group of the Azure Migrate Project in the current subscription. ${ResourceGroupName}, [Parameter(ParameterSetName = 'ListByName', Mandatory)] [Parameter(ParameterSetName = 'GetByMachineName', Mandatory)] [Parameter(ParameterSetName = 'GetHealthByMachineName', Mandatory)] [Parameter(ParameterSetName = 'GetByApplianceName', Mandatory)] [Parameter(ParameterSetName = 'GetByPrioritiseServer', Mandatory)] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Path')] [System.String] # Specifies the Azure Migrate project in the current subscription. ${ProjectName}, [Parameter(ParameterSetName = 'GetByMachineName', Mandatory)] [Parameter(ParameterSetName = 'GetHealthByMachineName', Mandatory)] [Parameter(ParameterSetName = 'GetByPrioritiseServer', Mandatory)] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Path')] [System.String] # Specifies the display name of the replicating machine. ${MachineName}, [Parameter(ParameterSetName = 'GetByApplianceName', Mandatory)] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Path')] [System.String] # Specifies the name of the appliance. ${ApplianceName}, [Parameter(ParameterSetName = 'GetHealthByMachineName', Mandatory)] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Path')] [System.Management.Automation.SwitchParameter] # Specifies whether the health issues to show for replicating server. ${Health}, [Parameter(ParameterSetName = 'GetByPrioritiseServer', Mandatory)] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Path')] [System.Management.Automation.SwitchParameter] # Specifies whether to expedite the operation of a replicating server. ${Expedite}, [Parameter(ParameterSetName = 'ListByName')] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Query')] [System.String] # OData filter options. ${Filter}, [Parameter(ParameterSetName = 'ListByName')] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Query')] [System.String] # The pagination token. ${SkipToken}, [Parameter()] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Path')] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Runtime.DefaultInfo(Script = '(Get-AzContext).Subscription.Id')] [System.String] # Azure Subscription ID. ${SubscriptionId}, [Parameter()] [Alias('AzureRMContext', 'AzureCredential')] [ValidateNotNull()] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Azure')] [System.Management.Automation.PSObject] # The credentials, account, tenant, and subscription used for communication with Azure. ${DefaultProfile}, [Parameter(DontShow)] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Runtime')] [System.Management.Automation.SwitchParameter] # Wait for .NET debugger to attach ${Break}, [Parameter(DontShow)] [ValidateNotNull()] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Runtime')] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Runtime.SendAsyncStep[]] # SendAsync Pipeline Steps to be appended to the front of the pipeline ${HttpPipelineAppend}, [Parameter(DontShow)] [ValidateNotNull()] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Runtime')] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Runtime.SendAsyncStep[]] # SendAsync Pipeline Steps to be prepended to the front of the pipeline ${HttpPipelinePrepend}, [Parameter(DontShow)] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Runtime')] [System.Uri] # The URI for the proxy server to use ${Proxy}, [Parameter(DontShow)] [ValidateNotNull()] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Runtime')] [System.Management.Automation.PSCredential] # Credentials for a proxy server to use for the remote call ${ProxyCredential}, [Parameter(DontShow)] [Microsoft.Azure.PowerShell.Cmdlets.Migrate.Category('Runtime')] [System.Management.Automation.SwitchParameter] # Use the default credentials for the proxy ${ProxyUseDefaultCredentials} ) process { Function MakeTable ($TableName, $ColumnArray) { foreach($Col in $ColumnArray) { $MCol = New-Object System.Data.DataColumn $Col; $TableName.Columns.Add($MCol) } } $appMap = @{} Function PopulateApplianceDetails ($projName, $rgName) { # Get vault name from SMS solution. $smsSolution = Get-AzMigrateSolution -MigrateProjectName $projName -ResourceGroupName $rgName -Name "Servers-Migration-ServerMigration" if (-not $smsSolution.DetailExtendedDetail.AdditionalProperties.vaultId) { throw 'Azure Migrate appliance not configured. Setup Azure Migrate appliance before proceeding.' } $VaultName = $smsSolution.DetailExtendedDetail.AdditionalProperties.vaultId.Split("/")[8] # Get all appliances and sites in the project from SDS solution. $sdsSolution = Get-AzMigrateSolution -MigrateProjectName $projName -ResourceGroupName $rgName -Name "Servers-Discovery-ServerDiscovery" if ($null -ne $sdsSolution.DetailExtendedDetail["applianceNameToSiteIdMapV2"]) { $appMapV2 = $sdsSolution.DetailExtendedDetail["applianceNameToSiteIdMapV2"] | ConvertFrom-Json # Fetch all appliances from V2 map first. Then these can be updated if found again in V3 map. foreach ($item in $appMapV2) { $appMap[$item.SiteId.Split('/')[-1]] = $item.ApplianceName } } if ($null -ne $sdsSolution.DetailExtendedDetail["applianceNameToSiteIdMapV3"]) { $appMapV3 = $sdsSolution.DetailExtendedDetail["applianceNameToSiteIdMapV3"] | ConvertFrom-Json foreach ($item in $appMapV3) { $t = $item.psobject.properties $appMap[$t.Value.SiteId.Split('/')[-1]] = $t.Name } } } Function GetApplianceName ($site) { if (!$appMap.ContainsKey($site)) { return "No appliance found for site name: $site" } return $appMap[$site] } Function GetState { param( [string]$State, [object]$ReplicationMigrationItem ) if ($ReplicationMigrationItem.MigrationState -eq "MigrationFailed") { return "Migration Failed" } elseif ($ReplicationMigrationItem.MigrationState -match "InitialSeedingFailed") { return "InitialReplication Failed" } if ([string]::IsNullOrEmpty($State)) { return $ReplicationMigrationItem.MigrationState } $State = $State -replace "PlannedFailoverOverDeltaReplication", "FinalDeltaReplication" return $State } function Convert-MillisecondsToTime { param ( [int]$Milliseconds ) if ($Milliseconds -eq $null) { return $null } $TotalMinutes = [math]::Floor($Milliseconds / 60000) $Hours = [math]::Floor($TotalMinutes / 60) $Minutes = $TotalMinutes % 60 if ($Hours -eq 0) { if ($Minutes -eq 0) { return "-" } return "$Minutes min" } else { return "$Hours hr $Minutes min" } } function Convert-ToMbps { param ( [double]$UploadSpeedInBytesPerSecond ) if ($UploadSpeedInBytesPerSecond -eq $null -or $UploadSpeedInBytesPerSecond -eq 0) { return "-" } # Conversion factor: 1 byte = 8 bits $UploadSpeedInBitsPerSecond = $UploadSpeedInBytesPerSecond * 8 # Conversion factor: 1 megabit = 1,000,000 bits $UploadSpeedInMbps = [math]::Round($UploadSpeedInBitsPerSecond / 1e6) return "$UploadSpeedInMbps Mbps" } function Add-Percent { param ( [double]$Value ) if ($null -ne $Value) { return "$Value %" } else { return "-" } } function Add-MBps { param ( [double]$Value ) if ($null -ne $Value) { return "$Value MBps" } else { return "-" } } function Add-MB { param ( [double]$Value ) if ($null -ne $Value) { return "$Value MB" } else { return "-" } } function ConvertToCustomTimeFormat { param ( [string]$LocalTimeString ) if ([string]::IsNullOrEmpty($LocalTimeString)) { return "-" } # Parse the input string $localTime = [datetime]::ParseExact($LocalTimeString, "MM/dd/yyyy HH:mm:ss", $null) # Format the local time as desired $formattedTime = Get-Date $localTime -Format "M/d/yyyy, h:mm:ss tt" return $formattedTime } # Helper function to determine status function Get-ResourceStatus { param ( [double]$Capacity, [double]$Utilization, [string]$ResourceType ) if ($Capacity -eq 0 -or $null -eq $Capacity) { return "-" } if ($null -eq $Utilization) { return "-" } if ($ResourceType -match "CPU Sum" -and $Utilization -eq 0) { return "-" } $thresholds = @{ "ApplianceRam" = @{ AtCapacity = 95; Throttled = 85; Underutilized = 60 } "ApplianceCpu" = @{ AtCapacity = 99; Throttled = 95; Underutilized = 60 } "NetworkBandwidth" = @{ AtCapacity = 95; Throttled = 90; Underutilized = 50 } "EsxiNfcBuffer" = @{ AtCapacity = 100; Throttled = 90; Underutilized = 70 } "ParallelDisks" = @{ AtCapacity = 100; Throttled = 95; Underutilized = 70 } "Datastore" = @{ AtCapacity = 100; Throttled = 95; Underutilized = 70 } "Default" = @{ AtCapacity = 100; Throttled = 90; Underutilized = 70 } } # Map resource type to threshold set $typeKey = switch -Regex ($ResourceType) { "RAM" { "ApplianceRam" } "CPU" { "ApplianceCpu" } "Network" { "NetworkBandwidth" } "NFC" { "EsxiNfcBuffer" } "Disk" { "ParallelDisks" } "Datastore" { "Datastore" } default { "Default" } } $t = $thresholds[$typeKey] $percent = ($Utilization / $Capacity) * 100 if ($percent -ge $t.AtCapacity) { return "At capacity" } elseif ($percent -ge $t.Throttled) { return "Throttled" } elseif ($percent -le $t.Underutilized) { return "Underutilized" } else { return "Normal" } } $parameterSet = $PSCmdlet.ParameterSetName $null = $PSBoundParameters.Remove('ResourceGroupName') $null = $PSBoundParameters.Remove('ProjectName') $HasFilter = $PSBoundParameters.ContainsKey('Filter') $HasSkipToken = $PSBoundParameters.ContainsKey('SkipToken') $null = $PSBoundParameters.Remove('Filter') $null = $PSBoundParameters.Remove('SkipToken') $null = $PSBoundParameters.Remove('MachineName') $null = $PSBoundParameters.Remove('ApplianceName') $null = $PSBoundParameters.Remove('Health') $null = $PSBoundParameters.Remove('Expedite') $output = New-Object System.Collections.ArrayList # Create a hashtable to store the output. $null = $PSBoundParameters.Add("ResourceGroupName", $ResourceGroupName) $null = $PSBoundParameters.Add("Name", "Servers-Migration-ServerMigration") $null = $PSBoundParameters.Add("MigrateProjectName", $ProjectName) $solution = Az.Migrate.private\Get-AzMigrateSolution_Get @PSBoundParameters if ($solution -and ($solution.Count -ge 1)) { $VaultName = $solution.DetailExtendedDetail.AdditionalProperties.vaultId.Split("/")[8] } else { throw "Solution not found." } $null = $PSBoundParameters.Remove("Name") $null = $PSBoundParameters.Remove("MigrateProjectName") $null = $PSBoundParameters.Add('ResourceName', $VaultName) if ($HasFilter) { $null = $PSBoundParameters.Add("Filter", $Filter) } if ($HasSkipToken) { $null = $PSBoundParameters.Add("SkipToken", $SkipToken) } PopulateApplianceDetails $ProjectName $ResourceGroupName if ($parameterSet -eq "GetByApplianceName" -and !$appMap.ContainsValue($ApplianceName)) { throw "No appliance found with name $ApplianceName" } if ($PSBoundParameters.ContainsKey('SubscriptionId')) { if ($null -eq $PSBoundParameters['SubscriptionId'] -or ` ($PSBoundParameters['SubscriptionId'] -is [System.Array] -and $PSBoundParameters['SubscriptionId'].Count -eq 0) -or ` [string]::IsNullOrEmpty($PSBoundParameters['SubscriptionId'])) { $currentContext = Get-AzContext if ($null -eq $currentContext -or [string]::IsNullOrEmpty($currentContext.Subscription.Id)) { throw "No Azure context is set. Please login using Connect-AzAccount." } $PSBoundParameters['SubscriptionId'] = $currentContext.Subscription.Id } } else { $currentContext = Get-AzContext if ($null -eq $currentContext -or [string]::IsNullOrEmpty($currentContext.Subscription.Id)) { throw "No Azure context is set. Please login using Connect-AzAccount." } $PSBoundParameters['SubscriptionId'] = $currentContext.Subscription.Id } if ($parameterSet -eq "GetByMachineName" -or $parameterSet -eq "GetHealthByMachineName" -or $parameterSet -eq "GetByPrioritiseServer") { $ReplicationMigrationItems = Get-AzMigrateServerReplication -ProjectName $ProjectName -ResourceGroupName $ResourceGroupName -MachineName $MachineName -SubscriptionId $PSBoundParameters['SubscriptionId'] } else { $ReplicationMigrationItems = Get-AzMigrateServerReplication -ProjectName $ProjectName -ResourceGroupName $ResourceGroupName -SubscriptionId $PSBoundParameters['SubscriptionId'] } if ($ReplicationMigrationItems -eq $null) { if ($parameterSet -eq "GetByMachineName" -or $parameterSet -eq "GetHealthByMachineName") { Write-Host "No replicating machine found with name $MachineName." } else { Write-Host "No replicating machine found." } return; } $vmMigrationStatusTable = New-Object System.Data.DataTable("") if ($parameterSet -eq "GetByApplianceName") { $column = @("Server", "State", "Progress", "TimeElapsed", "TimeRemaining", "UploadSpeed", "Health", "LastSync", "ESXiHost", "Datastore") } elseif ($parameterSet -eq "ListByName") { $column = @("Appliance", "Server", "State", "Progress", "TimeElapsed", "TimeRemaining", "UploadSpeed", "Health", "LastSync", "ESXiHost", "Datastore") } else { $column = @("Appliance", "Server", "State", "Progress", "TimeElapsed", "TimeRemaining", "UploadSpeed", "LastSync", "ESXiHost", "Datastore") } MakeTable $vmMigrationStatusTable $column foreach ($ReplicationMigrationItem in $ReplicationMigrationItems) { if ($parameterSet -eq "GetByMachineName") { if ($ReplicationMigrationItem.health -eq "Normal") { $op = $output.Add("`nServer $MachineName is currently healthy.") } elseif ($ReplicationMigrationItem.health -eq "None") { $op = $output.Add("`nServer $MachineName is in $($ReplicationMigrationItems.ReplicationStatus) state.") } else { $op = $output.Add("`nServer $MachineName is currently facing critical error/ warning. Please run the command given below to know about the errors and resolutions.`n`nGet-AzMigrateServerMigrationStatus -ProjectName <String> -ResourceGroupName <String> -Appliance <String> -MachineName <String> -Health") } } if ($parameterSet -eq "GetByMachineName" -or $parameterSet -eq "GetHealthByMachineName" -or $parameterSet -eq "GetByPrioritiseServer") { $ReplicationMigrationItem = Get-AzMigrateServerReplication -TargetObjectID $ReplicationMigrationItem.Id } $site = $ReplicationMigrationItem.ProviderSpecificDetail.vmwareMachineId.Split('/')[-3] $appName = GetApplianceName $site $row1 = $vmMigrationStatusTable.NewRow() if ($parameterSet -eq "GetByApplianceName" -and $appName -ne $ApplianceName) { continue; } if ($parameterSet -ne "GetByApplianceName") { $row1["Appliance"] = $appName } $row1["Server"] = $ReplicationMigrationItem.MachineName $row1["State"] = GetState -State $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailState -ReplicationMigrationItem $ReplicationMigrationItem if ($ReplicationMigrationItem.ReplicationStatus -match "Pause" -and $ReplicationMigrationItem.MigrationState -notmatch "migration") { $row1["State"] = $ReplicationMigrationItem.ReplicationStatus $row1["TimeRemaining"] = "-" $row1["UploadSpeed"] = "-" $row1["Progress"] = "-" $row1["TimeElapsed"] = "-" } elseif ($ReplicationMigrationItem.ReplicationStatus -match "Resum") { $row1["State"] = $ReplicationMigrationItem.ReplicationStatus $row1["TimeRemaining"] = Convert-MillisecondsToTime -Milliseconds $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailTimeRemaining $row1["UploadSpeed"] = Convert-ToMbps -UploadSpeedInBytesPerSecond $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailUploadSpeed $row1["Progress"] = Add-Percent -Value $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailProgressPercentage $row1["TimeElapsed"] = Convert-MillisecondsToTime -Milliseconds $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailTimeElapsed } elseif ($ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailState -match "Completed") { $row1["TimeRemaining"] = "-" $row1["UploadSpeed"] = "-" $row1["Progress"] = "-" $row1["TimeElapsed"] = "-" } else { $row1["TimeRemaining"] = Convert-MillisecondsToTime -Milliseconds $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailTimeRemaining $row1["UploadSpeed"] = Convert-ToMbps -UploadSpeedInBytesPerSecond $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailUploadSpeed $row1["Progress"] = Add-Percent -Value $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailProgressPercentage $row1["TimeElapsed"] = Convert-MillisecondsToTime -Milliseconds $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailTimeElapsed } if ($parameterSet -eq "ListByName" -or $parameterSet -eq "GetByApplianceName") { if ([string]::IsNullOrEmpty($ReplicationMigrationItem.health) -or $ReplicationMigrationItem.health -eq "None") { $row1["Health"] = "-" } else { $row1["Health"] = $ReplicationMigrationItem.health } } $row1["LastSync"] = ConvertToCustomTimeFormat -LocalTimeString $ReplicationMigrationItem.ProviderSpecificDetail.lastRecoveryPointReceived $row1["ESXiHost"] = $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailHostName if (-not [string]::IsNullOrEmpty($ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailDataStore)) { $row1["Datastore"] = $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailDataStore -join ', ' } else { $row1["Datastore"] = "-" } $vmMigrationStatusTable.Rows.Add($row1) if( $parameterSet -eq "GetByMachineName" -or $parameterSet -eq "GetHealthByMachineName" -or $parameterSet -eq "GetByPrioritiseServer") { if ($parameterSet -eq "GetHealthByMachineName" -or $parameterSet -eq "GetByPrioritiseServer") { $op = $output.Add("`nServer Information:") } $vmMigrationStatusTable = $vmMigrationStatusTable | Format-Table -AutoSize | Out-String $op = $output.Add($vmMigrationStatusTable) # Store the table in the output hashtable $diskStatusTable = New-Object System.Data.DataTable("") $diskcolumn = @("Disk", "State", "Progress", "TimeElapsed", "TimeRemaining", "UploadSpeed", "Datastore") MakeTable $diskStatusTable $diskcolumn foreach($disk in $ReplicationMigrationItem.ProviderSpecificDetail.ProtectedDisk) { $row = $diskStatusTable.NewRow() $row["Disk"] = $disk.DiskName $row["State"] = GetState -State $disk.GatewayOperationDetailState -ReplicationMigrationItem $ReplicationMigrationItem if ($ReplicationMigrationItem.ReplicationStatus -match "Pause" -and $ReplicationMigrationItem.MigrationState -notmatch "migration") { $row["State"] = $ReplicationMigrationItem.ReplicationStatus $row["TimeRemaining"] = "-" $row["UploadSpeed"] = "-" $row["Progress"] = "-" $row["TimeElapsed"] = "-" } elseif ($ReplicationMigrationItem.ReplicationStatus -match "Resum") { $row["State"] = $ReplicationMigrationItem.ReplicationStatus #$row["TimeRemaining"] = Convert-MillisecondsToTime -Milliseconds $disk.GatewayOperationDetailTimeRemaining $row["TimeRemaining"] = "-" $row["UploadSpeed"] = "-" #$row["UploadSpeed"] = Convert-ToMbps -UploadSpeedInBytesPerSecond $disk.GatewayOperationDetailUploadSpeed #$row["Progress"] = Add-Percent -Value $disk.GatewayOperationDetailProgressPercentage $row["Progress"] = "-" $row["TimeElapsed"] = "-" #$row["TimeElapsed"] = Convert-MillisecondsToTime -Milliseconds $disk.GatewayOperationDetailTimeElapsed } elseif ($disk.GatewayOperationDetailState -match "Completed") { $row["Progress"] = "-" $row["TimeElapsed"] = "-" $row["TimeRemaining"] = "-" $row["UploadSpeed"] = "-" } else { $row["TimeRemaining"] = Convert-MillisecondsToTime -Milliseconds $disk.GatewayOperationDetailTimeRemaining $row["UploadSpeed"] = Convert-ToMbps -UploadSpeedInBytesPerSecond $disk.GatewayOperationDetailUploadSpeed $row["Progress"] = Add-Percent -Value $disk.GatewayOperationDetailProgressPercentage $row["TimeElapsed"] = Convert-MillisecondsToTime -Milliseconds $disk.GatewayOperationDetailTimeElapsed } if (-not [string]::IsNullOrEmpty($disk.GatewayOperationDetailDataStore)) { $row["Datastore"] = $disk.GatewayOperationDetailDataStore -join ', ' } else { $row["Datastore"] = "-" } $diskStatusTable.Rows.Add($row) } if ($parameterSet -eq "GetHealthByMachineName" -or $parameterSet -eq "GetByPrioritiseServer") { $op = $output.Add("`nDisk Level Operation Status:") } $diskStatusTable = $diskStatusTable | Format-Table -AutoSize | Out-String $op = $output.Add($diskStatusTable) # Store the table in the output hashtable } if ($parameterSet -eq "GetHealthByMachineName") { if ($ReplicationMigrationItem.health -eq "Normal") { $op = $output.Add("No warnings or critical errors for this server.") } else { $op = $output.Add("List of warning or critical errors for this server with their resolutions: `n") $healthError = $ReplicationMigrationItem.HealthError foreach ($err in $healthError) { $op = $output.Add("Error Message: $($err.ErrorMessage)`nPossible Causes: $($err.PossibleCaus)`nRecommended Actions: $($err.RecommendedAction)`n`n") } } } } if( $parameterSet -eq "GetByPrioritiseServer") { $replicationState = GetState -State $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailState -ReplicationMigrationItem $ReplicationMigrationItem if ($replicationState -match "Failed" -or $replicationState -match "Completed" -or ($replicationState -notmatch "InProgress" -and $replicationState -notmatch "Queued")) { $op = $output.Add("Replication for server '$($ReplicationMigrationItem.MachineName)' is in state '$replicationState'. Expedite recommendations are only applicable for servers in 'Queued' or 'InProgress' state.`n") return $output; } # Build a table of VMs sharing resources (appliance, datastore, ESXi host) with the reference VM. $vmMigrationTable = New-Object System.Data.DataTable("") $column = @("Appliance", "Server", "SharedResourceType", "State", "TimeRemaining", "ESXiHost", "Datastore") MakeTable $vmMigrationTable $column # Get reference VM's ESXi host and datastores $refESXiHost = $ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailHostName $refDatastores = @() if ($ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailDataStore) { $refDatastores = @($ReplicationMigrationItem.ProviderSpecificDetail.GatewayOperationDetailDataStore) } $MigrationItems = Get-AzMigrateServerReplication -ProjectName $ProjectName -ResourceGroupName $ResourceGroupName $addedMachines = @{} # Hashtable to track added machines foreach($MigrationItem in $MigrationItems) { # Skip the reference VM itself if ($MigrationItem.MachineName -eq $ReplicationMigrationItem.MachineName) { continue } # Skip if this machine has already been added if ($addedMachines.ContainsKey($MigrationItem.MachineName)) { continue } $site = $MigrationItem.ProviderSpecificDetail.vmwareMachineId.Split('/')[-3] $appName1 = GetApplianceName $site # Check for shared resources $sharedTypes = @() if ($appName1 -eq $appName) { $sharedTypes += "Appliance" } $esxiHost = $MigrationItem.ProviderSpecificDetail.GatewayOperationDetailHostName if ($refESXiHost -and $esxiHost -and ($esxiHost -eq $refESXiHost)) { $sharedTypes += "ESXiHost" } $datastores = @() if ($MigrationItem.ProviderSpecificDetail.GatewayOperationDetailDataStore) { $datastores = @($MigrationItem.ProviderSpecificDetail.GatewayOperationDetailDataStore) } $commonDatastores = $refDatastores | Where-Object { $datastores -contains $_ } foreach ($ds in $commonDatastores) { $sharedTypes += "Datastore" } if ($sharedTypes.Count -gt 0) { $row1 = $vmMigrationTable.NewRow() $row1["Appliance"] = $appName1 $row1["Server"] = $MigrationItem.MachineName $row1["SharedResourceType"] = $sharedTypes -join ', ' $row1["TimeRemaining"] = Convert-MillisecondsToTime -Milliseconds $MigrationItem.ProviderSpecificDetail.GatewayOperationDetailTimeRemaining $row1["State"] = $MigrationItem.ProviderSpecificDetail.GatewayOperationDetailState $row1["ESXiHost"] = $MigrationItem.ProviderSpecificDetail.GatewayOperationDetailHostName if ($datastores -and $datastores.Count -gt 0) { $row1["Datastore"] = $datastores -join ', ' } else { $row1["Datastore"] = "-" } $vmMigrationTable.Rows.Add($row1) # Mark this machine as added to avoid duplicates $addedMachines[$MigrationItem.MachineName] = $true } } if ($vmMigrationTable.Rows.Count -gt 0) { $op = $output.Add("Resource Sharing:`n`nThe following VMs share at least one resource (Appliance, ESXi Host, or Datastore) with VM " + ` "'$($ReplicationMigrationItem.MachineName)'. The 'SharedResourceType' and 'SharedResourceName' columns indicate which resource is shared.") $vmMigrationTable = $vmMigrationTable | Format-Table -AutoSize | Out-String $op = $output.Add($vmMigrationTable) } else { $op = $output.Add("No other VMs found sharing Appliance, ESXi Host, or Datastore with VM '$($ReplicationMigrationItem.MachineName)'.") } $resourceUtilizationTable = New-Object System.Data.DataTable("") $column = @("Resource", "Capacity", "Utilization for server migrations", "Total utilization", "Status") MakeTable $resourceUtilizationTable $column # RAM $row1 = $resourceUtilizationTable.NewRow() $ramCapacity = $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.RamDetailCapacity $ramTotalUtil = $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.RamDetailTotalUtilization $row1["Resource"] = "Appliance RAM Sum : Primary and scale out appliances" $row1["Capacity"] = Add-MB -Value $ramCapacity $row1["Utilization for server migrations"] = Add-MB -Value ($ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.RamDetailProcessUtilization) $row1["Total utilization"] = Add-MB -Value $ramTotalUtil $row1["Status"] = Get-ResourceStatus -Capacity $ramCapacity -Utilization $ramTotalUtil -ResourceType $row1["Resource"] $resourceUtilizationTable.Rows.Add($row1) # CPU $row2 = $resourceUtilizationTable.NewRow() $cpuCapacity = $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.CpuDetailCapacity $cpuProcessUtil = $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.CpuDetailProcessUtilization $cpuTotalUtil = $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.CpuDetailTotalUtilization $row2["Resource"] = "Appliance CPU Sum : Primary and scale out appliances" if ($null -ne $cpuCapacity -and $cpuCapacity -ne 0) { $row2["Capacity"] = "$($cpuCapacity) Cores" } else { $row2["Capacity"] = "-" } $row2["Utilization for server migrations"] = Add-Percent -Value $cpuProcessUtil $row2["Total utilization"] = Add-Percent -Value $cpuTotalUtil $row2["Status"] = Get-ResourceStatus -Capacity 100 -Utilization $cpuTotalUtil -ResourceType $row2["Resource"] $resourceUtilizationTable.Rows.Add($row2) # Network Bandwidth $row3 = $resourceUtilizationTable.NewRow() $netCapacity = $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.NetworkBandwidthCapacity $netTotalUtil = $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.NetworkBandwidthTotalUtilization $row3["Resource"] = "Network bandwidth Sum : Primary and scale out appliances" $row3["Capacity"] = Add-MBps -Value $netCapacity $row3["Utilization for server migrations"] = Add-MBps -Value $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.NetworkBandwidthProcessUtilization $row3["Total utilization"] = Add-MBps -Value $netTotalUtil $row3["Status"] = Get-ResourceStatus -Capacity $netCapacity -Utilization $netTotalUtil -ResourceType $row3["Resource"] $resourceUtilizationTable.Rows.Add($row3) # ESXi NFC Buffer $row4 = $resourceUtilizationTable.NewRow() $nfcCapacity = $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.EsxiNfcBufferCapacity $nfcProcessUtil = $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.EsxiNfcBufferProcessUtilization $row4["Resource"] = "ESXi host NFC buffer" $row4["Capacity"] = Add-MB -Value $nfcCapacity $row4["Utilization for server migrations"] = Add-MB -Value $nfcProcessUtil $row4["Total utilization"] = "-" $row4["Status"] = Get-ResourceStatus -Capacity $nfcCapacity -Utilization $nfcProcessUtil -ResourceType $row4["Resource"] $resourceUtilizationTable.Rows.Add($row4) # Parallel Disks Replicated $row5 = $resourceUtilizationTable.NewRow() $diskCapacity = $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.DiskReplicationDetailCapacity $diskProcessUtil = $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.DiskReplicationDetailProcessUtilization $row5["Resource"] = "Parallel Disks Replicated Sum : Primary and scale out appliances" if ($null -ne $diskCapacity) { $row5["Capacity"] = $diskCapacity } else { $row5["Capacity"] = "-" } if ($null -ne $diskProcessUtil) { $row5["Utilization for server migrations"] = $diskProcessUtil } else { $row5["Utilization for server migrations"] = "-" } $row5["Total utilization"] = "-" $row5["Status"] = Get-ResourceStatus -Capacity $diskCapacity -Utilization $diskProcessUtil -ResourceType $row5["Resource"] $resourceUtilizationTable.Rows.Add($row5) # Datastore Snapshots (list) $datastoreList = $ReplicationMigrationItem.ProviderSpecificDetail.ApplianceMonitoringDetail.datastoreSnapshot if ($datastoreList) { foreach ($ds in $datastoreList) { $row = $resourceUtilizationTable.NewRow() if ($null -ne $ds.datastoreName) { $datastoreName = $ds.datastoreName } else { $datastoreName = "-" } $row["Resource"] = "Datastore '$datastoreName' Snapshot Count" if ($null -ne $ds.totalSnapshotsSupported) { $row["Capacity"] = $ds.totalSnapshotsSupported } else { $row["Capacity"] = "-" } if ($null -ne $ds.totalSnapshotsCreated) { $row["Utilization for server migrations"] = $ds.totalSnapshotsCreated } else { $row["Utilization for server migrations"] = "-" } $row["Total utilization"] = "-" $row["Status"] = Get-ResourceStatus -Capacity $ds.totalSnapshotsSupported -Utilization $ds.totalSnapshotsCreated -ResourceType $row["Resource"] $resourceUtilizationTable.Rows.Add($row) } } $op = $output.Add("Resource utilization information for migration operations:") $resourceUtilizationTableString = $resourceUtilizationTable | Format-Table -AutoSize | Out-String $op = $output.Add($resourceUtilizationTableString) # Recommendations $recommendations = @() foreach ($row in $resourceUtilizationTable.Rows) { $resource = $row["Resource"] # Normalize resource string for matching $resourceNorm = $resource.ToLower() $status = $row["Status"].ToLower() $isQueued = $replicationState -match "Queued" if ($status -eq "Throttled" -or $status -eq "At capacity") { if ($resourceNorm -like "*ram*") { $recommendations += "`nResource `"$resource`" is $status. Recommendations:" $recommendations += "Stop other processes running on the appliance that are consuming RAM." $recommendations += "Stop initial replication (IR) or pause delta replication (DR) for other low-priority VMs migrating under this appliance to free up RAM." $recommendations += "Decrease the number of workers (parallel disk replications)." $ramCapacityVal = 0 if ($row["Capacity"] -match "(\d+(\.\d+)?)") { $ramCapacityVal = [double]$matches[1] } if ($ramCapacityVal -lt 32768 -and $ramCapacityVal -gt 0) { $recommendations += "Consider increasing the appliance RAM to improve migration performance and support higher workloads." } $recommendations += "If only the primary appliance is present, consider adding or increasing the number of scale-out appliances.`n" } elseif ($resourceNorm -like "*cpu*") { $recommendations += "`nResource `"$resource`" is $status. Recommendations:" $recommendations += "Stop other processes running on the appliance that are consuming CPU." $recommendations += "Stop initial replication (IR) or pause delta replication (DR) for other low-priority VMs migrating under this appliance to free up CPU." $recommendations += "Decrease the number of workers (parallel disk replications)." $recommendations += "If only the primary appliance is present, consider adding or increasing the number of scale-out appliances.`n" } elseif ($resourceNorm -like "*network bandwidth*") { $recommendations += "`nResource `"$resource`" is $status. Recommendations:" $recommendations += "Pause or stop other processes utilizing the network." $recommendations += "Stop initial replication (IR) or pause delta replication (DR) for other low-priority VMs migrating under this appliance to free up network bandwidth." $recommendations += "Decrease the number of workers (parallel disk replications)." $recommendations += "Review and adjust Quality of Service (QoS) limits per process if applicable." $recommendations += "Increase the network bandwidth available to the appliance.`n" } elseif ($resourceNorm -like "*nfc buffer*") { if ($isQueued) { $recommendations += "`nResource `"$resource`" is $status. Recommendations:" $recommendations += "Stop initial replication (IR) or pause delta replication (DR) for other low-priority VMs migrating under this appliance to free up NFC buffer on the ESXi host." $recommendations += "Perform vMotion for other low-priority virtual machines." $recommendations += "Increase the size of the NFC buffer on the ESXi host." $recommendations += "Stop or schedule blackout windows for other backup providers running on the ESXi host.`n" } } elseif ($resourceNorm -like "*parallel disk replicated*") { if ($isQueued) { $recommendations += "`nResource `"$resource`" is $status. Recommendations:" $recommendations += "Add a scale-out appliance to distribute the migration workload more effectively." $recommendations += "Stop initial replication (IR) or pause delta replication (DR) for other low-priority VMs migrating under this appliance to free up parallel disk replication capacity." $recommendations += "If this is the only bottleneck and all other resources are available, increase the number of workers (parallel disk replications)." } } elseif ($resourceNorm -like "*datastore*" -and $resourceNorm -like "*snapshot*") { if ($isQueued) { $recommendations += "`nResource `"$resource`" is $status. Recommendations:" $recommendations += "Pause or stop replications for other VMs migrating under this appliance to free up snapshot capacity." $recommendations += "Increase the snapshot count supported by the datastore." $recommendations += "Perform storage vMotion for other low-priority replicating VMs.`n" } } } } if ($recommendations.Count -gt 0) { $op = $output.Add("Based on the resource utilization seen above, here are suggestions to expedite server $($ReplicationMigrationItem.MachineName) migration:") foreach ($rec in $recommendations) { $op = $output.Add("$rec") } } } if ($parameterSet -eq "GetByApplianceName" -or $parameterSet -eq "ListByName") { if ($parameterSet -eq "ListByName") { $desiredCols = @("Appliance","Server","State","Progress","TimeElapsed","TimeRemaining","UploadSpeed","Health","LastSync", "ESXiHost", "Datastore") } elseif ($parameterSet -eq "GetByApplianceName") { $desiredCols = @("Server","State","Progress","TimeElapsed","TimeRemaining","UploadSpeed","Health","LastSync","ESXiHost","Datastore") } else { $desiredCols = $vmMigrationStatusTable.Columns | ForEach-Object { $_.ColumnName } } $existingCols = $vmMigrationStatusTable.Columns | ForEach-Object { $_.ColumnName } $cols = $desiredCols | Where-Object { $existingCols -contains $_ } if (-not $cols -or $cols.Count -eq 0) { $cols = $existingCols } # Select columns explicitly and force Format-Table to render all of them (no truncation) $vmMigrationStatusTable = $vmMigrationStatusTable | Select-Object -Property $cols | Format-Table -Property $cols -AutoSize -Wrap -Force | Out-String -Width 4096 $op = $output.Add($vmMigrationStatusTable) $op = $output.Add("To check expedite the operation of a server use the command") $op = $output.Add("Get-AzMigrateServerMigrationStatus -ProjectName <String> -ResourceGroupName <String> -MachineName <String> -Expedite`n") $op = $output.Add("To resolve the health issue use the command") $op = $output.Add("Get-AzMigrateServerMigrationStatus -ProjectName <String> -ResourceGroupName <String> -MachineName <String> -Health`n") } return $output; } } # SIG # Begin signature block # MIInbgYJKoZIhvcNAQcCoIInXzCCJ1sCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCSydufDj+aTpRl # WoYyzoEcUkr1mRaAMuphr/F/2eBHJKCCDMkwggYEMIID7KADAgECAhMzAAACHPrN # xZvoL37EAAAAAAIcMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQxWhcNMjcwNDE1MTg1 # OTQxWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDVsZfgOKmM31HPfoWOoNEiw0SlCiIxUMC0I9NMWbucKOw/e9lP # oAoehQVu6SG65V4EPzrYsnBnFPNoi4/HoOdjhz1qkrEt4I6tEcxXU6oOeY9zGveC # /3iBeuhLYxM3M/PkcUoebF+Nednm8OkdSPoDu8imViHPQq/8CQUu0WRR4rE+dMRf # rpVqfmNi2qWCX94T4MsepijGVkwE//tJg0ryAiYdHT34LSnlG/RSBZmQRGWZ5g8j # qnKjRParSqMft1gvjuUTVgtWNZfgcLFSK5Wa0myrq8OPcgTGGsRgun+tnSS+IxDT # xVsAPH1OzvPjwomguByhUe/OcvUN0D5Wmp7xAgMBAAGjggGqMIIBpjAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFNoH7a2YDjOSwpkp6DHcmUS7J+0yMFQGA1UdEQRNMEukSTBHMS0wKwYDVQQL # EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxFjAUBgNVBAUT # DTIzMDAxMis1MDc1NjkwHwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEw # YAYDVR0fBFkwVzBVoFOgUYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w # cy9jcmwvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # bDBtBggrBgEFBQcBAQRhMF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9z # b2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmcl # MjBQQ0ElMjAyMDI0LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IC # AQAUnEqhaRXe0T3hIJjvdQErEkrA/7bByjn6t5IArODkkRjzkYwtKMc2yYj2quaN # rLutWw2YZcngKPy1b71YyDJQTy4NDRwaSh9Tw5thrk3NmcPrAHia5vtcBJ1CgtKK # 7mQbIcQ22d/N3813ayCDDFewu1+jsZmX+r/aTEqaOM4TVxVtRSkuCy8nAXKuChOK # Li/zA4XuH8iEYqIsj2YoNaeSxVmeGiERXpKdo3dDmYi0kO5w2D8VS4c3+9h6gElY # BaAAg/dYErBg27qT3vv0zRDJhJufvCNylA8S7/+8H5E/PV5cng6na9VV/w9OV3qu # uND6zdGa2EX38Glp50F9AIQk3p2xXmcvorDeM4XJ7UlWYBi6g80J1SSOQnInCYFE # msfUNn3+1AaTJKSJL83quKArTac2pKhu0Yzzzrzo6HrsRiQKzpnRBb1/dMa6P3hz # 75XbMRBctNsFhZC07WCmjExdLg2eHW5uV0TY8D5+6wozJf7vF3+WHkYPO85Z+BC6 # U4FkNbYNycZ9cE4j1tXRdyDCfml6c0HWPHjNVDObrv9lKt3qUqFpX38VCqVCyNOO # 1UcXfQiVjJw32U2WUKZjt/neJKHEBsm9kFsLuWzkQ53+qcaSaytmsCnk2gOglrlD # 5d3kKyvvAw+rzm0lT8K38P6PLxfZQHhu4W8dV7Av8N2ZmDCCBr0wggSloAMCAQIC # EzMAAAA5O7Y3Gb8GHWcAAAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS # b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoX # DTM2MDMyMjIyMTMwNFowVzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQ # Q0EgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeq # lRYHNa265v4IY9fH8TKhemHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo # 0dtS/EW6I/yEL/bLSY8hKpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATv # QVL4tcf03aTycsz8QeCdM0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a # 1uv1zerOYMnsneRRwCbpyW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1 # FyQfK0fVkaya8SmVHQ/tOf23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfO # GSWHIIV4YrTJTT6PNty5REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7 # ttOu1bVnXfHaqPYl2rPs20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJ # uz2MXMCt7iw7lFPG9LXKGjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxS # CwyoGIq0PhaA7Y+VPct5pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOm # VQop36wUVUYklUy++vDWeEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3 # SkE/xIkgpfl22MM1itkZ35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8E # BAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPX # LQaUEggxMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMB # Af8wHwYDVR0jBBgwFoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBP # oE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv # TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAw # TgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMv # TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOC # AgEAFJQfOChP7onn6fLIMKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D # 5W4wMwYeLystcEqfkjz4NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBY # nbu0+THSuVHTe0VTTPVhily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSI # vgn0JksVBVMYVI5QFu/qhnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6 # aR9y34aiM1qmxaxBi6OUnyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4w # PKC5OmHm1DQIt/MNokbbH3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7 # RTX8AdBPo0I6OEojf39zuFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK # /fg8B2qjW88MT/WF5V5uvZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSK # YBv0VisCzfxgeU+dquXW9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkw # YTu/9dLeH2pDqeJZAABVDWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVT # Ql0v4q8J/AUmQN5W4n101cY2L4A7GTQG1h32HHAvfQESWP0xghn7MIIZ9wIBATBu # MFcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # KDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIc # +s3Fm+gvfsQAAAAAAhwwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwG # CisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZI # hvcNAQkEMSIEIM3MB4mmzoPSidD1i0VHmTbfQiTtEY+s+0KveNRREqn4MEIGCisG # AQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAwZv6gEYOatH9eydpYLB0 # gjGbgvkMh0QIwCzPNLvfCS+3Rg7BPzv3Jodq/9d7KY4mZ97aeGHBJ/tK9L8vX5ZH # BV89jdc2ycVBaWe7LNrG+J5pkB1Cs87FIOSkSXu4vunno+UkuT5unevfSaxokX+u # y9sXrRVXrETSbsxB7jss3GgAUI89lVULshhJJ6oXL8OafL2AlySKGis3s0AE+0e3 # zxNwz9UZWGamC0d7LBEakPmSMz+qPdGB3gfPo+VLp17bD7/MbRu4ayYY1QkaMdEe # JyyUplSC3XNxyAXkttQ36wa/HThRpZ181SL1FFPgR/cOKvjWOoPDC8RwrrHoDSZ1 # 9KGCF60wghepBgorBgEEAYI3AwMBMYIXmTCCF5UGCSqGSIb3DQEHAqCCF4YwgheC # AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFFMIIB # QQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCA/sncMqFU/0MLZcsGv # wGMoGog9uWbsl4BMmykFSPPWNgIGaq5gzUn7GBMyMDI2MDkzMDA0MTk0My41MjJa # MASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0 # ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo0QzFBLTA1RTAtRDk0NzElMCMG # A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEfswggcoMIIFEKAD # AgECAhMzAAACGCXZkgXi5+XkAAEAAAIYMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgyNVoXDTI2MTExMzE4 # NDgyNVowgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTAr # BgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUG # A1UECxMeblNoaWVsZCBUU1MgRVNOOjRDMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxN # aWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEAsdzo6uuQJqAfxLnvEBfIvj6knK+p6bnMXEFZ/QjPOFywlcjD # fzI8Dg1nzDlxm7/pqbvjWhyvazKmFyO6qbPwClfRnI57h5OCixgpOOCGJJQIZSTi # Mgui3B8DPiFtJPcfzRt3FsnxjLXwBIjGgnjGfmQl7zejA1WoYL/qBmQhw/FDFTWe # bxfo4m0RCCOxf2qwj31aOjc2aYUePtLMXHsXKPFH0tp5SKIF/9tJxRSg0NYEvQqV # ilje8aQkPd3qzAux2Mc5HMSK4NMTtVVCYAWDUZ4p+6iDI9t5BNCBIsf5ooFNUWtx # CqnpFYiLYkHfFfxhVUBZ8LGGxYsA36snD65s2Hf4t86k0e8WelH/usfhYqOM3z2y # aI8rg08631IkwqUzyQoEPqMsHgBem1xpmOGSIUnVvTsAv+lmECL2RqrcOZlZax8K # 0aiij8h6UkWBN2IA/ikackTSGVRBQmWWZuLFWV/T4xuNzscC0X7xo4fetgpsqaEA # 0jY/QevkTvLv4OlNN9eOL8LNh7Vm0R65P7oabOQDqtUFAwCgjgPJ0iV/jQCaMAcO # 3SYpG5wSAYiJkk4XLjNSlNxU2Idjs1sORhl7s7LC6hOb7bVAHVwON74GxfFNiEIA # 6BfudANjpQJ0nUc/ppEXpT4pgDBHsYtV8OyKSjKsIxOdFR7fIJIjDc8DvUkCAwEA # AaOCAUkwggFFMB0GA1UdDgQWBBQkLqHEXDobY7dHuoQCBa4sX7aL0TAfBgNVHSME # GDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRw # Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1l # LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsG # AQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p # Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMB # Af8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDAN # BgkqhkiG9w0BAQsFAAOCAgEAnkjRhjwPgdoIpvt4YioT/j0LWuBxF3ARBKXDENgg # raKvC0oRPwbjAmsXnPEmtuo5MD8uJ9Xw9eYrxqqkK4DF9snZMrHMfooxCa++1irL # z8YoozC4tci+a4N37Sbke1pt1xs9qZtvkPgZGWn5BcwVfmAwSZLHi2CuZ06Y0/X+ # t6fNBnrbMVovNaDX4WPdyI9GEzxfIggDsck2Ipo4VXL/Arcz7p2F7bEZGRuyxjgM # C+woCkDJaH/yk/wcZpAsixe4POdN0DW6Zb35O3Dg3+a6prANMc3WIdvfKDl75P0a # qcQbQAR7b0f4gH4NMkUct0Wm4GN5KhsE1YK7V/wAqDKmK4jx3zLz3a8Hsxa9HB3G # yitlmC5sDhOl4QTGN5kRi6oCoV4hK+kIFgnkWjHhSRNomz36QnbCSG/BHLEm2GRU # 9u3/I4zUd9E1AC97IJEGfwb+0NWb3QEcrkypdGdWwl0LEObhrQR9B1V7+edcyNms # X0p2BX0rFpd1PkXJSbxf8IcEiw/bkNgagZE+VlDtxXeruLdo5k3lGOv7rPYuOEao # ZYxDvZtpHP9P36wmW4INjR6NInn2UM+krP/xeLnRbDBkm9RslnoDhVraliKDH62B # xhcgL9tiRgOHlcI0wqvVWLdv8yW8rxkawOlhCRqT3EKECW8ktUAPwNbBULkT+oWc # vBcwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEB # CwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYD # VQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAe # Fw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGm # TOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/H # ZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDc # wUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62A # W36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1w # jjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCG # MFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ # 1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP # 8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFz # ymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHz # NgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3 # xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsG # AQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/ # LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEG # DCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29m # dC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYB # BQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8G # A1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQw # VgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9j # cmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUF # BwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br # aS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQEL # BQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfC # cTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AF # vonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l # 9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn # 8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5m # O0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyx # TkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4 # S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9 # y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM # +Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhw # RNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDVjCCAj4C # AQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0 # ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo0QzFBLTA1RTAtRDk0NzElMCMG # A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIa # AxUAnWtGrXWiuNE8QrKfm4CtGr57z+mggYMwgYCkfjB8MQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T # dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAO5mtfkwIhgPMjAyNjA5Mjky # MjExMDVaGA8yMDI2MDkzMDIyMTEwNVowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA # 7ma1+QIBADAHAgEAAgIhBTAHAgEAAgISXDAKAgUA7mgHeQIBADA2BgorBgEEAYRZ # CgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0G # CSqGSIb3DQEBCwUAA4IBAQAd+wN72L32bbG4IY2R2Dn12Z/H+AACPJoLItqVLVN7 # 18DDTWyqjWWhSoQJA3DSXcFGSKnBM3KuY8D+Wh++QN6ASfMb2hTNjbsE+0gl9RXq # IHRIvejPXPog316e6qPpGAGRPQ4Ls0o90trmcKI4TIoLelHqv9BTu7buLMoNVwG/ # 5hfZiDCZTNM3sygHb4cZASsIixvQZQ5YHyolkn2FGSa3ijCJmrLIBUUQIGkkYX36 # wpmMtSojmw3EFPpiuzMjihae/sw0r0vqlOpQuGAhO/z6QWwq6K+22N4EATBXPsMw # 6pmYlJSk2lHjNQbl2mR221qqSINa66mfvwVzcxWRZM+xMYIEDTCCBAkCAQEwgZMw # fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd # TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIYJdmSBeLn5eQAAQAA # AhgwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB # BDAvBgkqhkiG9w0BCQQxIgQgM7d4fCpBXffxPqZ+Y3jviJJ8YW4Pv83XMyEP3Pkf # KyUwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCCZE9yJuOTItIwWaES6lzGK # K1XcSoz1ynRzaOVzx9eFajCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD # QSAyMDEwAhMzAAACGCXZkgXi5+XkAAEAAAIYMCIEIEXk/ZWvKdqycOBVFm0EPjNU # YNmhOW4jZMZN4S7ow+hIMA0GCSqGSIb3DQEBCwUABIICABaE+YqsIETcR6sLx9qA # zfktHJJZ+G3aCY5ZztledeklswMC3yGETOVAy4D+X6E7T06RKveXrp+OzV37ZFWm # 2y1PhDcxgcRCEcguNrQwWUXiOKk+QitZ8B1eltPzBKkEfySdovVipIUncMkrz393 # TTCeSDOsdpZvdm78q29MIHgh/xZeWI58JQ3RqwbTXbB6zFRVFBNySe954MfQskRJ # CKo1q5afuuqTVoWQ2P671iVu8A8nufioX++LjYMohuf852s9tCCZ4AOsVG30aqEB # IhalrQc9Ycr1tMCEecQE6QjHqzz+r5cst/z+N47XrlCj3Dq08gX5x/aTLZDQRLiE # I/gD1P5nWAKdeIC95cbbWYUwrfVCEUp94iaGcLGEmVELk4ISRLwJzaiH3h1DbrpA # HkidfQYILqU/YEImQDygmAVExJ1Fcurx+937A7NxuSmtJKOspWmuB4Ggt7173tpE # sJ1WPebWxDIBRggJISZ6QM2Vg1lT4R+BHdzE7c9lbOG2ceW+x00ayH/t3vScXJI0 # 2PdpJzXkcqhmUH6pBNfa1qeSuINloHNjVeaXYNWPd1FBbtp9WbOo99icT02GGQL7 # RzFVLI7NdHyoP6/jA7f9hUBFaKJF0iE/BlIS01ZAo+JOXHFGcYsroypFpQUjGw47 # TUbrPnkOeCb89AgJ5Ns63aeK # SIG # End signature block |