modules/Start-AzBasicLoadBalancerUpgrade/Start-AzBasicLoadBalancerUpgrade.psm1

# Load Modules
Import-Module ((Split-Path $PSScriptRoot -Parent) + "\Log\Log.psd1")
Import-Module ((Split-Path $PSScriptRoot -Parent) + "\ScenariosMigration\ScenariosMigration.psd1")
Import-Module ((Split-Path $PSScriptRoot -Parent) + "\ValidateScenario\ValidateScenario.psd1")
Import-Module ((Split-Path $PSScriptRoot -Parent) + "\ValidateMigration\ValidateMigration.psd1")
Import-Module ((Split-Path $PSScriptRoot -Parent) + "\BackupResources\BackupResources.psd1")

<#
.SYNOPSIS
    This module will migrate a Basic SKU load balancer connected to a Virtual Machine Scaleset (VMSS) or Virtual Machine(s) to a Standard SKU load balancer, preserving the existing configuration and functionality.
 
.DESCRIPTION
    This module consists of a number of child modules which abstract the operations required to successfully migrate a Basic to a Standard load balancer.
    A Basic Load Balancer cannot be natively migrate to a Standard SKU, therefore this module creates a new Standard laod balancer based on the configuration of the existing Basic load balancer.
 
    Unsupported scenarios:
    - Basic load balancers with backend pool members which are not VMs or a VMSS
    - Basic load balancers with IPV6 frontend IP configurations
    - Basic load balancers with a VMSS backend pool member where one or more VMSS instances have ProtectFromScaleSetActions Instance Protection policies enabled
    - Migrating a Basic load balancer to an existing Standard load balancer
 
    Multi-load balancer support:
    In a situation where multiple Basic load balancers are configured with the same backend pool members (internal and external load balancers), the migration can be performed in a single operation by specifying the
    -MultiLBConfig parameter. This option deletes all specified basic load balancers before starting the migration, then creates new standard load balancers mirroring the basic load balancer configurations.
 
    Recovering from a failed migration:
    The module takes a backup of the basic load balancer configuration, which can be used to retry a failed migration. The backup files are stored in the directory where the script is executed, or in the directory
    specified with the -RecoveryBackupPath parameter.
 
    In a multi-load balancer migration, recovery in performed on a per-load balancer basis--attempt to retry the migration of each load balancer individually.
 
.PARAMETER ResourceGroupName
    Resource group containing the Basic Load Balancer to migrate. The new Standard load balancer will be created in this resource group.
 
.PARAMETER BasicLoadBalancerName
    Name of the existing Basic Load Balancer to migrate
 
.PARAMETER BasicLoadBalancer
    Load Balancer object to migrate passed as pipeline input or parameter
 
.PARAMETER basicLoadBalancerStatePath
Use in combination with -validateCompletedMigration to validate a completed migration
 
.PARAMETER FailedMigrationRetryFilePathLB
    Location of a Basic load balancer backup file (used when retrying a failed migration or manual configuration comparison)
 
.PARAMETER FailedMigrationRetryFilePathVMSS
    Location of a VMSS backup file (used when retrying a failed migration or manual configuration comparison)
 
.PARAMETER outputMigrationValiationObj
    Switch parameter to output the migration validation object to the console - useful for large scale and pipeline migrations
 
.PARAMETER StandardLoadBalancerName
    Name of the new Standard Load Balancer. If not specified, the name of the Basic load balancer will be reused.
 
.PARAMETER skipUpgradeNATPoolsToNATRules
    If specified, the migration will skip upgrading NAT Pools to NAT Rules. NAT Rules are more managable and functional than NAT Pools, while providing the same functionality
 
.PARAMETER MultiLBConfig
    Array of objects containing the basic load balancer and standard load balancer name to migrate. Use this parameter to migrate multiple load balancers with shared backend pool members. Optionally, specify a new standard load balancer name for each basic load balancers.
    Example value: $multiLBConfig = @(
        @{
            'standardLoadBalancerName' = 'myStandardLB01'
            'basicLoadBalancer' = (Get-AzLoadBalancer -ResourceGroupName myRG -Name myBasicLB01)
        },
            @{
            'standardLoadBalancerName' = 'myStandardLB02'
            'basicLoadBalancer' = (Get-AzLoadBalancer -ResourceGroupName myRG -Name myBasicLB02)
        }
    )
 
