modules/ValidateMigration/ValidateMigration.psm1

Import-Module ((Split-Path $PSScriptRoot -Parent) + "/Log/Log.psd1")
Import-Module ((Split-Path $PSScriptRoot -Parent) + "/NsgCreation/NsgCreation.psd1")
Import-Module ((Split-Path $PSScriptRoot -Parent) + "/ValidateScenario/ValidateScenario.psd1")
Import-Module ((Split-Path $PSScriptRoot -Parent) + "/GetVmssFromBasicLoadBalancer/GetVmssFromBasicLoadBalancer.psd1")

Function ValidateMigration {
    param(
        [Parameter(Mandatory = $True)][Microsoft.Azure.Commands.Network.Models.PSLoadBalancer] $BasicLoadBalancer,
        [Parameter(Mandatory = $false)][string] $StandardLoadBalancerName,
        [Parameter(Mandatory = $false)][switch] $OutputMigrationValiationObj
    )

    log -Message "[ValidateMigration] Initiating Validation of Migration for basic LB '$($BasicLoadBalancer.Name)' to standard LB '$($standardLoadBalancerName)')'"

    $validationResult = @{
        "migrationSuccessful" = $false
        "failedValidations"   = @()
        "passedValidations"   = @()
        "warnValidations"     = @()
    }

    $scenario = New-Object -TypeName psobject -Property @{
        'ExternalOrInternal'              = ''
        'BackendType'                     = ''
        'VMsHavePublicIPs'                = $false
        'VMSSInstancesHavePublicIPs'      = $false
        'SkipOutboundRuleCreationMultiBE' = $false
    }

    If ([string]::IsNullOrEmpty($StandardLoadBalancerName)) {
        log -Message "[ValidateMigration] Standard Load Balancer name not specified, assuming reusing Basic Load Balancer name '$($BasicLoadBalancer.Name)'"
        $StandardLoadBalancerName = $BasicLoadBalancer.Name
    }
    
    # detecting if source load balancer is internal or external-facing
    If (![string]::IsNullOrEmpty($BasicLoadBalancer.FrontendIpConfigurations[0].PrivateIpAddress)) {
        $scenario.ExternalOrInternal = 'Internal'
    }
    ElseIf (![string]::IsNullOrEmpty($BasicLoadBalancer.FrontendIpConfigurations[0].PublicIpAddress)) {
        $scenario.ExternalOrInternal = 'External'
    }

    # getting backend type
    $scenario.backendType = _GetScenarioBackendType -BasicLoadBalancer $BasicLoadBalancer -skipLogging
    log -Message "[ValidateMigration] Backend type: $($scenario.backendType)"

    # validate the standard load balancer exists and is standard SKU
    $standardLoadBalancer = Get-AzLoadBalancer -Name $StandardLoadBalancerName -ResourceGroupName $BasicLoadBalancer.ResourceGroupName -ErrorAction SilentlyContinue

    If ($null -eq $standardLoadBalancer) {
        log -Message "[ValidateMigration] Standard Load Balancer does not exist" -Severity Error
        $validationResult.failedValidations += "Standard Load Balancer does not exist"
    }
    Else {
        log -Message "[ValidateMigration] Standard Load Balancer exists" -Severity Information
        $validationResult.passedValidations += "Standard Load Balancer exists"
    }

    If ($standardLoadBalancer.Sku.Name -ne "Standard") {
        log -Message "[ValidateMigration] Standard Load Balancer is not Standard SKU" -Severity Error
        $validationResult.failedValidations += "Standard Load Balancer is not Standard SKU"
    }
    Else {
        log -Message "[ValidateMigration] Standard Load Balancer is Standard SKU" -Severity Information
        $validationResult.passedValidations += "Standard Load Balancer is Standard SKU"
    }

    # validate the standard load balancer has the same number of frontend IPs as the basic load balancer
    If ($standardLoadBalancer.FrontendIPConfigurations.Count -ne $BasicLoadBalancer.FrontendIPConfigurations.Count) {
        log -Message "[ValidateMigration] Standard Load Balancer does not have the same number of frontend IPs ('$($standardLoadBalancer.FrontendIPConfigurations.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.FrontendIPConfigurations.Count)')" -Severity Error
        $validationResult.failedValidations += "Standard Load Balancer does not have the same number of frontend IPs ('$($standardLoadBalancer.FrontendIPConfigurations.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.FrontendIPConfigurations.Count)')"
    }
    Else {
        log -Message "[ValidateMigration] Standard Load Balancer has the same number of frontend IPs ('$($standardLoadBalancer.FrontendIPConfigurations.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.FrontendIPConfigurations.Count)')"
        $validationResult.passedValidations += "Standard Load Balancer has the same number of frontend IPs ('$($standardLoadBalancer.FrontendIPConfigurations.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.FrontendIPConfigurations.Count)')"
    }

    # validate the standard load balancer has the same number of backend pools as the basic load balancer
    If ($standardLoadBalancer.BackendAddressPools.Count -ne $BasicLoadBalancer.BackendAddressPools.Count) {
        log -Message "[ValidateMigration] Standard Load Balancer does not have the same number of backend pools ('$($standardLoadBalancer.BackendAddressPools.Count)') as the Basic Load Balancer ('$($BasicLoadBalancer.BackendAddressPools.Count)')" -Severity Error
        $validationResult.failedValidations += "Standard Load Balancer does not have the same number of backend pools ('$($standardLoadBalancer.BackendAddressPools.Count)') as the Basic Load Balancer ('$($BasicLoadBalancer.BackendAddressPools.Count)')"
    }
    Else {
        log -Message "[ValidateMigration] Standard Load Balancer has the same number of backend pools ('$($standardLoadBalancer.BackendAddressPools.Count)') as the Basic Load Balancer ('$($BasicLoadBalancer.BackendAddressPools.Count)')" -Severity Information
        $validationResult.passedValidations += "Standard Load Balancer has the same number of backend pools ('$($standardLoadBalancer.BackendAddressPools.Count)') as the Basic Load Balancer ('$($BasicLoadBalancer.BackendAddressPools.Count)')"
    }

    # validate the standard load balancer has the same number of load balancing rules as the basic load balancer
    If ($standardLoadBalancer.LoadBalancingRules.Count -ne $BasicLoadBalancer.LoadBalancingRules.Count) {
        log -Message "[ValidateMigration] Standard Load Balancer does not have the same number of load balancing rules ('$($standardLoadBalancer.LoadBalancingRules.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.LoadBalancingRules.Count)')" -Severity Error
        $validationResult.failedValidations += "Standard Load Balancer does not have the same number of load balancing rules ('$($standardLoadBalancer.LoadBalancingRules.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.LoadBalancingRules.Count)')"
    }
    Else {
        log -Message "[ValidateMigration] Standard Load Balancer has the same number of load balancing rules ('$($standardLoadBalancer.LoadBalancingRules.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.LoadBalancingRules.Count)')" -Severity Information
        $validationResult.passedValidations += "Standard Load Balancer has the same number of load balancing rules ('$($standardLoadBalancer.LoadBalancingRules.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.LoadBalancingRules.Count)')"
    }

    # validate the standard load balancer has the same number of probes as the basic load balancer
    If ($standardLoadBalancer.Probes.Count -ne $BasicLoadBalancer.Probes.Count) {
        log -Message "[ValidateMigration] Standard Load Balancer does not have the same number of health probes ('$($standardLoadBalancer.Probes.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.Probes.Count)')" -Severity Error
        $validationResult.failedValidations += "Standard Load Balancer does not have the same number of health probes ('$($standardLoadBalancer.Probes.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.Probes.Count)')"
    }
    Else {
        log -Message "[ValidateMigration] Standard Load Balancer has the same number of health probes ('$($standardLoadBalancer.Probes.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.Probes.Count)')" -Severity Information
        $validationResult.passedValidations += "Standard Load Balancer has the same number of health probes ('$($standardLoadBalancer.Probes.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.Probes.Count)')"
    }

    # validate the standard load balancer has the same number of inbound NAT rules as the basic load balancer
    If ($standardLoadBalancer.InboundNatRules.Count -ne $BasicLoadBalancer.InboundNatRules.Count) {
        log -Message "[ValidateMigration] Standard Load Balancer does not have the same number of inbound NAT rules ('$($standardLoadBalancer.InboundNatRules.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.InboundNatRules.Count)') " -Severity Error
        $validationResult.failedValidations += "Standard Load Balancer does not have the same number of inbound NAT rules ('$($standardLoadBalancer.InboundNatRules.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.InboundNatRules.Count)')"
    }
    Else {
        log -Message "[ValidateMigration] Standard Load Balancer has the same number of inbound NAT rules ('$($standardLoadBalancer.InboundNatRules.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.InboundNatRules.Count)') " -Severity Information
        $validationResult.passedValidations += "Standard Load Balancer has the same number of inbound NAT rules ('$($standardLoadBalancer.InboundNatRules.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.InboundNatRules.Count)')"
    }

    # validate the standard load balancer has the same number of inbound NAT pools as the basic load balancer
    If ($standardLoadBalancer.InboundNatPools.Count -ne $BasicLoadBalancer.InboundNatPools.Count) {
        log -Message "[ValidateMigration] Standard Load Balancer does not have the same number of inbound NAT pools ('$($standardLoadBalancer.InboundNatPools.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.InboundNatPools.Count)')" -Severity Error
        $validationResult.failedValidations += "Standard Load Balancer does not have the same number of inbound NAT pools ('$($standardLoadBalancer.InboundNatPools.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.InboundNatPools.Count)')"
    }
    Else {
        log -Message "[ValidateMigration] Standard Load Balancer has the same number of inbound NAT pools ('$($standardLoadBalancer.InboundNatPools.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.InboundNatPools.Count)')" -Severity Information
        $validationResult.passedValidations += "Standard Load Balancer has the same number of inbound NAT pools ('$($standardLoadBalancer.InboundNatPools.Count)') as the Basic Load Balancer ('$($basicLoadBalancer.InboundNatPools.Count)')"
    }

    # validate the standard load balancer has outbound rules
    If ($standardLoadBalancer.OutboundRules.Count -eq 0 -and $scenario.ExternalOrInternal -eq "External") {
        log -Message "[ValidateMigration] Standard Load Balancer does not have outbound rules" -Severity Warning
        $validationResult.warnValidations += "Standard Load Balancer does not have outbound rules"
    }
    ElseIf ($scenario.ExternalOrInternal -ne "Internal") {
        log -Message "[ValidateMigration] Standard Load Balancer has outbound rules ('$($standardLoadBalancer.OutboundRules.Count)')" -Severity Information
        $validationResult.passedValidations += "Standard Load Balancer has outbound rules ('$($standardLoadBalancer.OutboundRules.Count)')"
    }

    # validate the standard load balancer frontend IP addresses are the same as the basic load balancer frontend IP addresses
    If ($scenario.ExternalOrInternal -eq 'Internal') {
        $basicLoadBalancerFrontendIPs = $BasicLoadBalancer.FrontendIPConfigurations.properties.privateIPAddress
        $standardLoadBalancerFrontendIPs = $standardLoadBalancer.FrontendIPConfigurations.properties.privateIPAddress

        ForEach ($basicLoadBalancerFrontendIP in $basicLoadBalancerFrontendIPs) {
            If ($standardLoadBalancerFrontendIPs -notcontains $basicLoadBalancerFrontendIP) {
                log -Message "[ValidateMigration] Standard Load Balancer is missing Basic Load Balancer private IP address '$basicLoadBalancerFrontendIP'" -Severity Error
                $validationResult.failedValidations += "Standard Load Balancer is missing Basic Load Balancer private IP address '$basicLoadBalancerFrontendIP'"
            }
            Else {
                log -Message "[ValidateMigration] Standard Load Balancer has Basic Load Balancer private IP address '$basicLoadBalancerFrontendIP'" -Severity Information
                $validationResult.passedValidations += "Standard Load Balancer has Basic Load Balancer private IP address '$basicLoadBalancerFrontendIP'"
            }
        }
    }
    ElseIf ($scenario.ExternalOrInternal -eq 'External') {
        $basicLoadBalancerFrontendIPs = $BasicLoadBalancer.FrontendIPConfigurations.publicIPAddress.id
        $standardLoadBalancerFrontendIPs = $standardLoadBalancer.FrontendIPConfigurations.publicIPAddress.id

        ForEach ($basicLoadBalancerFrontendIP in $basicLoadBalancerFrontendIPs) {
            If ($standardLoadBalancerFrontendIPs -notcontains $basicLoadBalancerFrontendIP) {
                log -Message "[ValidateMigration] External Standard Load Balancer is missing Basic Load Balancer public IP address '$basicLoadBalancerFrontendIP'" -Severity Error
                $validationResult.failedValidations += "External Standard Load Balancer is missing Basic Load Balancer public IP address '$basicLoadBalancerFrontendIP'"
            }
            Else {
                log -Message "[ValidateMigration] External Standard Load Balancer has Basic Load Balancer public IP address '$basicLoadBalancerFrontendIP'" -Severity Information
                $validationResult.passedValidations += "External Standard Load Balancer has Basic Load Balancer public IP address '$basicLoadBalancerFrontendIP'"
            }
        }
    }

    # validate that the standard load balancer backend pool membership matches the basic load balancer backend pool membership
    $basicLoadBalancerBackendPools = $BasicLoadBalancer.BackendAddressPools
    $standardLoadBalancerBackendPools = $standardLoadBalancer.BackendAddressPools

    ForEach ($basicBackendAddressPool in $basicLoadBalancerBackendPools) {
        $matchedStdPool = $standardLoadBalancerBackendPools | Where-Object { $_.Name -eq $basicBackendAddressPool.Name }

        $differentMembership = Compare-Object $matchedStdPool.BackendIpConfigurations $basicBackendAddressPool.BackendIpConfigurations -Property Id

        If ($differentMembership) {
            ForEach ($membership in $differentMembership) {
                switch ($membership.sideIndicator) {
                    '<=' {
                        log -Message "[ValidateMigration] Standard Load Balancer pool '$($matchedStdPool.name)' has extra member '$($membership.Id)'" -Severity Error
                        $validationResult.failedValidations += "Standard Load Balancer pool '$($matchedStdPool.name)' is missing Basic Load Balancer backend pool membership '$($membership.Id)'"
                    }
                    '=>' {
                        log -Message "[ValidateMigration] Standard Load Balancer pool '$($matchedStdPool.name)' is missing member '$($membership.Id)'" -Severity Error
                        $validationResult.failedValidations += "Standard Load Balancer pool '$($matchedStdPool.name)' is missing Basic Load Balancer backend pool membership '$($membership.Id)'"
                    }
                }
            }
        }
        Else {
            log -Message "[ValidateMigration] Standard Load Balancer pool '$($matchedStdPool.name)' has the same membership as Basic Load Balancer pool '$($basicBackendAddressPool.name)'" -Severity Information
            $validationResult.passedValidations += "Standard Load Balancer pool '$($matchedStdPool.name)' has the same membership as Basic Load Balancer pool '$($basicBackendAddressPool.name)'"
        }
    }

    If ($validationResult.failedValidations.Count -eq 0) {
        $validationResult.migrationSuccessful = $true
    }
    ElseIf ($validationResult.failedValidations.Count -gt 0) {
        $validationResult.migrationSuccessful = $false
    }

    # validate backend members have NSGs which allow the same ports as the load balancing rules
    If ($scenario.ExternalOrInternal -eq 'External') {
        switch ($scenario.BackendType) {
            'VM' {
                $nicsNeedingNewNSG, $nsgIDsToUpdate = _GetVMNSG -BasicLoadBalancer $BasicLoadBalancer -skipLogging

                If ($nicsNeedingNewNSG.count -gt 0) {
                    # _GetVMNSG querys Azure Resource Graph, which may be delayed in returning results. Double-checking against network RP. Latency should improve in new ARG releases, so this may not be necessary in the future.
                    ForEach ($nicId in $nicsNeedingNewNSG) {
                        $nic = Get-AzNetworkInterface -ResourceId $nicId

                        # check for NSG on NIC
                        If ([string]::IsNullOrEmpty($nic.NetworkSecurityGroup)) {
                            $subnetConfig = Get-AzVirtualNetworkSubnetConfig -ResourceId $nic.IpConfigurations.Subnet.Id

                            # if there is no NSG on NIC, check subnet
                            If ([string]::IsNullOrEmpty($subnetConfig.NetworkSecurityGroup)) {
                                log -Message "[ValidateMigration] The following VM NICs need new NSGs: $($nicsNeedingNewNSG)" -Severity Error
                                $validationResult.failedValidations += "The following VM NICs need new NSGs: $($nicsNeedingNewNSG)"
                            }
                        }
                    }
                }
                Else {
                    log -Message "[ValidateMigration] All VM NICs have NSGs" -Severity Information
                    $validationResult.passedValidations += "All VM NICs have NSGs"
                }
            }
            'VMSS' {
                $vmssIDs = $BasicLoadBalancer.BackendAddressPools.BackendIpConfigurations.id | Foreach-Object { ($_ -split '/virtualMachines/')[0].ToLower() } | Select-Object -Unique  

                ForEach ($vmssId in $vmssIds) {
                    $vmss = Get-AzResource -ResourceId $vmssId | Get-AzVmss

                    $vmssHasNSG = _GetVMSSNSG -vmss $vmss -skipLogging

                    If (!$vmssHasNSG) {
                        log -Message "[ValidateMigration] VMSS '$($vmss.Name)' does not have an NSG" -Severity Error
                        $validationResult.failedValidations += "VMSS '$($vmss.Name)' does not have an NSG"
                    }
                    Else {
                        log -Message "[ValidateMigration] VMSS '$($vmss.Name)' has an NSG" -Severity Information
                        $validationResult.passedValidations += "VMSS '$($vmss.Name)' has an NSG"
                    }
                }
            }
        }
    }
    Else {
        log -Message "[ValidateMigration] Skipping NSG validation for internal load balancer" -Severity Information
        $validationResult.passedValidations += "Skipped NSG validation for internal load balancer"
    }

    # return object with validation status - useful for testing and scale migrations
    If ($OutputMigrationValiationObj) {
        return $validationResult
    }
}