VConnectMultiStampManagement.psm1

Invoke-InstallOrUpdateModule 'SqlServer'

function Get-VConnectAzsStampsHealth {
      [CmdletBinding()]
      param (
            [Parameter(Mandatory = $true, Position = 0, HelpMessage = "VConnect API connection name; Please use Add-ApiConnection to create new connecition")]
            [string] $endpointName,
            [Switch] $exportCsv,
            [Switch] $gridView,
            [Switch] $printToConsole,
            [Switch] $extendedOutput,
            [Switch] $showLogs
      )
      try {
            $result = Invoke-RestApiRequest -endpointName $endpointName -relativeUrl 'admin/AzureStackAdminConnections/Get' -method 'GET'

            if (($result.Length -eq 2) -and ($null -ne $result[1])) {

                  $azsConnectionsList = $result[1] | Select-Object -Property ConnectionId, ConnectionName, Location, ArmEndpointUrl, TenantArmEndpointUrl, DefaultProviderSubscriptionId, TenantId, ClientId, LoginEndpoint, UseCertificateThumbprintInsteadAppSecret, EnvironmentCategoryTag, EnvironmentSubCategoryTag, IsAdfs, IsDisconnectedEnvironment, IsAllowManagement, CreatedTime, LastUpdatedTime

                  foreach ($azsConnection in $azsConnectionsList) {
                        try {
                              $adminArmEndpoint = $azsConnection.ArmEndpointUrl
                              $requestUrl = "$($adminArmEndpoint)/metadata/endpoints?api-version=2015-01-11"
                              if ($showLogs) {
                                    Write-Host "$($azsConnection.ConnectionName): Validating ARM endpoint..."
                                    Invoke-WebRequest $requestUrl
                              } else {
                                    Invoke-WebRequest $requestUrl |  Out-Null
                              }
                              Add-Member -InputObject $azsConnection -MemberType NoteProperty -Name Reachable -Value $true
                        }
                        catch {
                              $exception = $_
                              if ($showLogs) {
                                    Write-Host "$($azsConnection.ConnectionName) Validating ARM endpoint failed."
                                    Write-Host $exception
                              }
                              Add-Member -InputObject $azsConnection -MemberType NoteProperty -Name Reachable -Value $false
                        }
                  }

                  if ($gridView) {
                        $azsConnectionsList | Out-GridView
                        return
                  } 
                  if ($exportCsv) {
                        $dateTimeTicks = (Get-Date).Ticks
                        $outFileName = "Get-MamStamps-$($endpointName)-$($dateTimeTicks).csv"
                        Write-Host "Writing to File: $($outFileName)"
                        $azsConnectionsList | Export-Csv -Path $outFileName -NoTypeInformation
                        return
                  }
                  if ($printToConsole) {
                        if (-not $extendedOutput) {
                              $azsConnectionsList = $azsConnectionsList | Select-Object -Property ConnectionId, ConnectionName, Location, IsAdfs, EnvironmentCategoryTag, ArmEndpointUrl,TenantArmEndpointUrl,Reachable
                        }
                        Write-Host ""
                        Write-Host "Azure Stack Hub Stamp Connectivity Status" -BackgroundColor White -ForegroundColor DarkBlue
                        Write-Host ""

                        foreach ($azsConnection in $azsConnectionsList) {
                              Write-Host "------------------------------------------------------------------------------------------------"
                              if ($azsConnection.Reachable) {
                                    Write-Host "$($azsConnection.ConnectionName) is Reachable" -BackgroundColor Green -ForegroundColor Black
                              }
                              else {
                                    Write-Host "$($azsConnection.ConnectionName) is not Reachable" -BackgroundColor Red -ForegroundColor Black
                              }
                              Write-Host "------------------------------------------------------------------------------------------------"
                              Write-Output -InputObject $azsConnection
                              Write-Host ""
                        }
                        return
                  }
                  return $azsConnectionsList
            }
            Write-Error "Command returns null result."
            return $null
      }
      catch {
            $exception = $_
            Write-Host "Azure Stack Hub Stamp ARM endpoint validation: Failed to get stamp details."
            Write-Error $exception
      }
}

