Skip to content

Instantly share code, notes, and snippets.

@mitch-b
Created June 8, 2017 13:18
Show Gist options
  • Save mitch-b/bd39a363925f696801961d2e3ab2366d to your computer and use it in GitHub Desktop.
Save mitch-b/bd39a363925f696801961d2e3ab2366d to your computer and use it in GitHub Desktop.
Associate existing Hybrid Connections to Azure Web App with PowerShell
<#
AssociateHybridConnections
#>
param
(
[string] $webAppName,
[string] $relayName,
[string] $region,
[string] $webAppResourceGroup,
[string] $relayResourceGroup,
[string[]] $connections = @(),
[switch] $verbose,
[switch] $quiet
)
$version = "2017.0607.4"
$global:quiet = $false
$global:verbose = $false
function Set-Globals
{
$global:quiet = $quiet
if (!$global:quiet) { # -quiet will override -verbose
$global:verbose = $verbose
}
}
function Show-ScriptHeader
{
Write-Host "=="
Write-Host "=="
Write-Host "== Associate Hybrid Connections"
Write-Host "== Mitchell Barry, $version"
Write-Host "=="
Write-Host "== This script exists because currently, Azure does not offer the "
Write-Host "== ability to associate Hybrid Connections within an "
Write-Host "== Azure Resource Manager template (ARM template). "
Write-Host "== When this is available, we need to switch to that method."
Write-Host "=="
Write-Host "== This script still expects to be ran under context of (Login-AzureRmAccount) "
Write-Host "== to access LoginToken."
Write-Host "=="
}
function Show-Help
{
Write-Host "=="
Write-Host "=="
Write-Host "== How to run: "
Write-Host "== .\RmAssociateHybridConnections.ps1 "
Write-Host "== -webAppName [string] ``"
Write-Host "== -webAppResourceGroup [string] ``"
Write-Host "== -relayName [string] ``"
Write-Host "== -relayResourceGroup [string] ``"
Write-Host "== -region [string] ``"
Write-Host "== -connections [string[]]"
Write-Host "=="
}
Set-Globals
if (!$global:quiet) {
Show-ScriptHeader
}
function Get-ParameterError {
$parameterError = $false
if (-Not $webAppName) {
Write-Host "==! -webAppName [string] parameter must be populated with a value"
Write-Host "==! for example: -webAppName `"wapp-myapp-cus-dev`""
Write-Host "==!"
$parameterError = $true
}
if (-Not $webAppResourceGroup) {
Write-Host "==! -webAppResourceGroup [string] parameter must be populated with a value"
Write-Host "==! for example: -webAppResourceGroup `"rg-myapp-cus-dev`""
Write-Host "==!"
$parameterError = $true
}
if (-Not $relayName) {
Write-Host "==! -relayName [string] parameter must be populated with a value"
Write-Host "==! for example: -relayName `"shared-servicebus`""
Write-Host "==!"
$parameterError = $true
}
if (-Not $relayResourceGroup) {
Write-Host "==! -relayResourceGroup [string] parameter must be populated with a value"
Write-Host "==! for example: -relayResourceGroup `"rg-shared-cus-dev`""
Write-Host "==!"
$parameterError = $true
}
if (-Not $region) {
Write-Host "==! -region [string] parameter must be populated with a value"
Write-Host "==! for example: -region `"cus`""
Write-Host "==!"
$parameterError = $true
}
if (-Not $connections -or $connections.Length -eq 0) {
Write-Host "==! -connections [string[]] parameter must be populated with a value"
Write-Host "==! for example: -connections `"name:hostname:port`",`"another-name:another-hostname:port`""
Write-Host "==!"
Write-Host "==! NOTE: the Hybrid Connection name will use the hostname for each entry if not specified"
Write-Host "==! : meaning `"server1:80`" will be interpreted as `"server1:server1:80`""
Write-Host "==! : if this is not desirable, specify the name in front of the hostname followed by a colon "
Write-Host "==! : like `"reportserver:server1:80`""
Write-Host "==!"
$parameterError = $true
}
return $parameterError
}
if ((Get-ParameterError)) {
if (!$global:quiet) {
Show-Help
}
Write-Error "== Exiting due to parameter errors"
Exit 100
}
function Invoke-AssociateHybridConnections {
param (
[string] $subscriptionId
)
Import-Module "$PSScriptRoot\AzureModules\RmHybridConnectionUtilities.psm1" -Force
$authToken = Get-AzureAuthorizationHeaderForTenant $subscriptionId
$connectionObjects = Expand-Connections $connections
if ($global:verbose) {
Write-Host "== Associating $($connectionObjects.Count) Hybrid Connections to $webAppName"
}
foreach ($connection in $connectionObjects) {
$connectionDetails = Invoke-AttachExistingHybridConnection `
-subscriptionId $subscriptionId `
-authToken $authToken `
-relayName $relayName `
-relayResourceGroup $relayResourceGroup `
-webAppResourceGroup $webAppResourceGroup `
-webAppName $webAppName `
-region $region `
-endpointName $connection.name `
-serverName $connection.hostname `
-port $connection.port
if (!$global:quiet) {
Write-Host "== Associated $($connectionDetails.name) to $webAppName"
}
}
}
function Expand-Connections {
param (
[string[]] $connections
)
$connectionObjects = @()
foreach ($connString in $connections) {
$connection = @{
name = ''
hostname = ''
port = 0
}
$segments = ([string]$connString).Split(':')
if ($segments.Count -lt 1) {
Write-Error "==! Incorrectly formatted Hybrid Connection string: $connString"
continue
} elseif ($segments[2]) {
# name of connection is explicitly stated
$connection.name = $segments[0]
$connection.hostname = $segments[1]
$connection.port = [int]$segments[2]
} else {
# hostname is the name of connection
$connection.name = $segments[0]
$connection.hostname = $segments[0]
$connection.port = [int]$segments[1]
}
if ($global:verbose) {
Write-Host "== Parsed $connString into $(ConvertTo-Json -InputObject $connection -Compress)"
}
$connectionObjects += $connection
}
return $connectionObjects
}
# Ensure user logged into Azure
# if we want, we could pass in user/pw ... https://github.com/Azure/azure-powershell/issues/1309
Import-Module "$PSScriptRoot\AzureModules\RmGetHttpAuthorizationHeader.psm1" -Force
$subscription = Resolve-AzureLogin
$subscriptionId = $subscription.Subscription.SubscriptionId
if (-Not $subscriptionId) {
Write-Host "== Execute 'Login-AzureRmAccount' and retry this script."
Exit 300
}
Invoke-AssociateHybridConnections $subscriptionId
if (!$global:quiet) {
Write-Host "== "
Write-Host "== Complete"
Write-Host ""
}
<#
Azure Authorization Header Module
#>
<#
.Synopsis
Acquire authorization header for making API calls to Azure
.Description
Needs to be run from within a logged-in Azure Resource Manager account context.
Requires PowerShell ActiveDirectory DLL at ${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.dll
.Parameter subscriptionId
Azure Subscription ID to retrieve login token
#>
function Get-AzureAuthorizationHeaderForTenant {
param
(
[string] $subscriptionId
)
$tenantId = (Get-AzureRmSubscription -SubscriptionId $subscriptionId).TenantId
if ($tenantId) {
$cache = [Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache]::DefaultShared
if ($cache) {
$token = $cache.ReadItems() | Where-Object { $_.TenantId -eq $tenantId }
} else {
Write-Error "== Could not retrieve cache from Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache"
return
}
} else {
Write-Error "== Could not retrieve Tenant ID from subscription '$subscriptionId'"
return
}
if ($token.AccessToken) {
if ($global:verbose) {
Write-Host "== Retrieved token: $($token.AccessToken)"
}
return $token.AccessToken
} else {
Write-Error "== Unable to acquire AccessToken from cache"
return
}
}
function Resolve-AzureLogin {
$subscription = $null
try {
$subscription = Get-AzureRmContext
} catch {
if ($_ -like "*Login-AzureRmAccount to login*") {
# https://stackoverflow.com/a/40904066/569531
if ([Environment]::UserInteractive -and
!([Environment]::GetCommandLineArgs() | Where-Object {$_ -ilike '-NonI*'})) {
Write-Host "== Need to perform (Login-AzureRmAccount) to use this script. Attempting now ... (requires interaction)"
Login-AzureRmAccount
$subscription = Get-AzureRmContext
} else {
Write-Host "== PowerShell not in interactive mode"
Write-Error $_
}
} else {
Write-Error "Unable to check (Get-AzureRmContext)"
}
}
if (!$subscription) {
Write-Error "PowerShell context is not logged into any Azure Subscription"
} else {
if ($global:verbose) {
Write-Host "== PowerShell context is logged into Azure Subscription ID '$($subscription.Subscription.SubscriptionId)'"
}
}
return $subscription
}
if ($global:verbose) {
Write-Host "== Loaded RmGetHttpAuthorizationHeader.psm1"
}
Export-ModuleMember -Function Resolve-AzureLogin
Export-ModuleMember -Function Get-AzureAuthorizationHeaderForTenant
<#
Azure Resource Manager : Hybrid Connection Utilities
#>
function Get-HybridConnectionRules
{
param
(
[string] $subscriptionId,
[string] $resourceGroup,
[string] $relayName,
[string] $endpointName,
[string] $authToken,
[string] $policy = 'defaultSender',
[string] $azureApiHost = 'https://management.azure.com',
[string] $apiVersion = '2016-07-01'
)
if (!$authToken) { # assuming $subscriptionId is available...
Import-Module "$PSScriptRoot\RmGetHttpAuthorizationHeader.psm1" -Force
$authToken = Get-AzureAuthorizationHeaderForTenant $subscriptionId
}
$headers = @{
"Authorization" = "Bearer $authToken";
}
$url = "$azureApiHost/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.Relay/namespaces/$relayName/hybridConnections/$endpointName/authorizationRules/$policy/listKeys?api-version=$apiVersion"
if ($global:verbose) {
Write-Host "== Hitting url: $url"
}
$data = Invoke-RestMethod -Method POST -Uri $url -Headers $headers
return $data
}
function Invoke-AttachExistingHybridConnection
{
param
(
[string] $subscriptionId,
[string] $relayName,
[string] $region,
[string] $webAppName,
[string] $webAppResourceGroup,
[string] $relayResourceGroup,
[string] $apiVersion = '2016-03-01',
[string] $authToken,
[string] $azureApiHost = "https://management.azure.com",
[string] $endpointName,
[string] $serverName,
[int] $port
)
if (!$authToken) { # assuming $subscriptionId is available...
Import-Module "$PSScriptRoot\RmGetHttpAuthorizationHeader.psm1" -Force
$authToken = Get-AzureAuthorizationHeaderForTenant $subscriptionId
}
$headers = @{
"Authorization" = "Bearer $authToken";
"Content-Type" = "application/json";
}
$hybridConnectionInfo = Get-HybridConnectionRules `
-subscriptionId $subscriptionId `
-resourceGroup $relayResourceGroup `
-relayName $relayName `
-authToken $authToken `
-endpointName $endpointName
if ($global:verbose) {
Write-Host "== $endpointName has settings: $hybridConnectionInfo"
}
$payload = @{
"location" = $region;
"properties" = @{
"hostName" = $serverName;
"port" = $port;
"relayArmUri" = "/subscriptions/$subscriptionId/resourceGroups/$relayResourceGroup/providers/Microsoft.Relay/namespaces/$relayName/hybridConnections/$endpointName";
"sendKeyName" = $hybridConnectionInfo.keyName;
"sendKeyValue" = $hybridConnectionInfo.primaryKey;
}
}
$payloadString = ($payload | ConvertTo-Json)
$url = "$azureApiHost/subscriptions/$subscriptionId/resourceGroups/$webAppResourceGroup/providers/Microsoft.Web/sites/$webAppName/hybridConnectionNamespaces/$relayName/relays/$($endpointName)?api-version=$apiVersion"
if ($global:verbose) {
Write-Host "== PUT : $url"
Write-Host "== Sending payload : "
Write-Host $payloadString
}
$data = Invoke-RestMethod -Method PUT -Uri $url -Body $payloadString -Headers $headers
return $data
}
if ($global:verbose) {
Write-Host "== Loaded RmHybridConnectionUtilities.psm1"
}
Export-ModuleMember -Function Invoke-AttachExistingHybridConnection
Export-ModuleMember -Function Get-HybridConnectionRules
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment