A week or so ago I shared a script I put together to help me workout which of my hundreds of Logic Apps were using which queue. I have added a couple of additional things to that script (below) so it now does the following:
- Compares service bus entities (queue, topic,subscription) in a service bus namespace with logic apps to workout which logic app uses which service bus entity
- If any subscriptions are configured with a forward to and dont have a matching logic app then they are added to a seperate output file so you can easily see these. If they have forward to set then you wont be listening to them and will be listening to a queue instead
- Any that dont match are output to a csv file so you can easily see which service bus entities might be orphans
- You can add exclusions. A few of my queues are listened to by functions so i can exclude them from this comparison
With the script I am now easily able to find those orphaned queues i was looking for so I know they can be removed which is what I wanted to do
Below is the script update if anyone needs to do similar things.
$subscriptioname = '[My Sub]'
Connect-AzAccount
Set-AzContext -Subscription $subscriptioname
$serviceBusEntityPrefix = 'test-'
$serviceBusResourceGroup = '[My-Resource-Group]'
$serviceBusNamespace = '[My-ServiceBus-Namespace]'
$logicAppResourceGroup = '[My-Resource-Group]'
$removeServiceBusPrefixBeforeCompare = $true
Get-AzServiceBusNamespace -ResourceGroupName $serviceBusResourceGroup -Name $serviceBusNamespace
$matchList = [System.Collections.ArrayList]::new();
$noMatchList = [System.Collections.ArrayList]::new();
$forwardToList = [System.Collections.ArrayList]::new();
$exclusionList = [System.Collections.ArrayList]::new();
#Add a match is a helper function to make it easy to add a match identified between a logic app and
#a service bus entity to the collection so we can use it later when writing out the documentation
function AddMatch([string] $logicAppName = '', [string] $actionName = '', [string] $triggerName = '', [string] $parameterName = '', [string] $queue = '', [string] $topic = '', [string] $subscription = '', [string] $logicAppParameter = ''){
Write-Host 'Adding Match for Logic App: ' $logicAppName
$match = New-Object -TypeName psobject
$match | Add-Member -MemberType NoteProperty -Name 'LogicApp' -Value $logicAppName
$match | Add-Member -MemberType NoteProperty -Name 'LogicAppTrigger' -Value $triggerName
$match | Add-Member -MemberType NoteProperty -Name 'LogicAppAction' -Value $actionName
$match | Add-Member -MemberType NoteProperty -Name 'LogicAppParameter' -Value $logicAppParameter
$match | Add-Member -MemberType NoteProperty -Name 'Queue' -Value $queue
$match | Add-Member -MemberType NoteProperty -Name 'Topic' -Value $topic
$match | Add-Member -MemberType NoteProperty -Name 'Subscription' -Value $subscription
$matchList.Add($match)
}
#This is a helper function to add a service bus entity which did not match a logic app to the collection for documentation later
#If there was a forward to on the subscription then we add it to the list with forward to specified as they will be likely to not
#have a direct subscriber
function AddNoMatch([string] $queue = '', [string] $topic = '', [string] $subscription = '', [string] $forwardTo = ''){
if($forwardTo -eq ''){
$item = New-Object -TypeName psobject
$item | Add-Member -MemberType NoteProperty -Name 'Queue' -Value $queue
$item | Add-Member -MemberType NoteProperty -Name 'Topic' -Value $topic
$item | Add-Member -MemberType NoteProperty -Name 'Subscription' -Value $subscription
$noMatchList.Add($item)
}
else{
$item = New-Object -TypeName psobject
$item | Add-Member -MemberType NoteProperty -Name 'Queue' -Value $queue
$item | Add-Member -MemberType NoteProperty -Name 'Topic' -Value $topic
$item | Add-Member -MemberType NoteProperty -Name 'Subscription' -Value $subscription
$item | Add-Member -MemberType NoteProperty -Name 'ForwardTo' -Value $forwardTo
$forwardToList.Add($item)
}
}
#This is a helper to add a list of service bus entities we to excluse because they are not used by logic apps
function AddExclusion([string] $queue = '', [string] $topic = '', [string] $subscription = '', [string] $reason = ''){
$item = New-Object -TypeName psobject
$item | Add-Member -MemberType NoteProperty -Name 'Queue' -Value $queue
$item | Add-Member -MemberType NoteProperty -Name 'Topic' -Value $topic
$item | Add-Member -MemberType NoteProperty -Name 'Subscription' -Value $subscription
$item | Add-Member -MemberType NoteProperty -Name 'Reason' -Value $reason
$exclusionList.Add($item)
}
#This is a helper function which will recursively check the actions in a logic app looking for matches
function RecursivelyProcessLogicAppActions($actionsObject){
$actions = $actionsObject.PSObject.Properties
foreach($action in $actions){
$actionName = $action.Name
$actionType = $action.Value.type
$actionJsonText = $action.Value | ConvertTo-Json
#This lets us ignore actions like scope or condition or other actions which may cause duplicates
if($actionType -ne 'Scope' -and $actionType -ne 'Foreach' -and $actionType -ne 'If' -and $actionType -ne 'Until'){
# Compare with Queues
foreach($queue in $serviceBusDescription.Queues){
if($actionJsonText -match $queue.Name){
Write-Host $logicAppName ' is a match for Queue: ' $queue.Name ' in action ' $actionName
AddMatch -logicAppName $logicAppName -queue $queue.Name -actionName $actionName
}
}
# Compare with Topics
foreach($topic in $serviceBusDescription.Topics){
if($actionJsonText -match $topic.Name){
Write-Host $logicAppName ' is a match for Topic ' $topic.Name ' in action ' $actionName
AddMatch -logicAppName $logicAppName -topic $topic.Name -actionName $actionName
foreach($subscription in $topic.Subscriptions){
if($actionJsonText -match $subscription.Name){
Write-Host $logicAppName ' is a match for Subscription '$subscription.Name ' on Topic ' $topic.Name ' in action ' $actionName
AddMatch -logicAppName $logicAppName -topic $topic.Name -subscription $subscription.Name -actionName $actionName
}
}
}
}
}
#If there are child actions then recursively process them too
$childActions = $action.Value.actions
if($childActions -ne $null){
RecursivelyProcessLogicAppActions -actionsObject $childActions
}
}
}
#This is a helper function which will let us format the service bus entity name. We have an environment name prefix on queues
#so we can remove it here to cover scenarios if the logic apps use an environment name parameter or variable.
function FormatServiceBusEntityName([string] $entityName){
$tempEntityName = $entityName
if($removeServiceBusPrefixBeforeCompare -eq $true){
if($tempEntityName.StartsWith($serviceBusEntityPrefix)){
$tempEntityName = $tempEntityName.Substring($serviceBusEntityPrefix.Length)
}
}
return $tempEntityName
}
#Add some exclusions so we can take certain service bus entities, etc out from the analysis if they arent used by logic apps
Write-Host 'Adding Exclusions'
AddExclusion -queue 'int-dataplat-ingress' -reason 'Used by Azure Function'
AddExclusion -queue 'int-hpc-volumetrics-router' -reason 'Used by Azure Function'
AddExclusion -topic 'int-from-amsted-railcar-gps-events' -reason 'Used by Azure Function'
AddExclusion -topic 'int-from-amsted-railcar-gps-events' -reason 'Used by Azure Function'
AddExclusion -topic 'int-from-ec-events-router-output' -reason 'Used by Azure Function'
#Read the Service Bus entities and make a simple object model with the data we are interested in to compare against service bus
Write-Host 'Reading Queues'
$queueDescriptions= @()
$queues = Get-AzServiceBusQueue -ResourceGroupName $serviceBusResourceGroup -NamespaceName $serviceBusNamespace -MaxCount 500
foreach($queue in $queues){
if($queue.Name.ToLower().StartsWith($serviceBusEntityPrefix)){
Write-Host 'Reading Queue: ' $queue.Name
$queueEntityName = FormatServiceBusEntityName -entityName $queue.Name
$queueDescription = [PSCustomObject]@{}
$queueDescription | Add-Member -MemberType NoteProperty -Name 'Name' -Value $queueEntityName
$queueDescriptions += $queueDescription
}
else{
Write-Host 'Skipping: ' $queue.Name ' - the name doesnt match the environment prefix'
}
}
Write-Host 'Reading Topics'
$topicDescriptions= @()
$topics = Get-AzServiceBusTopic -ResourceGroupName $serviceBusResourceGroup -NamespaceName $serviceBusNamespace -MaxCount 500
foreach($topic in $topics){
if($topic.Name.ToLower().StartsWith($serviceBusEntityPrefix)){
Write-Host 'Reading Topic: ' $topic.Name
$topicEntityName = FormatServiceBusEntityName -entityName $topic.Name
$topicDescription = [PSCustomObject]@{}
$topicDescription | Add-Member -MemberType NoteProperty -Name 'Name' -Value $topicEntityName
$subscriptionDescriptions= @()
$subscriptions = Get-AzServiceBusSubscription -ResourceGroupName $serviceBusResourceGroup -NamespaceName $serviceBusNamespace -TopicName $topic.Name
foreach($subscription in $subscriptions){
Write-Host 'Reading Subscription: Topic=' $topic.Name ' Subscription=' $subscription.Name
$subscriptionEntityName = FormatServiceBusEntityName -entityName $subscription.Name
$subscriptionDescription = [PSCustomObject]@{}
$subscriptionDescription | Add-Member -MemberType NoteProperty -Name 'Name' -Value $subscriptionEntityName
$subscriptionDescription | Add-Member -MemberType NoteProperty -Name 'Topic' -Value $topicEntityName
$subscriptionDescription | Add-Member -MemberType NoteProperty -Name 'ForwardTo' -Value $subscription.ForwardTo
$subscriptionDescriptions += $subscriptionDescription
}
$topicDescription | Add-Member -MemberType NoteProperty -Name 'Subscriptions' -Value $subscriptionDescriptions
$topicDescriptions += $topicDescription
}
else{
Write-Host 'Skipping: ' $topic.Name ' - the name doesnt match the environment prefix'
}
}
$serviceBusDescription = [PSCustomObject]@{}
$serviceBusDescription | Add-Member -MemberType NoteProperty -Name 'Queues' -Value $queueDescriptions
$serviceBusDescription | Add-Member -MemberType NoteProperty -Name 'Topics' -Value $topicDescriptions
Write-Host 'Finished Reading Service Bus'
# Read Logic Apps from the resource group so we can loop over them and compare against serice bus
$logicAppResourceGroupItem = Get-AzResourceGroup -Name $logicAppResourceGroup
$logicAppResourceGroupPath = $logicAppResourceGroupItem.ResourceId
Write-Host 'Resource Group Path: ' $logicAppResourceGroupPath
$resources = Get-AzResource -ResourceGroupName $logicAppResourceGroup -ResourceType Microsoft.Logic/workflows
$resources | ForEach-Object {
$logicAppName = $_.Name
Write-Host 'Testing Logic App = ' $logicAppName
$logicApp = Get-AzLogicApp -Name $logicAppName -ResourceGroupName $logicAppResourceGroup
$logicAppUrl = $logicAppResourceGroupPath + '/providers/Microsoft.Logic/workflows/' + $logicApp.Name + '?api-version=2018-07-01-preview'
#Get Logic App Json content from Azure via the API
$logicAppJsonText = az rest --method get --uri $logicAppUrl
$logicAppJsonObject = $logicAppJsonText | ConvertFrom-Json
#Recursively Search Logic App Actions in the json definition looking for use of the service bus entities
$actions = $logicAppJsonObject.properties.definition.actions
RecursivelyProcessLogicAppActions -actionsObject $actions
#Search Logic App Triggers looking for use of service bus entities
$triggers = $logicAppJsonObject.properties.definition.triggers.PSObject.Properties
foreach($trigger in $triggers){
$triggerName = $trigger.Name
$triggerJsonText = $trigger.Value | ConvertTo-Json
# Compare with Queues
foreach($queue in $serviceBusDescription.Queues){
if($triggerJsonText -match $queue.Name){
Write-Host $logicAppName ' is a match for Queue: ' $queue.Name ' in trigger ' $triggerName
AddMatch -logicAppName $logicAppName -queue $queue.Name -triggerName $triggerName
}
}
# Compare with Topics
foreach($topic in $serviceBusDescription.Topics){
if($triggerJsonText -match $topic.Name){
Write-Host $logicAppName ' is a match for Topic ' $topic.Name ' in trigger ' $triggerName
AddMatch -logicAppName $logicAppName -topic $topic.Name -triggerName $triggerName
#Compare with subscriptions
foreach($subscription in $topic.Subscriptions){
if($triggerJsonText -match $subscription.Name){
Write-Host $logicAppName ' is a match for Subscription '$subscription.Name ' on Topic ' $topic.Name ' in trigger ' $triggerName
AddMatch -logicAppName $logicAppName -topic $topic.Name -subscription $subscription.Name -triggerName $triggerName
}
}
}
}
}
#Search the parameters in the logic app looking for use of service bus entities
$parameters = $logicAppJsonText.properties.definition.parameters.PSObject.Properties
foreach($parameter in $parameters){
$parameterName = $parameter.Name
$parameterJsonText = $parameter.Value | ConvertTo-Json
# Compare with Queues
foreach($queue in $serviceBusDescription.Queues){
if($parameterJsonText -match $queue.Name){
Write-Host $logicAppName ' is a match for Queue: ' $queue.Name ' in parameter ' $parameterName
AddMatch -logicAppName $logicAppName -queue $queue.Name -logicAppParameter $parameterName
}
}
# Compare with Topics
foreach($topic in $serviceBusDescription.Topics){
if($parameterJsonText -match $topic.Name){
Write-Host $logicAppName ' is a match for Topic ' $topic.Name ' in parameter ' $parameterName
AddMatch -logicAppName $logicAppName -topic $topic.Name -logicAppParameter $parameterName
#Compare with subscriptions
foreach($subscription in $topic.Subscriptions){
if($parameterJsonText -match $subscription.Name){
Write-Host $logicAppName ' is a match for Subscription '$subscription.Name ' on Topic ' $topic.Name ' in parameter ' $parameterName
AddMatch -logicAppName $logicAppName -topic $topic.Name -subscription $subscription.Name -logicAppParameter $parameterName
}
}
}
}
}
}
#Look for Service Bus Queues which are not used by Logic Apps
foreach($queue in $serviceBusDescription.Queues){
$queueFound = $false
#Check if the queue is in the list of matches we found
foreach($match in $matchList){
if($match.Queue -eq $queue.Name){
$queueFound = $true
}
}
#Check if the queue is listed in the exclusion list so we can ignore it
foreach($exclusion in $exclusionList){
if($exclusion.Queue -eq $queue.Name){
$queueFound = $true
}
}
#If not found add a no match to the no match collection
if($queueFound -eq $false){
Write-Host 'No Match for Queue= ' $queue.Name
AddNoMatch -queue $queue.Name
}
}
#Look for Service Bus topics and subscriptions not used by logic apps
foreach($topic in $serviceBusDescription.Topics){
$topicFound = $false
#Check if its in the list of matches we found
foreach($match in $matchList){
if($match.Topic -eq $topic.Name){
$topicFound = $true
}
}
#Check if its in the exclusion list
foreach($exclusion in $exclusionList){
if($exclusion.Topic -eq $topic.Name){
$topicFound = $true
}
}
#If we didnt find a match, add a no match to the no match collection
if($topicFound -eq $false){
Write-Host 'No Match for Topic= ' $topic.Name
AddNoMatch -topic $topic.Name
}
#Check for a match for any subscriptions
foreach($subscription in $topic.Subscriptions){
$subscriptionFound = $false
#Check if we found a match
foreach($match in $matchList){
if($match.Topic -eq $topic.Name -and $match.Subscription -eq $subscription.Name){
$subscriptionFound = $true
}
}
#Check if its in the exclusion list
foreach($exclusion in $exclusionList){
if($exclusion.Topic -eq $topic.Name -and $exclusion.Subscription -eq $subscription.Name){
$topicFound = $true
}
}
#If we didnt find a match, add a no match to the no match collection
if($topicFound -eq $false){
Write-Host 'No Match for Subscription: Topic=' $topic.Name ' : Subscription= ' $subscription.Name
AddNoMatch -topic $topic.Name -subscription $subscription.Name -forwardTo $subscription.ForwardTo
}
}
}
#Write out some CSV files with the various matches, no matches, subscriptions using forward to and exclusions
Write-Host 'Exporting matches to CSV file'
$matchList | Export-Csv -Path C:\Temp\ServiceBus-Match-LogicApps.csv
Write-Host 'Exporting non-matches to CSV file'
$noMatchList | Export-Csv -Path C:\Temp\ServiceBus-NoMatch.csv
Write-Host 'Exporting Subscriptions with Forward To to CSV file'
$forwardToList | Export-Csv -Path C:\Temp\ServiceBus-NoMatch-Subscriptions-ForwardTo.csv
Write-Host 'Exporting exclusions to CSV file'
$exclusionList | Export-Csv -Path C:\Temp\ServiceBus-Exclusions.csv