function Get-VConnectDiscoveredResourcesStatus {
      param(
            [Parameter(Mandatory = $true, Position = 0)]
            $name,
            [Parameter(Mandatory = $true, Position = 1)]
            $dbName,
            [Parameter(Mandatory = $false, Position = 2)]
            [int] $connectionId = 0,
            [Switch] $printToConsole
            )

      $query = "select
           drs.PlatformConnectionId as ConnectionId,
           drs.Cluster as StampName,
           drs.PlatformServiceName as Service,
           drs.ResourceSubType as ResourceType,
           Max(drs.DiscoveredDate) as UpdatesDate,
           Max(dj.Id) as DiscoverJobId,
           Max(ds.DeploymentId) as DeploymentId,
           Max(ops.ResultMessage) as LastMessage
           from DiscoveredResources drs with (nolock)
           inner join DiscoveryJobs dj with (nolock) on drs.DiscoveryJobId = dj.Id
           inner join Deployments ds with (nolock) on ds.DeploymentId = dj.DiscoveryJobDeploymentId
           inner join Operations ops with (nolock) on ops.DeploymentId = ds.DeploymentId
           Where ($connectionId <= 0 or drs.PlatformConnectionId = $connectionId)
              and drs.IsDeleted = 0
              and drs.PlatformConnectionType = 1050
              and ds.StatusString = 'Completed'
              and ops.OperationName = 'DiscoverAdminPlatformServiceOp'
           Group by PlatformConnectionId, Cluster, PlatformServiceName, ResourceSubType
           order by Cluster, PlatformServiceName, ResourceSubType"


      Write-Host "Azure Stack Hub Stamp Resource Update Status..."

      if ($printToConsole) {
        Invoke-SqlDbQuery $name $query $dbName -printToConsole
        return
      }
      return Invoke-SqlDbQuery $name $query $dbName
}

