Skip to content

Instantly share code, notes, and snippets.

@timmyreilly
Created October 29, 2025 20:42
Show Gist options
  • Save timmyreilly/f9df725d106b7bc9b2bc8987b99a88a5 to your computer and use it in GitHub Desktop.
Save timmyreilly/f9df725d106b7bc9b2bc8987b99a88a5 to your computer and use it in GitHub Desktop.
#!/usr/bin/env pwsh
Write-Host "=== Azure Bastion Connection Helper ===" -ForegroundColor Cyan
Write-Host ""
# Step 1: Get all VMs with their bastion-related info
Write-Host "Loading VMs and Bastions..." -ForegroundColor Yellow
$vms = az vm list -d --query "[].{Name:name, ResourceGroup:resourceGroup, PowerState:powerState, VnetId:networkProfile.networkInterfaces[0].id}" | ConvertFrom-Json
if ($vms.Count -eq 0) {
Write-Host "No VMs found in subscription." -ForegroundColor Red
exit 1
}
$bastions = az network bastion list --query "[].{Name:name, ResourceGroup:resourceGroup, VnetId:virtualNetwork.id}" | ConvertFrom-Json
Write-Host ""
Write-Host "=== Available VMs ===" -ForegroundColor Cyan
for ($i = 0; $i -lt $vms.Count; $i++) {
$vm = $vms[$i]
Write-Host "[$($i + 1)] $($vm.Name) (RG: $($vm.ResourceGroup), State: $($vm.PowerState))"
}
Write-Host ""
$vmSelection = Read-Host "Select VM number (1-$($vms.Count))"
$selectedVM = $vms[$vmSelection - 1]
if (-not $selectedVM) {
Write-Host "Invalid selection." -ForegroundColor Red
exit 1
}
Write-Host ""
Write-Host "Selected VM: $($selectedVM.Name)" -ForegroundColor Green
Write-Host "Resource Group: $($selectedVM.ResourceGroup)" -ForegroundColor Green
Write-Host "Power State: $($selectedVM.PowerState)" -ForegroundColor Green
# Check if VM is running
if ($selectedVM.PowerState -ne "VM running") {
Write-Host ""
Write-Host "WARNING: VM is not running!" -ForegroundColor Yellow
$startVM = Read-Host "Would you like to start it? (y/n)"
if ($startVM -eq "y") {
Write-Host "Starting VM..." -ForegroundColor Yellow
az vm start --resource-group $selectedVM.ResourceGroup --name $selectedVM.Name
Write-Host "VM started successfully." -ForegroundColor Green
}
}
# Step 2: Get VM's VNet
Write-Host ""
Write-Host "Getting VM network information..." -ForegroundColor Yellow
$vmDetails = az vm show --resource-group $selectedVM.ResourceGroup --name $selectedVM.Name | ConvertFrom-Json
$vmId = $vmDetails.id
# Get NIC details to find VNet
$nicId = $vmDetails.networkProfile.networkInterfaces[0].id
$nic = az network nic show --ids $nicId | ConvertFrom-Json
$vmVnetId = $nic.ipConfigurations[0].subnet.id -replace '/subnets/.*', ''
Write-Host "VM VNet ID: $vmVnetId" -ForegroundColor Gray
# Step 3: Find bastions
Write-Host ""
Write-Host "=== Available Bastions ===" -ForegroundColor Cyan
if ($bastions.Count -eq 0) {
Write-Host "No Bastions found in subscription." -ForegroundColor Red
exit 1
}
# Try to match bastion to VM's VNet
$matchedBastion = $null
for ($i = 0; $i -lt $bastions.Count; $i++) {
$bastion = $bastions[$i]
$isMatch = $bastion.VnetId -eq $vmVnetId
$matchIndicator = if ($isMatch) { " (MATCHED TO VM)" } else { "" }
if ($isMatch) {
$matchedBastion = $bastion
}
Write-Host "[$($i + 1)] $($bastion.Name) (RG: $($bastion.ResourceGroup))$matchIndicator"
}
Write-Host ""
if ($matchedBastion) {
Write-Host "Auto-selecting matched bastion: $($matchedBastion.Name)" -ForegroundColor Green
$selectedBastion = $matchedBastion
} else {
$bastionSelection = Read-Host "Select Bastion number (1-$($bastions.Count))"
$selectedBastion = $bastions[$bastionSelection - 1]
if (-not $selectedBastion) {
Write-Host "Invalid selection." -ForegroundColor Red
exit 1
}
}
Write-Host ""
Write-Host "Selected Bastion: $($selectedBastion.Name)" -ForegroundColor Green
# Step 4: Select connection type
Write-Host ""
Write-Host "=== Connection Type ===" -ForegroundColor Cyan
Write-Host "[1] SSH (port 22)"
Write-Host "[2] RDP (port 3389)"
Write-Host "[3] Custom port"
Write-Host ""
$connType = Read-Host "Select connection type (1-3)"
switch ($connType) {
"1" {
$remotePort = 22
$localPort = 50022
$protocol = "SSH"
}
"2" {
$remotePort = 3389
$localPort = 53389
$protocol = "RDP"
}
"3" {
$remotePort = Read-Host "Enter remote port"
$localPort = Read-Host "Enter local port"
$protocol = "Custom"
}
default {
Write-Host "Invalid selection." -ForegroundColor Red
exit 1
}
}
# Step 5: Display connection command
Write-Host ""
Write-Host "=== Connection Details ===" -ForegroundColor Cyan
Write-Host "VM: $($selectedVM.Name)" -ForegroundColor White
Write-Host "Bastion: $($selectedBastion.Name)" -ForegroundColor White
Write-Host "Protocol: $protocol" -ForegroundColor White
Write-Host "Remote Port: $remotePort" -ForegroundColor White
Write-Host "Local Port: $localPort" -ForegroundColor White
Write-Host ""
$tunnelCommand = "az network bastion tunnel ``
--name $($selectedBastion.Name) ``
--resource-group $($selectedBastion.ResourceGroup) ``
--target-resource-id $vmId ``
--resource-port $remotePort ``
--port $localPort"
Write-Host "=== Tunnel Command ===" -ForegroundColor Cyan
Write-Host $tunnelCommand -ForegroundColor Yellow
Write-Host ""
if ($protocol -eq "SSH") {
Write-Host "After tunnel is established, connect with:" -ForegroundColor Cyan
Write-Host " ssh -p $localPort <username>@localhost" -ForegroundColor White
} elseif ($protocol -eq "RDP") {
Write-Host "After tunnel is established, connect with:" -ForegroundColor Cyan
Write-Host " mstsc /v:localhost:$localPort" -ForegroundColor White
}
Write-Host ""
$proceed = Read-Host "Start tunnel now? (y/n)"
if ($proceed -eq "y") {
Write-Host ""
Write-Host "Starting tunnel... (Press Ctrl+C to stop)" -ForegroundColor Green
Write-Host ""
Invoke-Expression $tunnelCommand
} else {
Write-Host ""
Write-Host "Tunnel command saved. Run it manually when ready." -ForegroundColor Yellow
}
@timmyreilly
Copy link
Author