.PARAMETER RecoveryBackupPath
    Location of the Recovery backup files
 
.PARAMETER FollowLog
    Switch parameter to enable the display of logs in the console
 
.PARAMETER validateScenarioOnly
    Only perform the validation portion of the migration, then exit the script without making changes
 
.PARAMETER validateCompletedMigration
    Using the exported Basic Load Balancer state file, validate the migration was completed successfully
 
.OUTPUTS
    This module outputs the following files on execution:
    - Start-AzBasicLoadBalancerUpgrade.log: in the directory where the script is executed, this file contains a log of the migration operation. Refer to it for error details in a failed migration.
    - 'ARMTemplate_<basicLBName>_<basicLBRGName>_<timestamp>.json: either in the directory where the script is executed or the path specified with -RecoveryBackupPath. This is an ARM template for the basic LB, for reference only.
    - 'State_<basicLBName>_<basicLBRGName>_<timestamp>.json: either in the directory where the script is executed or the path specified with -RecoveryBackupPath. This is a state backup of the basic LB, used in retry scenarios.
    - 'State_VMSS_<vmssName>_<vmssRGName>_<timestamp>.json: either in the directory where the script is executed or the path specified with -RecoveryBackupPath. This is a state backup of the VMSS, used in retry scenarios.
 
.EXAMPLE
    # Basic usage
    PS C:\> Start-AzBasicLoadBalancerUpgrade -ResourceGroupName myRG -BasicLoadBalancerName myBasicLB
 
.EXAMPLE
    # Pass LoadBalancer via pipeline input
    PS C:\> Get-AzLoadBalancer -ResourceGroupName myRG -Name myBasicLB | Start-AzBasicLoadBalancerUpgrade -StandardLoadBalancerName myStandardLB
 
.EXAMPLE
    # Pass LoadBalancer via pipeline input and re-use the existing Load Balancer Name
    PS C:\> Get-AzLoadBalancer -ResourceGroupName myRG -Name myBasicLB | Start-AzBasicLoadBalancerUpgrade
 
.EXAMPLE
    # Pass LoadBalancer object using -BasicLoadBalancer parameter input and re-use the existing Load Balancer Name
    PS C:\> $basicLB = Get-AzLoadBalancer -ResourceGroupName myRG -Name myBasicLB
    PS C:\> Start-AzBasicLoadBalancerUpgrade -BasicLoadBalancer $basicLB
 
.EXAMPLE
    # Specify a custom path for recovery backup files
    PS C:\> Start-AzBasicLoadBalancerUpgrade -ResourceGroupName myRG -BasicLoadBalancerName myBasicLB -RecoveryBackupPath C:\RecoveryBackups
 
.EXAMPLE
    # migrate multiple load balancers with shared backend pool members
    PS C:\> $multiLBConfig = @(
        @{
            'basicLoadBalancer' = (Get-AzLoadBalancer -ResourceGroupName myRG -Name myBasicLB01)
            'standardLoadBalancerName' = 'myStandardLB01' # optional new standard load balancer name
        },
            @{
            'basicLoadBalancer' = (Get-AzLoadBalancer -ResourceGroupName myRG -Name myBasicLB02)
            'standardLoadBalancerName' = 'myStandardLB02'
        }
    )
    PS C:\> Start-AzBasicLoadBalancerUpgrade -MultiLBConfig $multiLBConfig
 
.EXAMPLE
    # Retry a failed VMSS migration
    PS C:\> Start-AzBasicLoadBalancerUpgrade -FailedMigrationRetryFilePathLB C:\RecoveryBackups\State_mybasiclb_rg-basiclbrg_20220912T1740032148.json -FailedMigrationRetryFilePathVMSS C:\RecoveryBackups\VMSS_myVMSS_rg-basiclbrg_20220912T1740032148.json
 
.EXAMPLE
    # Retry a failed VM migration
    PS C:\> Start-AzBasicLoadBalancerUpgrade -FailedMigrationRetryFilePathLB C:\RecoveryBackups\State_mybasiclb_rg-basiclbrg_20220912T1740032148.json
 
.EXAMPLE
    # display logs in the console as the command executes
    PS C:\> Start-AzBasicLoadBalancerUpgrade -ResourceGroupName myRG -BasicLoadBalancerName myBasicLB -FollowLog
 