function Test-SharedFolderAccess {
      param(
            [Parameter(Mandatory = $true, Position = 0)]
            $MarketplaceOfflineRepoPath,
            [Parameter(Mandatory = $true, Position = 1)]
            $MarketplaceOfflineRepoUsername,
            [Parameter(Mandatory = $true, Position = 2)]
            $MarketplaceOfflineRepoPassword
            )
            
    try {
        $MarketplaceOfflineRepoPath = $MarketplaceOfflineRepoPath.TrimEnd('\')
        
        Write-Verbose "Connecting to '$MarketplaceOfflineRepoPath'..." -Verbose
        net use $MarketplaceOfflineRepoPath /user:$MarketplaceOfflineRepoUsername $MarketplaceOfflineRepoPassword /persistent:no
        if (-not (Test-Path $("filesystem::$MarketplaceOfflineRepoPath"))) {
            Write-Error "Unable to access Offline repo directory '$MarketplaceOfflineRepoPath'"
            throw
        }
        $tempDownloadDir = "$MarketplaceOfflineRepoPath\testaccess.txt"
        New-Item $tempDownloadDir -ItemType File
        if (Test-Path $tempDownloadDir) {
             Remove-Item -Path $tempDownloadDir -Force
        }
        Write-Verbose "Read Write validation completed." -Verbose
        Write-Host "Validating SharedFolderAccess is success. " -BackgroundColor Black -ForegroundColor Green
    }
    catch {
          Write-Error "Test Offline Marketplace Repository access failed"
          throw
    }
}

function Test-AzureSyndicationCredential {
      param(
            [Parameter(Mandatory = $true, Position = 0)]
            $SyndicationSubscriptionId,
            [Parameter(Mandatory = $true, Position = 1)]
            $SyndicationAdminTenantId,
            [Parameter(Mandatory = $false, Position = 2)]
            $SyndicationAdminApplicationId,
            [Parameter(Mandatory = $false, Position = 3)]
            $SyndicationAdminApplicationCredential
            )
    try {
        Import-Module -Name Az.Accounts -ErrorAction Stop
        Import-Module -Name Azs.Syndication.Admin -ErrorAction Stop
    }
    catch {
          Write-Error "Import Az.Accounts and Azs.Syndication.Admin failed."
          throw
    }
    # Login into Azure Stack environment
    try {
        Write-Verbose "Trying to login in to Azure..." -Verbose
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13
        Clear-AzContext -Scope Process -Force -ErrorAction Stop
        if ($SyndicationAdminApplicationId -ne $null) {
            $syndicationAdminCred = Get-Credential -UserName $SyndicationAdminApplicationId -Message 'Enter Azure Syndication Application Secret...'
        } else {
            $syndicationAdminCred = $SyndicationAdminApplicationCredential
        }
        if ($syndicationAdminCred -eq $null) {
          Write-Error "Please provide input for 'SyndicationAdminApplicationId' or 'syndicationAdminCred'..."
        }

        Connect-AzAccount -EnvironmentName AzureCloud -ServicePrincipal -Credential $syndicationAdminCred -TenantId $SyndicationAdminTenantId -Scope Process -Force -ErrorAction Stop
        Get-AzSubscription -SubscriptionID $SyndicationSubscriptionId | Select-AzSubscription -Scope Process
    }
    catch {
          Write-Error "Login into Azure to access $SyndicationSubscriptionId Failed."
          throw
    }
    try {
        Write-Verbose "Trying to list available Azure Marketplace Products..." -Verbose
        $productsSelected = Select-AzsMarketplaceItem
        $productsSelected
        Clear-AzContext -Scope Process -Force -ErrorAction SilentlyContinue

        Write-Host "Validating AzureSyndicationCredential is success. " -BackgroundColor Black -ForegroundColor Green
    }
    catch {
          Write-Error "Test AzureSyndicationCredential is failed."
          throw
    }
}

function Export-AzsMarketplaceItemToOfflineRepo {
      param(
            [Parameter(Mandatory = $true, Position = 0)]
            $SyndicationSubscriptionId,
            [Parameter(Mandatory = $true, Position = 1)]
            $SyndicationAdminTenantId,
            [Parameter(Mandatory = $false, Position = 2)]
            $SyndicationAdminApplicationId,
            [Parameter(Mandatory = $false, Position = 3)]
            $SyndicationAdminApplicationCredential,
            [Parameter(Mandatory = $true, Position = 4)]
            $MarketplaceOfflineRepoPath,
            [Parameter(Mandatory = $true, Position = 5)]
            $MarketplaceOfflineRepoUsername,
            [Parameter(Mandatory = $true, Position = 6)]
            $MarketplaceOfflineRepoPassword
            )
    try {
        Import-Module -Name Az.Accounts -ErrorAction Stop
        Import-Module -Name Azs.Syndication.Admin -ErrorAction Stop
    }
    catch {
          Write-Error "Import Az.Accounts and Azs.Syndication.Admin failed."
          throw
    }
    try {
        Write-Verbose "Connecting to '$MarketplaceOfflineRepoPath'..." -Verbose
        $MarketplaceOfflineRepoPath = $MarketplaceOfflineRepoPath.TrimEnd('\')
        $tempDownloadDir = "$MarketplaceOfflineRepoPath\.downloading"

        net use $MarketplaceOfflineRepoPath /user:$MarketplaceOfflineRepoUsername $MarketplaceOfflineRepoPassword /persistent:no
        if (-not (Test-Path $("filesystem::$MarketplaceOfflineRepoPath"))) {
            return Get-ScriptResult $false 1 "Unable to access Offline repo directory '$MarketplaceOfflineRepoPath'"
        }
        if (-not (Test-Path $tempDownloadDir)) {
            New-Item $tempDownloadDir -ItemType Directory -Force -ErrorAction SilentlyContinue
        }
    }
    catch {
          Write-Error "Test Offline Marketplace Repository access failed"
          throw
    }
    # Login into Azure Stack environment
    try {
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13
        Clear-AzContext -Scope Process -Force -ErrorAction Stop
        if ($SyndicationAdminApplicationId -ne $null) {
            $syndicationAdminCred = Get-Credential -UserName $SyndicationAdminApplicationId -Message 'Enter Azure Syndication Application Secret...'
        } else {
            $syndicationAdminCred = $SyndicationAdminApplicationCredential
        }
        if ($syndicationAdminCred -eq $null) {
          Write-Error "Please provide input for 'SyndicationAdminApplicationId' or 'SyndicationAdminApplicationCredential'..."
        }
        Connect-AzAccount -EnvironmentName AzureCloud -ServicePrincipal -Credential $syndicationAdminCred -TenantId $SyndicationAdminTenantId -Scope Process -Force -ErrorAction Stop
        Get-AzSubscription -SubscriptionID $SyndicationSubscriptionId | Select-AzSubscription -Scope Process
    }
    catch {
          Write-Error "Login into Azure to access $SyndicationSubscriptionId Failed."
          throw
    }
    try {
        Write-Verbose "Please select only one product to export." -Verbose
        $productToDownload = Select-AzsMarketplaceItem
        if ($productToDownload.Length -eq 0) {
            Write-Error "No Marketplace prodcuct is selected. Please try again."
            return
        }
        if ($productToDownload -is [array]) {
            Write-Warning "Multiple Products are selected to export. Please try again." -Verbose
            return
        }
        $MarketplaceItemId = $productToDownload.Split('/')[-1]
        Write-Verbose "Start exporting $MarketplaceItemId" -Verbose

        $products = @($productToDownload)
        $products | Export-AzsMarketplaceItem -RepositoryDir $tempDownloadDir -IgnoreDependencies -AcceptLegalTerms -ErrorAction Stop
        Write-Verbose "Exporting $MarketplaceItemId is completed successfully." -Verbose
    }
    catch {
          Write-Error "Export Marketplace products into Offline Repository failed."
          throw
    }
    
    Clear-AzContext -Scope Process -Force -ErrorAction SilentlyContinue
    try {
        # Update status of downloaded Marketplace product folder
        Write-Verbose "Moving download product from temp folder to Repository." -Verbose
        net use $MarketplaceOfflineRepoPath /user:$MarketplaceOfflineRepoUsername $MarketplaceOfflineRepoPassword /persistent:no
        Move-Item "$tempDownloadDir\$MarketplaceItemId" $MarketplaceOfflineRepoPath -Force -ErrorAction Stop
    }
    catch {
          Write-Error "Configuring Offline Marketplace Repository is failed."
          throw
    }
}

function Import-AzsMarketplaceItemFromOfflineRepo {
      param(
            [Parameter(Mandatory = $true, Position = 0)]
            $MarketplaceProductId,
            [Parameter(Mandatory = $true, Position = 1)]
            $AdminArmEndpoint,
            [Parameter(Mandatory = $true, Position = 2)]
            $DefaultProviderSubscriptionId,
            [Parameter(Mandatory = $true, Position = 3)]
            $AzsAdminTenantId,
            [Parameter(Mandatory = $false, Position = 4)]
            $AdminApplicationId,
            [Parameter(Mandatory = $false, Position = 5)]
            $AdminApplicationCredential,
            [Parameter(Mandatory = $true, Position = 6)]
            $MarketplaceOfflineRepoPath,
            [Parameter(Mandatory = $true, Position = 7)]
            $MarketplaceOfflineRepoUsername,
            [Parameter(Mandatory = $true, Position = 8)]
            $MarketplaceOfflineRepoPassword
            )
    
    try {
        Import-Module -Name Az.Accounts -ErrorAction Stop
        Import-Module -Name Az.Storage -ErrorAction Stop
        Import-Module -Name Azs.Syndication.Admin -ErrorAction Stop
    }
    catch {
        Write-Error "Import Az.Accounts, Az.Storage and Azs.Syndication.Admin failed."
        throw
    }
    try {
        Write-Verbose "Connecting to '$MarketplaceOfflineRepoPath'..." -Verbose
        # Validate access to shared folder / offline repo
        net use $MarketplaceOfflineRepoPath /user:$MarketplaceOfflineRepoUsername $MarketplaceOfflineRepoPassword /persistent:no
        if (-not (Test-Path $("filesystem::$MarketplaceOfflineRepoPath"))) {
            throw "Unable to find/access Offline repo directory $MarketplaceOfflineRepoPath"
        }

        $marketplaceItemPath = "$MarketplaceOfflineRepoPath\$MarketplaceProductId"
        if (-not (Test-Path $marketplaceItemPath)) {
            Write-Error "'$MarketplaceProductId' is not found the in offline repo. Please download the Marketplace Item."
        }
    }
    catch {
        Write-Error "Test Offline Marketplace Repository access failed"
        throw
    }

    try {
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13

        # Login into Azure Stack environment
        Clear-AzContext -Scope Process -Force -ErrorAction Stop

        $azsEnvironmentName = "AzsHub_ImportAzsMarketplaceItem"
        Add-AzEnvironment -Name $azsEnvironmentName -ArmEndpoint $AdminArmEndpoint -Scope Process -ErrorAction Stop

        if ($AdminApplicationId -ne $null) {
            $azSAdminCred = Get-Credential -UserName $AdminApplicationId -Message 'Enter Azure Stack Hub App Registration Secret...'
        } else {
            $azSAdminCred = $AdminApplicationCredential
        }
        if ($azSAdminCred -eq $null) {
          Write-Error "Please provide input for 'AdminApplicationId' or 'AdminApplicationCredential'..."
        }
        Add-AzAccount -EnvironmentName $azsEnvironmentName -ServicePrincipal -Credential $azSAdminCred -TenantId $AzsAdminTenantId -SubscriptionId $DefaultProviderSubscriptionId -Scope Process -ErrorAction Stop

        Get-AzSubscription -SubscriptionID $DefaultProviderSubscriptionId | Select-AzSubscription -Scope Process -ErrorAction Stop
    }
    catch {
        Write-Error "Login into Azure Stack Hub to access $DefaultProviderSubscriptionId Failed."
        throw
    }

    try {
        try {
            $JournalDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [guid]::NewGuid().ToString())

            # Invoke Syndication Module
            $productName = $($MarketplaceProductId) # Product Name List to import
            Import-AzsMarketplaceItem -RepositoryDir $MarketplaceOfflineRepoPath -ProductName $productName -JournalDir $JournalDir -UpTo Import -ErrorAction Stop
            Write-Verbose "Importing $MarketplaceProductId is completed successfully." -Verbose
        }
        catch {
          Clear-AzContext -Scope Process -Force -ErrorAction SilentlyContinue
          Write-Error "Import Marketplace products from Offline Repository failed."
          throw
        }

        # Removing Syndication tool journal directory
        if (Test-Path $JournalDir) {
            Remove-Item -Path $JournalDir -Recurse -Force -ErrorAction SilentlyContinue
        }

        try {
            # Can be found in 'Azs.Syndication.Admin' PS Module Path => Import.psm1
            $syndicationTempRgName = "syndicationtemp-rg"
            $syndicationTempSaName = "syndicationtempsa"

            # Cleaning Syndication Temp Storage Account
            $tempSa = Get-AzStorageAccount -ResourceGroupName $syndicationTempRgName -Name $syndicationTempSaName -ErrorAction SilentlyContinue
            if ($null -eq $tempSa) {
                Write-Warning "Successfully imported Marketplace Item '$MarketplaceProductId'. Storage Account $syndicationTempSaName is not found. Cleaning imported Marketplace Item artifacts skiped."
                return
            }
            $saContext = New-AzStorageContext -ConnectionString $tempSa.Context.ConnectionString -ErrorAction Stop
            $artifactsToClean = Get-AzStorageBlob -Container temp -Context $saContext -Prefix "$MarketplaceProductId/" -ErrorAction Stop
            if ($true) {
                $artifactsToClean = $artifactsToClean | Where-Object { -not $_.Name.StartsWith("$MarketplaceProductId/icons/") }
            }
            $artifactsToClean | Remove-AzStorageBlob -Context $saContext -ErrorAction Stop -Verbose
            Clear-AzContext -Scope Process -Force -ErrorAction SilentlyContinue
        }
        catch {
          Clear-AzContext -Scope Process -Force -ErrorAction SilentlyContinue
          Write-Error "Import Marketplace Product completed successfully, but clean-up failed from storage account $syndicationTempSaName."
          throw
        }

        Write-Verbose "Successfully imported Marketplace Item '$MarketplaceProductId'." -Verbose
    }
    catch {
        Clear-AzContext -Scope Process -Force -ErrorAction Stop
        try {
            net use $MarketplaceOfflineRepoPath /user:$MarketplaceOfflineRepoUsername $MarketplaceOfflineRepoPassword /persistent:no
            if (Test-Path $($JournalDir)) {
                Remove-Item -Path $JournalDir -Recurse -Force -ErrorAction Stop
            }
        }
        catch {
            # Can be ignored
        }
        $_
    }
}

function Enable-OnlyMsmVConnectScheduledTasks {
      param(
            [Parameter(Mandatory = $true, Position = 0)]
            $ConnectionName,
            [Parameter(Mandatory = $true, Position = 1)]
            $DBName,
            [Switch] $printToConsole
            )

      $query = "UPDATE [dbo].[AgentServiceConfigs] SET DesiredState = 0 WHERE
           ServiceName IN ( 'workflowhostservice' ,
           'workspacemonitoringscheduledtask',
           'backupschedulesstatusupdaterscheduledtask',
           'virtualnetworksyncscheduledtask',
           'vmcustompropertiesscheduledtask',
           'vmexpiryanddecommissionscheduledtask',
           'vmmetricscollectorscheduledtask',
           'mruntimestatusupdaterscheduledtask',
           'vmstatusupdaterscheduledtask',
           'autosuspendidlevmscheduledtask',
           'policyscheduledtask',
           'migratevmstofoldersscheduledtask')"


      Write-Host "Disable Jobs not needed for Multi Stamp Management..."

      if ($printToConsole) {
        Invoke-SqlDbQuery $ConnectionName $query $DBName -printToConsole
        return
      }
      return Invoke-SqlDbQuery $ConnectionName $query $DBName
}

function Add-BillingApiPortToVConnectRpNsg
{
    param(
    [Parameter(Mandatory = $true, Position = 0)]
    $TenantArmEndpoint,
    [Parameter(Mandatory = $true, Position = 1, HelpMessage ="VConnect RP Subscription Id")]
    $TenantSubscriptionId,
    [Parameter(Mandatory = $true, Position = 2, HelpMessage ="VConnect RP Resource Group")]
    $VConnectRpResourceGroup
    )

    try {
        $envName = "AzureStackUser"
        Write-Verbose "Fetching the details of Azure Stack Environment $envName ... " -Verbose
        $env =  Get-AzureRmEnvironment | Where-Object -Property ResourceManagerUrl -Contains $TenantArmEndpoint 
        if ($env -ne $null) {
            $envName = $env.Name
        }
        else {
            Write-Verbose "Adding the Azure Stack Environment $envName ... " -Verbose
            Add-AzureRmEnvironment -Name $envName -ArmEndpoint $TenantArmEndpoint
        }

        $tenantCreds = Get-Credential -Message "Enter Azure Stack Tenant Username and Password"
        $tenantDirectoryTenantName = $tenantCreds.UserName.Split('@')[1]
        $authEndpoint = (Get-AzureRmEnvironment -Name $envName).ActiveDirectoryAuthority.TrimEnd('/')

        Write-Verbose "Getting the Tenant Directory Id... " -Verbose
        $directoryTenantID = (invoke-restmethod "$($authEndpoint)/$($tenantDirectoryTenantName)/.well-known/openid-configuration").issuer.TrimEnd('/').Split('/')[-1]
       
        Write-Verbose "Logging into the Azure Stack Environment $envName ... " -Verbose
        Login-AzureRmAccount -EnvironmentName $envName -TenantId $directoryTenantID -Credential $tenantCreds -ErrorAction Stop
        Select-AzureRmSubscription -SubscriptionId $TenantSubscriptionId -ErrorAction Stop

        Write-Verbose "Adding Billing Api Port in VConnect RP Network Security Group... " -Verbose
        Get-AzureRmNetworkSecurityGroup -Name "VConnectVM1000-SSG" -ResourceGroupName $VConnectRpResourceGroup | 
        Add-AzureRmNetworkSecurityRuleConfig -Name "BillingApiPort" -Description "Allow Billing Api" -Access Allow -Protocol * -Direction Inbound -Priority 100 -SourceAddressPrefix * -SourcePortRange * -DestinationAddressPrefix * -DestinationPortRange 44306 | Set-AzureRmNetworkSecurityGroup
        Write-Host "Added Billing Api Port in VConnect RP Network Security Group."
    }
    catch {
        $exception = $_
        Write-Host "Failure Adding Billing Api Port in VConnect RP Network Security Group."
        Write-Error $exception
    }
}
# SIG # Begin signature block
# MIIQQAYJKoZIhvcNAQcCoIIQMTCCEC0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUcqHFVXAg6XvASLR8dOVnOTNC
# 4X6gggz8MIIGcjCCBFqgAwIBAgIIZDNR08c4nwgwDQYJKoZIhvcNAQELBQAwfDEL
# MAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgw
# FgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBD
# ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwNjI0MjA0NDMwWhcNMzEw
# NjI0MjA0NDMwWjB4MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNV
# BAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMTQwMgYDVQQDDCtTU0wuY29t
# IENvZGUgU2lnbmluZyBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIxMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAn4MTc6qwxm0hy9uLeod00HHcjpdymuS7iDS0
# 3YADxi9FpHSavx4PUOqebXjzn/pRJqk9ndGylFc++zmJG5ErVu9ny+YL4w45jMY1
# 9Iw93SXpAawXQn1YFkDc+dUoRB2VZDBhOmTyl9dzTH17IwJt83XrVT1vqi3Er750
# rF3+arb86lx56Q9DnLVSBQ/vPrGxj9BJrabjQhlUP/MvDqHLfP4T+SM52iUcuD4A
# SjpvMjA3ZB7HrnUH2FXSGMkOiryjXPB8CqeFgcIOr4+ZXNNgJbyDWmkcJRPNcvXr
# nICb3CxnxN3JCZjVc+vEIaPlMo4+L1KYxmA3ZIyyb0pUchjMJ4f6zXWiYyFMtT1k
# /Summ1WvJkxgtLlc/qtDva3QE2ZQHwvSiab/14AG8cMRAjMzYRf3Vh+OLzto5xXx
# d1ZKKZ4D2sIrJmEyW6BW5UkpjTan9cdSolYDIC84eIC99gauQTTLlEW9m8eJGB8L
# uv+prmpAmRPd71DfAbryBNbQMd80OF5XW8g4HlbUrEim7f/5uME77cIkvkRgp3fN
# 1T2YWbRD6qpgfc3C5S/x6/XUINWXNG5dBGsFEdLTkowJJ0TtTzUxRn50GQVi7Inj
# 6iNwmOTRL9SKExhGk2XlWHPTTD0neiI/w/ijVbf55oeC7EUexW46fLFOuato95tj
# 1ZFBvKkCAwEAAaOB+zCB+DAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0E
# CQei9Xp9UlMSkpXuOIAlDaZZMDAGCCsGAQUFBwEBBCQwIjAgBggrBgEFBQcwAYYU
# aHR0cDovL29jc3BzLnNzbC5jb20wEQYDVR0gBAowCDAGBgRVHSAAMBMGA1UdJQQM
# MAoGCCsGAQUFBwMDMDsGA1UdHwQ0MDIwMKAuoCyGKmh0dHA6Ly9jcmxzLnNzbC5j
# b20vc3NsLmNvbS1yc2EtUm9vdENBLmNybDAdBgNVHQ4EFgQUVML+EJUAk81q9efA
# 19myS7iPDOMwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQD1DyaH
# cK+Zosr11snwjWY9OYLTiCPYgr+PVIQnttODB9eeJ4lNhI5U0SDuYEPbV0I8x7CV
# 9r7M6qM9jk8GxitZhn/rcxvK5UAm4D1vzPa9ccbNfQ4gQDnWBdKvlAi/f8JRtyu1
# e4Mh8GPa5ZzhaS51HU7LYR71pTPfAp0V2e1pk1e6RkUugLxlvucSPt5H/5CcEK32
# VrKk1PrW/C68lyGzdoPSkfoGUNGxgCiA/tutD2ft+H3c2XBberpotbNKZheP5/Dn
# V91p/rxe4dWMnxO7lZoV+3krhdVtPmdHbhsHXPtURQ8WES4Rw7C8tW4cM1eUHv5C
# NEaOMVBO2zNXlfo45OYS26tYLkW32SLK9FpHSSwo6E+MQjxkaOnmQ6wZkanHE4Jf
# /HEKN7edUHs8XfeiUoI15LXn0wpva/6N+aTX1R1L531iCPjZ16yZSdu1hEEULvYu
# YJdTS5r+8Yh6dLqedeng2qfJzCw7e0wKeM+U9zZgtoM8ilTLTg1oKpQRdSYU6iA3
# zOt5F3ZVeHFt4kk4Mzfb5GxZxyNi5rzOLlRL/V4DKsjdHktxRNB1PjFiZYsppu0k
# 4XodhDR/pBd8tKx9PzVYy8O/Gt2fVFZtReVT84iKKzGjyj5Q0QA07CcIw2fGXOho
# v88uFmW4PGb/O7KVq5qNncyU8O14UH/sZEejnTCCBoIwggRqoAMCAQICEA0SjRWQ
# uYT7eM+eDgHqTTMwDQYJKoZIhvcNAQELBQAweDELMAkGA1UEBhMCVVMxDjAMBgNV
# BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDE0
# MDIGA1UEAwwrU1NMLmNvbSBDb2RlIFNpZ25pbmcgSW50ZXJtZWRpYXRlIENBIFJT
# QSBSMTAeFw0yMTEwMjUyMDQ1NTNaFw0yMzEwMjUyMDQ1NTNaMHcxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdSZWRtb25kMRkwFwYD
# VQQKDBBDbG91ZCBBc3NlcnQgTExDMQswCQYDVQQLDAJVUzEZMBcGA1UEAwwQQ2xv
# dWQgQXNzZXJ0IExMQzCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAOr+
# k6LFTYXntOaV4dGI/mIyACrEE3JCr4RP5Aur5QgWuraKhL2JSh63eADxOOuk5P4E
# KXhNG8F1XW67gDLNfUlQ9ZagD3+xts/Vc8hZOqmwGw57K0/EUy5RoVVVWntMQ7DX
# q0VNp2SgMVHBuRWLvB7MX7OGTJ96+IWgEzMITBxx+bToBl+iefkJhOVZi2lCG9oN
# M3i5Yrq2T7cV1uCQwl6JNBrsaCJ64vs6pz8LzR0XmhXtg5rLYehFCqcWYCcH4Njm
# ZUVharTmBozLOTPdL6y3UReZRM5J1SxvrfRvalFQGWX4hK6OirBey1yPnhzqNHAt
# iwCLxn5l+pnTh89LLmtc1Bp8OI2nN7yaiXK13441EQFpIYnBSQJ6e8n0dDpwwoux
# OSfxtgX8iila0DBoy9vLCyGTnyXdO1zZYGoll9v8aSbvWOZu4n4gvQPVIhgROU74
# wkfGXI61Ab9ZtltF5W5WQesJoDiRIYgHUxYWU5fsTPzsoQFIXzHyaTqeJKXOtwID
# AQABo4IBhzCCAYMwHwYDVR0jBBgwFoAUVML+EJUAk81q9efA19myS7iPDOMwegYI
# KwYBBQUHAQEEbjBsMEgGCCsGAQUFBzAChjxodHRwOi8vY2VydC5zc2wuY29tL1NT
# TGNvbS1TdWJDQS1Db2RlU2lnbmluZy1SU0EtNDA5Ni1SMS5jZXIwIAYIKwYBBQUH
# MAGGFGh0dHA6Ly9vY3Nwcy5zc2wuY29tMFEGA1UdIARKMEgwCAYGZ4EMAQQBMDwG
# DCsGAQQBgqkwAQMDATAsMCoGCCsGAQUFBwIBFh5odHRwczovL3d3dy5zc2wuY29t
# L3JlcG9zaXRvcnkwEwYDVR0lBAwwCgYIKwYBBQUHAwMwTQYDVR0fBEYwRDBCoECg
# PoY8aHR0cDovL2NybHMuc3NsLmNvbS9TU0xjb20tU3ViQ0EtQ29kZVNpZ25pbmct
# UlNBLTQwOTYtUjEuY3JsMB0GA1UdDgQWBBT5QOeOXNcPYtBWMGBY9lkdLp4AczAO
# BgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAGL909UmLyhbmLPe0AyH
# mItkDXXIonmIsCrNrquwtFB5ZFhV2eQEEcFi8N+R1Pw2CGWNQe8EGN83nr1ItDNc
# JqweHvadc6i5FF1DVRPKEVHzORKKsKGZ97KyYQkT+YxJLfVCLdFCemCd2QYQuFJQ
# 4LdKcR9QZE0LvoiE9qVZ0fv2oO/4Yg/jgFTS4m1znT1IXIfCgnxfK9dr5QwQt/wX
# 3ayq554Ptbl7f6g9AGnD3U7cEaDvaPqRX16AGgxWbJU4W740UeNZnsFvdNcBHY/7
# wWxCzR03dzTGivW1aozokn05KeOyF0ZU7vhhXSeKyoaLzJXEr96r7pBUfBlVL9p9
# 6IVsHxsnPGFVZiaaZ0YQFsBWJZLEpVOIXCl2Jb2KX/NshRJGeijK0a6msVYIHKPv
# mhLnDruJkadj4RIgk8AQ2wsttUWtjWRKjD072OnAVZatRsCPIPQJsk+8gSKqfDZR
# o3DZhnrCd6TfjuoU9aULSXrwJljrOqLNOZHFoBuT7y3dZHPoo596yCmwUs+7dYCR
# nBU+hQ0Fca9aWpaYw4lKdxxhXn66EIR00TbaE3HYdHhlOc8koA9VUI/eiWdd1rKL
# j67luXYkCEJ37fE6SlyL1Jkhu3dd79+GSYlTINRnH415fH4DwiOMckj8kRbdyRV1
# tT1R5QeVMAdZHzQ80j8shEydMYICrjCCAqoCAQEwgYwweDELMAkGA1UEBhMCVVMx
# DjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wg
# Q29ycDE0MDIGA1UEAwwrU1NMLmNvbSBDb2RlIFNpZ25pbmcgSW50ZXJtZWRpYXRl
# IENBIFJTQSBSMQIQDRKNFZC5hPt4z54OAepNMzAJBgUrDgMCGgUAoHgwGAYKKwYB
# BAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAc
# BgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUsRF7
# 4dYilCcV2qd4HMllXLUxkfwwDQYJKoZIhvcNAQEBBQAEggGAjCP0RjafRd9r453H
# dJkE1uFcE6OMnD5gbj5hIypfbfG3afEJ9CQd3fkvALONSTbBKPoRdohIPRy+9G/V
# VTv0JxpqkPGi/bIAXffGQk2GuwKVlT0xN2x3cM7m2STKIC2RJMZ+7KQiZ8TU1SWW
# US7m/fEOeC2/vvmx9+UxuMPW1nTqsMKxVV9kI4gGXFg50KrqdXq73vvNngWUnEUO
# 6AFI5+4O3khudfaurUSK7ndJaoPSLXuMRaTsXaNEcqNidQIZxOu0bT2T9HSx5TNO
# AAVriWVvW7rgHrFxHzpqfKGCvgPei6nGntGzmihjLM3pIUtRGlyen3TfBJbk8VPV
# +Ru8RbMaMUjWTaO3jzLOaqrepwQOE4HRkUS7CJI25+kqzZSU2W6G9g15xZ3X+Pc0
# TBTfynmK+6E9r6DkVOCvWbtBeYOm91ao9zXpLWkRVtGO7RQt8zwyfWyNTGGH0i17
# BZClFkMwJc6rN6eFtIh9XN3UBi6l23kb2fKBM8U1VFg73k1N
# SIG # End signature block