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

 

Buy Me A Coffee