timmyreilly commented Oct 29, 2025

=== Available Bastions ===
[1] snap-redacted
[2] bas-redacted

Select Bastion number (1-2): 2

Selected Bastion: bas-redacteddev

=== Connection Type ===
[1] SSH (port 22)
[2] RDP (port 3389)
[3] Custom port

Select connection type (1-3): 2

=== Connection Details ===
VM: vmbobfca1hub
Bastion: bas-bobfca1-dev
Protocol: RDP
Remote Port: 3389
Local Port: 53389

=== Tunnel Command ===
az network bastion tunnel `
  --name bas-bobfca1-dev `
  --resource-group rg-bobfca1-dev-hub `
  --target-resource-id /subscriptions/asdfasdfasdfsd/resourceGroups/RG-redacted-DEV-HUB/providers/Microsoft.Compute/virtualMachines/redacted`
  --resource-port 3389 `
  --port 53389

After tunnel is established, connect with:
  mstsc /v:localhost:53389

Start tunnel now? (y/n): y

Starting tunnel... (Press Ctrl+C to stop)

'\\wsl.localhost\Ubuntu\redactedk'
CMD.EXE was started with the above path as the current directory.
UNC paths are not supported.  Defaulting to Windows directory.
Bastion Host SKU must be Standard or Premium and Native Client must be enabled.

@timmyreilly
Copy link
Author

Na guarantee your sku includes native client support:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment