New-AzBastionShareAbleLink.ps1

#####################################################################################################################
Function New-AzBastionShareableLink {
    <#
    .SYNOPSIS
        This function will create a shareable link for the Bastion Host
        Author: Aammir Mirza
        Version: 1.0.0
    .DESCRIPTION
        There is no way to create a shareable link for the Bastion Host in the Azure PowerShell module. This function will hit the REST API directly to create the shareable link.
    .NOTES
        This function won't enable the shareable link feature for the Bastion Host. This needs to be done manually or see previous article on how to do this.
    .EXAMPLE
        New-AzBastionShareAbleLink -VMNames @("vm-01", "vm-03", "vm-02") -BastionNames @("bastion-vnt-0001","bastion-vnt-0003") -SubscriptionId $subscriptionId
        Function will support multiple VMs and Bastion Hosts. Just pass in the names of the VMs and Bastion Hosts and the subscription ID.
        You can run the script multiple times to add more VMs and Bastion Hosts to the shareable link. Existing links will remain intact.
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [array]
        $VMNames,
        [Parameter()]
        [array]
        $BastionNames,
        [Parameter()]
        $SubscriptionId,
        [Parameter()]$VMResourceGroup
    )
    $links = @() # Array to store the links
    $header = @{
        'Content-Type' = 'application/json'
        Authorization  = ('Bearer ' + (Get-AzAccessToken).Token)
    }
    ForEach ($bastion in $BastionNames) {
        $getBastion = Get-AzBastion -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $bastion }
        if (($getBastion.ProvisioningState -eq 'Succeeded') -and ($getBastion.sku.name)) {
            $ResourceGroupName = $getBastion.ResourceGroupName
            $Location = $getBastion.Location
            $SubscriptionId = $getBastion.Id.Split('/')[2]
            $Name = $getBastion.Name

            Write-Output "`e[36m[INFO] - ---------------------------------"
            Write-Output "`e[36m[INFO] - Bastion Host Name: $bastion"
            Write-Output "`e[36m[INFO] - Resource Group: $ResourceGroupName"
            Write-Output "`e[36m[INFO] - Subscription: $SubscriptionId"
            Write-Output "`e[36m[INFO] - Location: $Location"
            Write-Output "`e[36m[INFO] - ---------------------------------"

            Foreach ($vm in $VMNames) {
                if ($VMResourceGroup) {
                    $getVM = Get-AzVM -ResourceGroupName $VMResourceGroup -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $vm }
                }
                else {
                    $getVM = Get-AzVM -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $vm }
                }

                if ($getVM.ProvisioningState -eq 'Succeeded') {
                    $requestBody = @{
                        'vms' = @(
                            @{
                                'vm' = @{
                                    'id' = $getVM.Id
                                }
                            }
                        )
                    }
                    try {
                        $uriLink = "https://management.azure.com/subscriptions/$($SubscriptionId)/resourceGroups/$($ResourceGroupName)/providers/Microsoft.Network/bastionHosts/$($Name)/GetShareableLinks?api-version=2022-07-01"
                        $getBastionLink = Invoke-WebRequest -Method Post -Uri $uriLink -Headers $header -Body (ConvertTo-Json $requestBody -Depth 10) | ConvertFrom-Json
                    }
                    catch {
                        Write-Warning "Missing Shareable link for $($vm). Creating now..."
                    }
                    if (!$getBastionLink) {
                        $uri = "https://management.azure.com/subscriptions/$($SubscriptionId)/resourceGroups/$($ResourceGroupName)/providers/Microsoft.Network/bastionHosts/$($Name)/createShareableLinks?api-version=2022-07-01"
                        $response = Invoke-WebRequest -Method Post -Uri $uri -Headers $header -Body (ConvertTo-Json $requestBody -Depth 10)
                        Start-Sleep -Seconds 5 # Wait for the link to be created. This is to avoid the error "The link is not ready yet. Please try again later."
                        if ($response.StatusCode -eq 202) {
                            try {
                                $getBastionLink = Invoke-WebRequest -Method Post -Uri $uriLink -Headers $header -Body (ConvertTo-Json $requestBody -Depth 10) | ConvertFrom-Json
                                Write-Output "`e[32m[TASK 1.0] - Shareable Link is created for $vm. Please wait for retrieving the link."
                                $links += [PSCustomObject]@{
                                    'VMName'        = $vm
                                    'Bastion'       = $bastion
                                    'ResourceGroup' = $ResourceGroupName
                                    'Link'          = $getBastionLink.value.bsl
                                    'Created Date'  = $getBastionLink.value.createdAt
                                }
                                Write-Output "`e[32m[TASK 1.1] - Shareable Link has added for the Virtual Machine: $vm"
                                Write-Output "`e[32m[LINK] - $($getBastionLink.value.bsl)"
                            }
                            catch {
                                Write-Output "`e[31m[Error] - Virtual Machine $vm does not linked/connected to Bastion VNET OR Link creation operation failed."
                                Write-Output "`e[31m [Verify] - VM NIC is linked to VNET"
                                Write-Output "`e[31m [Verify] - Check the PEERING betwen Bastion VNET and VM VNET"
                                Write-Output "`e[31m [Verify] - Check if you can connect to VM via Bastion using Portal."
                            }
                        }
                    }
                    else {
                        Write-Output "`e[33m[STATUS] - Shareable Link already exist for : $vm"
                        $links += [PSCustomObject]@{
                            'VMName'        = $vm
                            'Bastion'       = $bastion
                            'ResourceGroup' = $ResourceGroupName
                            'Link'          = $getBastionLink.value.bsl
                            'Created Date'  = $getBastionLink.value.createdAt
                        }
                    }
                }
                else {
                    Write-Output "`e[31m[Error] - Virtual Machine $vm does not exist. Please check the name and try again."
                    continue
                }
            }
        }
        else {
            Write-Output "`e[31m[Error] - Bastion Host $bastion does not exist. Please check the name and try again."
            $status = ((Get-AzBastion -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $bastion }).ProvisioningState)
            Write-Warning "[ProvisioningState] - $($status)"
            if (($getBastion.ProvisioningState -eq 'Succeeded')) {
                Write-Output "`e[31m[Error] - Bastion Host is NOT running under STANDARD SKU. Only Standard SKU supports Shareable links. (So sad is'nt it ?)"
            }
            continue
        }
    }
    Return $links | Format-Table -AutoSize
}