.EXAMPLE
    # validate a completed migration using the exported Basic Load Balancer state file. Add -StandardLoadBalancerName to validate against a Standard Load Balancer with a different name than the Basic Load Balancer
    PS C:\> Start-AzBasicLoadBalancerUpgrade -validateCompletedMigration -basicLoadBalancerStatePath C:\RecoveryBackups\State_mybasiclb_rg-basiclbrg_20220912T1740032148.json
 
.LINK
    https://github.com/Azure/AzLoadBalancerMigration/tree/main/AzureBasicLoadBalancerUpgrade
 
.LINK
    https://learn.microsoft.com/azure/load-balancer/upgrade-basic-standard-with-powershell
#>

function Start-AzBasicLoadBalancerUpgrade {
    [CmdletBinding(DefaultParameterSetName = 'ByName')]
    Param(
        [Parameter(Mandatory = $True, ParameterSetName = 'ByName')][string] $ResourceGroupName, 
        [Parameter(Mandatory = $True, ParameterSetName = 'ByName')][string] $BasicLoadBalancerName,
        [Parameter(Mandatory = $True, ValueFromPipeline, ParameterSetName = 'ByObject')][Microsoft.Azure.Commands.Network.Models.PSLoadBalancer] $BasicLoadBalancer,
        [Parameter(Mandatory = $True, ParameterSetName = 'ByJsonVm')][string] 
        [Parameter(Mandatory = $True, ParameterSetName = 'ByJsonVmss')][string]
        $FailedMigrationRetryFilePathLB,
        [Parameter(Mandatory = $True, ParameterSetName = 'ByJsonVmss')][string] $FailedMigrationRetryFilePathVMSS,
        [Parameter(Mandatory = $false, ParameterSetName = 'ValidateCompletedMigration')][string]
        [Parameter(Mandatory = $false, ParameterSetName = 'ByName')][string]
        [Parameter(Mandatory = $false, ParameterSetName = 'ByObject')][string]
        [Parameter(Mandatory = $false, ParameterSetName = 'ByJsonVm')][string] 
        [Parameter(Mandatory = $false, ParameterSetName = 'ByJsonVmss')][string] 
        $StandardLoadBalancerName,
        [Parameter(Mandatory = $false)][string] $RecoveryBackupPath = $pwd,
        [Parameter(Mandatory = $false)][switch] $FollowLog,
        [Parameter(Mandatory = $false)][switch] $validateScenarioOnly,
        [Parameter(Mandatory = $True, ParameterSetName = 'MultiLB')][psobject[]] $multiLBConfig, # @(@{basicLoadBalancer=<[Microsoft.Azure.Commands.Network.Models.PSLoadBalancer]>[;standardLoadBalancerName='lb-standard-01']})
        [Parameter(Mandatory = $false, ParameterSetName = 'ByName')][switch]
        [Parameter(Mandatory = $false, ParameterSetName = 'ByObject')][switch]
        [Parameter(Mandatory = $false, ParameterSetName = 'ByJsonVmss')][switch] 
        [Parameter(Mandatory = $false, ParameterSetName = 'MultiLB')][switch] 
        [Parameter(Mandatory = $false, ParameterSetName = 'ValidateCompletedMigration')][switch]
        $skipUpgradeNATPoolsToNATRules,
        [Parameter(Mandatory = $true, ParameterSetName = 'ValidateCompletedMigration')][switch] $validateCompletedMigration,
        [Parameter(Mandatory = $true, ParameterSetName = 'ValidateCompletedMigration')][string] $basicLoadBalancerStatePath,
        [Parameter(Mandatory = $false)][switch] $outputMigrationValiationObj,
        [Parameter(Mandatory = $false)][int32] $defaultJobWaitTimeout = (New-Timespan -Minutes 10).TotalSeconds,
        [Parameter(Mandatory = $false)][switch] $force,
        [Parameter(Mandatory = $false)][switch] $Pre
    )

    # Set global variable to display log output in console
    If ($FollowLog.IsPresent) {
        $global:FollowLog = $true
    }

    # Default to -FollowLogs if running in Cloud Shell to avoid timeouts
    If ($env:POWERSHELL_DISTRIBUTION_CHANNEL -eq 'CloudShell') {
        $global:FollowLog = $true
    }

    # Set global variable for default job wait timoue
    $global:defaultJobWaitTimeout = $defaultJobWaitTimeout

    # validate backup path is directory
    If (!(Test-Path -Path $RecoveryBackupPath -PathType Container )) {
        Write-Error "The path '$recoveryBackupPath' specified with parameter recoveryBackupPath must exist and be a valid directory." -terminateOnError
    }

    log -Message "############################## Initializing Start-AzBasicLoadBalancerUpgrade ##############################"

    log -Message "[Start-AzBasicLoadBalancerUpgrade] PowerShell Version: $($PSVersionTable.PSVersion.ToString())"
    log -Message "[Start-AzBasicLoadBalancerUpgrade] AzureBasicLoadBalancerUpgrade Version: $((Get-Module -Name AzureBasicLoadBalancerUpgrade).Version.ToString())"

    log -Message "[Start-AzBasicLoadBalancerUpgrade] Checking that user is signed in to Azure PowerShell"
    if (!($azContext = Get-AzContext -ErrorAction SilentlyContinue)) {
        log -Severity 'Error' -Message "Sign into Azure Powershell with 'Connect-AzAccount' before running this script!"
        return
    }
    log -Message "[Start-AzBasicLoadBalancerUpgrade] User is signed in to Azure with account '$($azContext.Account.Id)', subscription '$($azContext.Subscription.Name)' selected"

    ### validate a completed migration ###
    if ($validateCompletedMigration) {
        log -Message "[Start-AzBasicLoadBalancerUpgrade] Validating completed migration using basic LB state file '$basicLoadBalancerStatePath' and standard load balancer name '$StandardLoadBalancerName'"

        # import basic LB from file
        $BasicLoadBalancer = RestoreLoadBalancer -BasicLoadBalancerJsonFile $basicLoadBalancerStatePath

        ValidateMigration -BasicLoadBalancer $BasicLoadBalancer -StandardLoadBalancerName $StandardLoadBalancerName -OutputMigrationValiationObj:$($OutputMigrationValiationObj.IsPresent) -natPoolsMigratedToNatRules:(!$skipUpgradeNATPoolsToNATRules)

        return
    }

    ### initiate a new or recovery migration ###
    # Load Azure Resources
    Write-Progress -Activity "Loading Azure Resources" -Status "Loading Azure Resources" -Id 1
    log -Message "[Start-AzBasicLoadBalancerUpgrade] Loading Azure Resources"

    try {
        $ErrorActionPreference = 'Stop'
        if (!$PSBoundParameters.ContainsKey("BasicLoadBalancer") -and (!$PSBoundParameters.ContainsKey("FailedMigrationRetryFilePathLB")) -and ($PSCmdlet.ParameterSetName -ne 'MultiLB')) {
            $BasicLoadBalancer = Get-AzLoadBalancer -ResourceGroupName $ResourceGroupName -Name $BasicLoadBalancerName
        }
        elseif (!$PSBoundParameters.ContainsKey("BasicLoadBalancer") -and ($PSBoundParameters.ContainsKey("FailedMigrationRetryFilePathVMSS"))) {
            
            # recover VMSS migration from backup state files
            $BasicLoadBalancer = RestoreLoadBalancer -BasicLoadBalancerJsonFile $FailedMigrationRetryFilePathLB
            $vmss = RestoreVmss -VMSSJsonFile $FailedMigrationRetryFilePathVMSS
        }
        elseIf (($PSBoundParameters.ContainsKey("FailedMigrationRetryFilePathLB") -and (!$PSBoundParameters.ContainsKey("FailedMigrationRetryFilePathVMSS")))) {
            
            #recovery VM migration from backup state file
            $BasicLoadBalancer = RestoreLoadBalancer -BasicLoadBalancerJsonFile $FailedMigrationRetryFilePathLB
        }

        If ($PSCmdlet.ParameterSetName -ne 'MultiLB') {
            log -Message "[Start-AzBasicLoadBalancerUpgrade] Basic Load Balancer '$($BasicLoadBalancer.Name)' in Resource Group '$($basicLoadBalancer.ResourceGroupName)' loaded"
        }
    }
    catch {
        $message = @"
            [Start-AzBasicLoadBalancerUpgrade] Failed to find basic load balancer '$BasicLoadBalancerName' in resource group '$ResourceGroupName' under subscription
            '$((Get-AzContext).Subscription.Name)'. Ensure that the correct subscription is selected and verify the load balancer and resource group names.
            Error text: $_
"@

        log -severity Error -message $message -terminateOnError
    }

    Write-Progress -Activity "Loading Azure Resources" -Status "Loading Azure Resources" -Completed -Id 1

    # verify basic load balancer configuration is a supported scenario

    ## verify scenario for a single load balancer
    Write-Progress -Activity "Validating Migration Scenario" -Status "Validating Migration Scenario" -PercentComplete 0 -Id 2
    If ($PSCmdlet.ParameterSetName -ne 'MultiLB') {
        if ($PSBoundParameters.ContainsKey("StandardLoadBalancerName")) {
            $StdLoadBalancerName = $StandardLoadBalancerName
        }
        else {
            $StdLoadBalancerName = $BasicLoadBalancer.Name
        }

        $scenario = Test-SupportedMigrationScenario -BasicLoadBalancer $BasicLoadBalancer -StdLoadBalancer $StdLoadBalancerName -Force:($force.IsPresent -or $validateScenarioOnly.isPresent) -Pre:$Pre.IsPresent -basicLBBackendIds $BasicLoadBalancer.BackendAddressPools.Id

        # create a migration config object array with a single entry
        $migrationConfigs = @(@{
                BasicLoadBalancer        = $BasicLoadBalancer
                StandardLoadBalancerName = $StdLoadBalancerName
                scenario                 = $scenario
                vmssRefObject            = ''
            })
        
        if ($validateScenarioOnly) {
            log -Message "[Start-AzBasicLoadBalancerUpgrade] Scenario validation completed, exiting because -validateScenarioOnly was specified"
            break
        }
    }
    ## verify scenario for multiple load balancers
    ElseIf ($PSCmdlet.ParameterSetName -eq 'MultiLB') {
        log -Message "[Start-AzBasicLoadBalancerUpgrade] -MultiLBConfig parameter set detected, validating scenarios for multiple load balancers"

        # verify the scenario for multi-load balancer configurations
        Test-SupportedMultiLBScenario -MultiLBConfig $multiLBConfig

        # verify scenario for each load balancer in the multiLBConfig array
        ForEach ($LBConfig in $multiLBConfig) {
            if (![string]::IsNullOrEmpty($LBConfig.standardLoadBalancerName)) {
                $StdLoadBalancerName = $LBConfig.standardLoadBalancerName
            }
            else {
                $LBConfig.standardLoadBalancerName = $BasicLoadBalancer.Name
                $StdLoadBalancerName = $BasicLoadBalancer.Name
            }

            $BasicLoadBalancer = $LBConfig.basicLoadBalancer

            log -Message "[Start-AzBasicLoadBalancerUpgrade] Validating scenario for Basic Load Balancer '$($BasicLoadBalancer.Name)' in Resource Group '$($basicLoadBalancer.ResourceGroupName)'"
            $scenario = Test-SupportedMigrationScenario -BasicLoadBalancer $BasicLoadBalancer -StdLoadBalancer $StdLoadBalancerName -Force:($force.IsPresent -or $validateScenarioOnly.isPresent) -Pre:$Pre.IsPresent -basicLBBackendIds $multiLBConfig.BasicLoadBalancer.BackendAddressPools.Id

            # add the evaluated scenario details to the LBConfig object
            $LBConfig['scenario'] = $scenario
        }
        
        if ($validateScenarioOnly) {
            log -Message "[Start-AzBasicLoadBalancerUpgrade] Scenario validation completed, exiting because -validateScenarioOnly was specified"
            break
        }

        # create a migration config object array from the input multiLBConfig parameter object array
        $migrationConfigs = $multiLBConfig
    }
    Write-Progress -Activity "Validating Migration Scenario" -Status "Validating Migration Scenario" -Completed -Id 2

    # prepare for migration by backing up the basic LB, upgrading Public IPs, and deleting the LB
    # this is done before the migration starts to ensure that all basic LBs are disassociated with any backend pool members, avoiding a mixed SKU scenario which would otherwise occur
    
    Write-Progress -Activity "Preparing for Migration" -Status "Preparing for Migration" -Id 3 -PercentComplete 0
    log -Message "[Start-AzBasicLoadBalancerUpgrade] Preparing for migration by backing up and deleteing the basic LB(s)"
    $migrationConfigs = LBMigrationPrep -migrationConfigs $migrationConfigs -RecoveryBackupPath $RecoveryBackupPath
    Write-Progress -Activity "Preparing for Migration" -Status "Preparing for Migration" -Completed -Id 3

    # initiate the migration of each load balancer in the migration config array
    Write-Progress -Activity "Starting Migration" -Status "Starting Migration" -Id 4
    $migrationConfigsCompleted = 0
    ForEach ($migrationConfig in $migrationConfigs) {
        Write-Progress -Activity "Starting Migration" -Status "Starting migration of basic load balancer '$($migrationConfig.BasicLoadBalancer.Name)'" -Id 4 -PercentComplete 0
        log -Message "[Start-AzBasicLoadBalancerUpgrade] Starting migration for Basic Load Balancer '$($migrationConfig.BasicLoadBalancer.Name)' in Resource Group '$($migrationConfig.BasicLoadBalancer.ResourceGroupName)'"

        $standardScenarioParams = @{
            BasicLoadBalancer             = $migrationConfig.BasicLoadBalancer
            StandardLoadBalancerName      = $migrationConfig.StandardLoadBalancerName
            Scenario                      = $migrationConfig.scenario
            outputMigrationValiationObj   = $outputMigrationValiationObj.IsPresent
            skipUpgradeNATPoolsToNATRules = $skipUpgradeNATPoolsToNATRules.IsPresent
        }

        switch ($migrationConfig.scenario.BackendType) {
            'VM' {
                switch ($migrationConfig.scenario.ExternalOrInternal) {
                    'internal' {
                        if ((!$PSBoundParameters.ContainsKey("FailedMigrationRetryFilePathLB"))) {
                            InternalLBMigrationVM @standardScenarioParams -RecoveryBackupPath $RecoveryBackupPath
                        }
                        else {
                            RestoreInternalLBMigrationVM @standardScenarioParams
                        }
                    }
                    'external' {
                        if ((!$PSBoundParameters.ContainsKey("FailedMigrationRetryFilePathLB"))) {
                            PublicLBMigrationVM @standardScenarioParams -RecoveryBackupPath $RecoveryBackupPath
                        }
                        else {
                            RestoreExternalLBMigrationVM @standardScenarioParams
                        }
                    }
                }
            }
            'VMSS' {
                switch ($migrationConfig.scenario.ExternalOrInternal) {
                    'internal' {
                        if ((!$PSBoundParameters.ContainsKey("FailedMigrationRetryFilePathLB"))) {
                            InternalLBMigrationVmss @standardScenarioParams -RecoveryBackupPath $RecoveryBackupPath -refVmss $migrationConfig.vmssRefObject
                        }
                        else {
                            RestoreInternalLBMigrationVmss @standardScenarioParams -vmss $vmss
                        }
                    }
                    'external' {
                        if ((!$PSBoundParameters.ContainsKey("FailedMigrationRetryFilePathLB"))) {
                            PublicLBMigrationVmss @standardScenarioParams -RecoveryBackupPath $RecoveryBackupPath -refVmss $migrationConfig.vmssRefObject
                        }
                        else {
                            RestoreExternalLBMigrationVmss @standardScenarioParams -vmss $vmss
                        }
                    }
                }
            }
            'Empty' {
                switch ($migrationConfig.scenario.ExternalOrInternal) {
                    'internal' {
                        if ((!$PSBoundParameters.ContainsKey("FailedMigrationRetryFilePathLB"))) {
                            InternalLBMigrationEmpty @standardScenarioParams -RecoveryBackupPath $RecoveryBackupPath
                        }
                        else {
                            RestoreInternalLBMigrationEmpty @standardScenarioParams
                        }
                    }
                    'external' {
                        if ((!$PSBoundParameters.ContainsKey("FailedMigrationRetryFilePathLB"))) {
                            PublicLBMigrationEmpty @standardScenarioParams -RecoveryBackupPath $RecoveryBackupPath
                        }
                        else {
                            RestoreExternalLBMigrationEmpty @standardScenarioParams
                        }
                    }
                }
            }
        }

        $migrationConfigsCompleted++
        $completedPercent = ($migrationConfigsCompleted / $migrationConfigs.Count) * 100
        Write-Progress -Activity "Completed migration" -Status "Completed migration of basic load balancer '$($migrationConfig.BasicLoadBalancer.Name)'" -PercentComplete $completedPercent -Id 4
    }

    log -Message "############################## Migration Completed ##############################"

    $global:FollowLog = $null
}

Export-ModuleMember -Function Start-AzBasicLoadBalancerUpgrade