HowTo - Connect to Azure Bastion VM on a Mac

While Azure Virtual Machines provide a handy 'Connect via Bastion' option directly in portal, it defaults to a web application session that has some limitations, and just does not work terribly well. Fortunately, there is a way to use a command line command to connect directly from and Azure Command Line interface.

On Windows, it is a single command. However, for the MacOS users there is not. With a little bit more work though, some adjustments to the Windows script, it is entirely possible to create a script that will allow the use of a native Terminal Services/Remote Desktop/Windows App client to connect via Bastion to your Azure Virtual Machines.

In this article, we will layout how to do it.

In order to connect, you will need to know a few bits of information, but given that, you can quickly create your own script to make connecting easy on a Mac as well.

Gather Some Information

Before getting started, you will need to find you Subscription Id for the Virtual Machine. You will also need the name of the Virtual Machine and the Resource Group it belongs. In each of these commands that information needs to put in place to make it work. Whereever you need to use your data we have used a placeholder wrapped in { }.

Fortunately, all of the information you need is available on the Overview page of the Virtual Machine in the Azure Portal.

For Windows the command is straight forward, however, this does not work on the Mac.

az network bastion rdp --name "{bastion-name}" \
--resource-group "{resource-group-name}" --target-resource-id "/subscriptions/{subscription-id-guid}/resourceGroups/{vm-group-name}/providers/Microsoft.Compute/virtualMachines/{vm-instance-name}"

Replace the following with your values:

  • {subscription-id-guid}
  • {bastion-name}
  • {resource-group-name}
  • {vm-group-name}
  • {vm-instance-name}

So for the Mac we need to create a shell script:

#!/bin/sh

rm -f /var/tmp/{vm-instance-name}.rdp
touch /var/tmp/{vm-instance-name}.rdp
echo "full address:s:127.0.0.1:50025
prompt for credentials:i:1
administrative session:i:1
username:s:tw-admin
" > /var/tmp/{vm-login-name}.rdp

# Start the tunnel

az network bastion tunnel --name "{bastion-name}" --resource-group "{resource-group-name}" \
--target-resource-id \
"/subscriptions/{subscription-id-guid}/resourceGroups/{vm-group-name}/providers/Microsoft.Compute/virtualMachines/{vm-instance-name}" \
--resource-port "3389" --port "50025" &
TUNNEL_PID=$!
sleep 2

# Start the rdp connection
open --wait-apps /var/tmp/{vm-instance-name}.rdp -a "Windows App.app"

# Clean up after ourselves
echo "PID: $TUNNEL_PID"
kill -9 $TUNNEL_PID >> /dev/null
INT_PID=`ps x | grep bastion | grep "azure.cli" | grep "virtualMachines/{vm-instance-name}" | awk '{print $1}'`
echo "PID: $INT_PID"
kill -9 $INT_PID
echo "PROCESS TERMINATED"
rm -f /var/tmp/{vm-instance-name}.rdp

Replace the following with your values:

  • {subscription-id-guid}
  • {bastion-name}
  • {resource-group-name}
  • {vm-group-name}
  • {vm-instance-name}
  • {vm-login-name}

In addition, if you want to use multiple VM's concurrently, you can use create multiple versions of the script supporting different ports.

Once created and saved, if we open Terminal, we can run it using the command

sh ./name-of-our-script.sh

The Terminal window will need to stay open until you are done with the RDP session. In fact, closing the Windows App should also terminate the script, but if you need to leave the application open for other RDP sessions, you can simply use Ctrl + C to end the connection, or just close the Terminal Session.

To make life more convenient, the script can be marked executable and then set to open with Terminal, which allows running it from Finder.

In Terminal, change the mode of the script to executable:

chmod +x ./name-of-our-script.sh

Once done, right click on the script in Finder. Under Open With, select Terminal, and you now have a clickable, runnable icon for your convenience.

Next Step - A Generic Script

Now, sometimes we do not want to have to modify a script for every configuration, it would be useful to have a more generic one that works on command line options. The following script does just that. It takes several parameters to define the connection, and then does the work just like the other, but this time in a generic manner.

Parameters

  • -s subscription-id
  • -b bastion-name
  • -g bastion-resource-group
  • -v vm-name
  • -V vm-group-name ( if different from -g )
  • -u vm-user-name ( optional )
  • -p port for the tunnel ( optional, default is 50022 )
#!/bin/sh
set -x
echo "

"

AZ_LOCAL_PORT="50022" 

while getopts "b:s:g:v:V:u:p:?" opt; do
  case "$opt" in
    s) AZ_SUBSCRIPTION="$OPTARG" ;;
    b) AZ_BASTION="$OPTARG" ;;
    g) AZ_RESGROUP="$OPTARG" ;;
    v) AZ_VM="$OPTARG" ;;
    V) AZ_VMGROUP="$OPTARG" ;;
    u) VM_USER="$OPTARG" ;;
    p) AZ_LOCAL_PORT="$OPTARG" ;;
    ?)
     echo "Usage: $(basename $0) -s <subscription> -b <bastion> -g <group> -v <vm-name> [-u <vm-user>] [-p <custom-port>]"
     exit 1 ;;
  esac
done

IS_VALID=""
if [[ -z "$AZ_SUBSCRIPTION" ]]; then
  echo "\tA Subscription ID must be provided: -s <subscription>"
  IS_VALID="false"
fi
if [[ -z "$AZ_BASTION" ]]; then
  echo "\tA Bastion must be provided:         -b <bastion>"
  IS_VALID="false"
fi
if [[ -z "$AZ_RESGROUP" ]]; then
  echo "\tA Resource Group must be provided:  -g <group>"
  IS_VALID="false"
fi
if [[ -z "$AZ_VM" ]]; then
  echo "\tA VM Name must be provided:         -v <vm-name>"
  IS_VALID="false"
fi
if [[ -n "$AZ_VMGROUP" ]]; then 
  $AZ_VMGROUP=$AZ_RESGROUP
fi
if [[ -n "$IS_VALID" ]]; then 
  exit 1
fi

RDP_FILE="/var/tmp/$AZ_VM.rdp"

# create a temporary .rdp file that we will use to connect with our native 
# application ( on the Mac we default to "Windows App.app" )

rm -f $RDP_FILE
touch $RDP_FILE
echo "full address:s:127.0.0.1:$AZ_LOCAL_PORT
prompt for credentials:i:1
administrative session:i:1
username:s:$VM_USER
" > $RDP_FILE

# Start the tunnel

az network bastion tunnel --name "${AZ_BASTION}" --resource-group "$AZ_RESGROUP" \
--target-resource-id "/subscriptions/$AZ_SUBSCRIPTION/resourceGroups/$AZ_VMGROUP/providers/Microsoft.Compute/virtualMachines/$AZ_VM" \
--resource-port "3389" --port $AZ_LOCAL_PORT &
sleep 2

# Start the rdp connection

open --wait-apps $RDP_FILE -a "Windows App.app"

# Clean up after ourselves 
# ( note this triggers upon closing the Windows App.app, not just closing the window )

echo "PID: $TUNNEL_PID"
kill -9 $TUNNEL_PID
INT_PID=`ps x | grep bastion | grep "Python -Im azure.cli" | grep "virtualMachines/$AZ_VM" | awk '{print $1}'`
echo "PID: $INT_PID"
kill -9 $INT_PID
echo "PROCESS TERMINATED"
rm -f $RDP_FILE

Hope this helps out others. We find it useful, because the web based connection has too many limitations...

Satori, Dru @ 1/26/2026 8:08:06 PM
information technologyazurevirtual machinemacOSscript