Ever Been Baffled About How to Auto-Cleanup Azure Bastion Sharable Links.?

Azure Bastion Overview

Azure Bastion Sharable Links becoming a much-needed requirement now days. So, before we are getting into how let’s talk about what are these sharable links.

As per Microsoft,

The Bastion Shareable Link feature lets users connect to a target resource (virtual machine or virtual machine scale set) using Azure Bastion without accessing the Azure portal. This article helps you use the Shareable Link feature to create a shareable link for an existing Azure Bastion deployment.

Auto-Cleanup Azure Bastion Sharable Links

When a user without Azure credentials clicks a shareable link, a webpage opens that prompts the user to sign into the target resource via RDP or SSH. Users authenticate using username and password or private key, depending on what you have configured for the target resource. The shareable link does not contain any credentials – the admin must provide sign-in credentials to the user.

This is a pretty good addition to Azure Bastion, but it does come with a security flow too. Since we will be sharing these links with external users/vendors, Links that we generate does not expire or cannot set an expiry at the moment and also when accessing these links there is no MFA requirements too. If one of these links, get into the wrong hands there is a high chance to get your environment exploited.

So, what we could do is to minimize the risk if you are using this, we could automatically delete the links in schedule as in everyday midnight.

Auto-Cleanup Azure Bastion Sharable Links

At the moment we have 2 options to achieve this. either thru GUI or using API. So, there is no PowerShell option. So, I came up with a script to perform the cleanup using Azure Automation Runbook.

Explanation –

Azure automation service principal was granted following permissions.

  • Microsoft.Network/bastionHosts/deleteShareableLinks/action
  • Microsoft.Network/bastionHosts/deleteShareableLinksByToken/action
  • Microsoft.Network/bastionHosts/getShareableLinks/action

Also, you would need to grant access to read the VM details too.

Script will

  1. Query the tenant to get the bastion details.
  2. Go thru all the subscriptions to query all the virtual machines with sharable links.
  3. Make sure links are not more than 1 day older, if so, it will delete the link.

You need to update the script with subscription id where you have the bastion. deployed.

Also, you would need to use PowerShell 7 for this script too as some of the API commands are only available in ps7.


# Define the subscription ID for the platform/Bastion Service
$PlatformSubscriptionId = ""

# Define header for HTTP requests
$header = @{
    "Content-Type" = "application/json"
    # Acquire token for authentication
    Authorization  = ("Bearer " + (Get-AzAccessToken).Token)

# Select the given subscription
select-azsubscription -SubscriptionId $PlatformSubscriptionId

# Attempt to retrieve Bastion information; suppress any errors
$azBastion = Get-AzBastion -ErrorAction SilentlyContinue   

# Check if the Bastion provisioning was successful
if ($azBastion.ProvisioningState -eq "Succeeded") {
    # Extract necessary details from the Bastion information
    $ResourceGroupName = $azBastion.ResourceGroupName
    $SubscriptionId = $azBastion.Id.Split('/')[2]
    $Name = $azBastion.Name      

    Write-Output "🔵 Bastion Host Name: $name"

    # Define the URI for the shareable link request
    $uri = "$($PlatformSubscriptionId)/resourceGroups/$($azBastion.ResourceGroupName)/providers/Microsoft.Network/bastionHosts/$($azBastion.Name)/GetShareableLinks?api-version=2023-02-01"
    # Retrieve a list of all subscriptions
    $SubscriptionList = Get-AzSubscription | Select-Object Id, Name

    # Iterate over each subscription
    foreach ($sub in $SubscriptionList) {
        select-azsubscription -SubscriptionId $sub.Id
        Write-Output "Processing Subscription $($sub.Name)"

        # Fetch all VMs under the current subscription
        $vmList = Get-AzVm

        # Iterate over each VM in the subscription
        foreach ($vm in $vmList) {         
            Write-Host "Processing VM $($vm.Name)"
            # Define the request body to get shareable link for the VM
            $requestBody = @{
                "vms" = @(
                        "vm" = @{
                            "id" = $vm.Id

            # Check if request body exists and is not null
            if ($null -ne $requestBody) {    
                # Make HTTP request to fetch shareable link
                $getBastionLink = Invoke-RestMethod -Method Post -Uri $uri -Headers $header -Body (ConvertTo-Json $requestBody -Depth 10) -SkipHttpErrorCheck   
                # Check if the link exists
                if ($null -ne $getBastionLink.value) {
                    # Convert string date to DateTime object
                    $convertedDate = [DateTime]::parseexact($getBastionLink.value.createdAt,"MM/dd/yyyy HH:mm:ss",[System.Globalization.CultureInfo]::InvariantCulture)
                    # Calculate difference in days from link creation date to now
                    $daysDifference = (Get-Date) - $convertedDate
                    # Check if link was created more than 1 days ago
                    if ($daysDifference.Days -gt 1) {
                        # Print details and potentially delete old links
                        Write-Output "Deleting the Shareable Link for the Virtual Machine: $($vm.Name)"
                        $uri = "$($PlatformSubscriptionId)/resourceGroups/$($azBastion.ResourceGroupName)/providers/Microsoft.Network/bastionHosts/$($azBastion.Name)/deleteShareableLinks?api-version=2023-02-01"                 
                        $output = Invoke-WebRequest -Method Post -Uri $uri -Headers $header -Body (ConvertTo-Json $requestBody -Depth 10) 
                    else {
                        # The link is not older than 1 days
                        Write-Output "The date is not older than 1 days."
                else {
                    # No ABS Link for the VM
                    # Write-Output "ABS Link does not exist for the Virtual Machine: $($vm.Name)" 
            else {
                # Something went wrong, potentially with the VM
                Write-Output "VM $($vm.Name) Something went wrong."
else {
    # No Azure Bastion was found in the provided subscription
    Write-Output "NO Azure Bastion Exists within the permitted subscription."

So as the next step you could use this script to run as an automation job in an Azure Automation account. That will make sure this cleanup is happening as per you schedule requirements.

Also, you can refer to the below link if you want to know how to deploy a runbook.

[mailpoet_form id="1"]

Other Recent Blogs

Microsoft Teams IP Phones and Intune Enrollment

Microsoft Teams provides a growing portfolio of devices that can be used as desk and conference room phones. These IP phones run on Android 8.x or 9.x and are required to be enrolled in Intune. By default, these devices are enrolled as personal devices, which is not ideal as users should not be able to enrol their own personal Android devices.

Read More »

Level 9, 360 Collins Street, 
Melbourne VIC 3000

Level 2, 24 Campbell St,
Sydney NSW 2000

200 Adelaide St,
Brisbane QLD 4000

191 St Georges Terrace
Perth WA 6000

Level 10, 41 Shortland Street

Part of

Arinco trades as Arinco (VIC) Pty Ltd and Arinco (NSW) Pty Ltd. © 2023 All Rights Reserved Arinco™ | Privacy Policy | Sustainability and Our Community
Arinco acknowledges the Traditional Owners of the land on which our offices are situated, and pay our respects to their Elders past, present and emerging.

Get started on the right path to cloud success today. Our Crew are standing by to answer your questions and get you up and running.