bdh.psm1

### --- PUBLIC FUNCTIONS --- ###
#Region - Build-BicepDirectory.ps1
function Build-BicepDirectory {
    # ...
    param(
        [Parameter(Mandatory = $false)]
        [string]$TenantId = (Get-AzTenant | Select-Object -ExpandProperty Id),

        [Parameter(Mandatory = $false)]
        [bool]$StacksOnly = $false,

        [Parameter(Mandatory = $false)]
        [bool]$StateOnly = $true,

        [Parameter(Mandatory = $false)]
        [string]$bicepDirectory,

        [Parameter(Mandatory = $false)]
        [bool]$Force = $false,
        
        [Parameter(Mandatory = $false)]
        [string]$RootManagementGroup = 'Tenant_Root_Group',

        [Parameter(Mandatory = $false)]
        [array]$SubscriptionNames,

        [Parameter(Mandatory = $false)]
        [switch]$SkipManagementGroups
    )

    try {

        # Start with clean variables if no bicepDirectory is given.
        if([string]::IsNullOrEmpty($bicepDirectory))
        {
            Clear-Variable -Name bicepDirectory
            Clear-Variable -Name bicepHandlerFile

            # First we need to have the bicep directory.
            if([string]::IsNullOrEmpty($bicepDirectory))
            {
                Write-Host "Get-BicepDirectory"
                $bicepDirectory = Get-BicepDirectory
            }
    
            # Second we need to have the bicep handler file in place.
            if([string]::IsNullOrEmpty($bicepHandlerFile))
            {
                $bicepHandlerFile = Get-BicepHandlerFile -bicepDirectory $bicepDirectory
            }
        } else {
            Clear-Variable -Name bicepHandlerFile

            # Else we need to have the bicep handler file in place.
            if([string]::IsNullOrEmpty($bicepHandlerFile))
            {
                $bicepHandlerFile = Get-BicepHandlerFile -bicepDirectory $bicepDirectory
            }
        }

        # Retrieve all available subscriptions
        $subscriptions = Get-AzSubscription | Where-Object {$_.State -eq 'Enabled' -and $_.HomeTenantId -eq $TenantId}

        # If this function is called but there is no bicep handler file, we require you to create one.
        do {
            if((Test-Path -Path $bicepHandlerFile) -eq $false)
            {
                Write-Warning "There is no bicephandler.json file in the directory: $bicepDirectory"
                Write-Warning "We will create one now, elsewise we cannot continue"
                Initialize-BicepDirectory -TenantId $TenantId -bicepDirectory $bicepDirectory
            }
        } while((Test-Path -Path $bicepHandlerFile) -eq $false)

        # This if statement will determine if the bicepHandlerFile scope is there or not. If so it will check if the scope is set or not by counting the items in the array.

        $bicepHandlerObject = Get-Content -Path $bicepHandlerFile | ConvertFrom-Json
        if(($bicepHandlerObject.scope).Count -gt 0)
        {
            $scopeIsSet = $true

            # Determine what kind of scope object we have, single object or an array?

            $objectType = ($bicepHandlerObject.scope.GetType()).BaseType.Name

            # Here we set empty lists and fill them later on with the property scope set at the bicephandler.json file.
            $managementGroups = @()
            $subs = @()
            $rgs = @()

            # In the case of an array we need to loop through all the objects.

            if($objectType -eq "Array")
            {
                # Start looping through the scope objects
                foreach($scope in $bicepHandlerObject.scope)
                {
                    # If it is a Management Group we need to retrieve the Management Group object and add it to the list.
                    $managementGroup = Get-AzManagementGroup -GroupName ($scope.managementGroup.Replace('/providers/Microsoft.Management/managementGroups/','')) -Expand -Recurse
                    if([string]::IsNullOrEmpty($managementGroup))
                    {
                        Write-Warning -Message "The Management Group: $($scope.managementGroup) does not exist."
                    }
                    else
                    {
                        $managementGroups += $managementGroup
                    }

                    # If there are child objects for subscriptions we need to retrieve them as well.
                    if(($scope.subscriptions).Count -gt 0)
                    {
                        # Loop through the subscriptions and add them to the list.
                        foreach($sub in $scope.subscriptions)
                        {
                            if(($sub | Get-Member).Name -eq 'id')
                            {
                                $subscription = $subscriptions | Where-Object {$_.Id -eq $sub.id -or $_.Name -eq $sub.id}
                            }
                            else
                            {
                                $subscription = $subscriptions | Where-Object {$_.Id -eq $sub -or $_.Name -eq $sub}
                            }
                            
                            if([string]::IsNullOrEmpty($subscription))
                            {
                                Write-Warning -Message "The subscription: $($sub) does not exist. Not storing it in the list."
                            }
                            else
                            {
                                $subs += $subscription
                            }

                            # If there are child objects for resource groups we need to retrieve them as well.
                            if(($sub.resourceGroups).Count -gt 0)
                            {
                                # Loop through the resource groups at the context of the subscription and add them to the list.
                                Set-AzContext -Subscription $subscription | Out-Null
                                $resourceGroups = Get-AzResourceGroup

                                # Loop through the resource groups and add them to the list.
                                foreach($rg in $sub.resourceGroups)
                                {
                                    $resourceGroup = $resourceGroups | Where-Object {$_.ResourceGroupName -eq $rg}
                                    if([string]::IsNullOrEmpty($resourceGroup))
                                    {
                                        Write-Warning -Message "The resource group: $($rg) does not exist. Not storing it in the list."
                                    }
                                    else
                                    {
                                        $rgs += $resourceGroup
                                    }
                                }
                            }
                            else
                            {
                                # Loop through the resource groups at the context of the subscription and add them to the list.
                                Set-AzContext -Subscription $subscription | Out-Null
                                $rgs += Get-AzResourceGroup
                            }
                        }
                    }
                }

                New-BicepDirectoryManagementGroups -ManagementGroups $managementGroups `
                -subscriptions $subs `
                -resourceGroups $rgs `
                -bicepDirectory $bicepDirectory `
                -StateOnly $StateOnly `
                -StacksOnly $StacksOnly
            }
            else # When there is no Array we have several scenarios for the bicephandler.json file to determine the scope.
            {
                # Check if the scope is a Management Group or a Subscription
                if(($bicepHandlerObject.scope | Get-Member).Name -eq 'managementGroups')
                {
                    # If there are child objects for management groups we need to retrieve them as well.
                    if(($bicepHandlerObject.scope.managementGroups).Count -gt 0)
                    {
                        # Loop through the management groups and add them to the list.
                        foreach($mg in $bicepHandlerObject.scope.managementGroups)
                        {
                            # If it is a Management Group we need to retrieve the Management Group object and add it to the list.
                            $managementGroup = Get-AzManagementGroup -GroupName ($mg.Replace('/providers/Microsoft.Management/managementGroups/','')) -Expand -Recurse
                            if([string]::IsNullOrEmpty($managementGroup))
                            {
                                Write-Warning -Message "The Management Group: $($scope.managementGroup) does not exist. Not storing it in the list."
                            }
                            else
                            {
                                $managementGroups += $managementGroup
                            }
                        }
                    }
                    else
                    {
                        Write-Warning -Message "No subscriptions are set."
                    }

                    if($bicepHandlerObject.managementGroupsOnly -eq $true)
                    {
                        New-BicepDirectoryManagementGroups -ManagementGroups $managementGroups `
                        -bicepDirectory $bicepDirectory `
                        -StateOnly $StateOnly `
                        -StacksOnly $StacksOnly `
                        -managementGroupsOnly $true
                    }
                    else
                    {
                        New-BicepDirectoryManagementGroups -ManagementGroups $managementGroups `
                        -bicepDirectory $bicepDirectory `
                        -StateOnly $StateOnly `
                        -StacksOnly $StacksOnly
                    }
                }
                if(($bicepHandlerObject.scope | Get-Member).Name -eq 'subscriptions')
                {
                    # If there are child objects for subscriptions we need to retrieve them as well.
                    if(($bicepHandlerObject.scope.subscriptions).Count -gt 0)
                    {
                        # Loop through the subscriptions and add them to the list.
                        foreach($sub in $bicepHandlerObject.scope.subscriptions)
                        {
                            if(($sub | Get-Member).Name -eq 'id')
                            {
                                $subscription = $subscriptions | Where-Object {$_.Id -eq $sub.id -or $_.Name -eq $sub.id}
                            }
                            else
                            {
                                $subscription = $subscriptions | Where-Object {$_.Id -eq $sub -or $_.Name -eq $sub}
                            }
                            
                            if([string]::IsNullOrEmpty($subscription))
                            {
                                Write-Warning -Message "The subscription: $($sub) does not exist. Not storing it in the list."
                            }
                            else
                            {
                                $subs += $subscription
                            }
                            
                            # If there are child objects for resource groups we need to retrieve them as well.
                            if(($sub.resourceGroups).Count -gt 0)
                            {
                                # Loop through the resource groups at the context of the subscription and add them to the list.
                                Set-AzContext -Subscription $subscription
                                $resourceGroups = Get-AzResourceGroup

                                # Loop through the resource groups and add them to the list.
                                foreach($rg in $sub.resourceGroups)
                                {
                                    $resourceGroup = $resourceGroups | Where-Object {$_.ResourceGroupName -eq $rg}
                                    if([string]::IsNullOrEmpty($resourceGroup))
                                    {
                                        Write-Warning -Message "The resource group: $($rg) does not exist. Not storing it in the list."
                                    }
                                    else
                                    {
                                        $rgs += $resourceGroup
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        Write-Warning -Message "No subscriptions are set."
                    }

                    New-BicepDirectorySubscriptions -subscriptions $subs `
                    -resourceGroups $rgs `
                    -bicepDirectory $bicepDirectory `
                    -StateOnly $StateOnly `
                    -StacksOnly $StacksOnly
                }
                if(($bicepHandlerObject.scope | Get-Member).Name -eq 'resourceGroups')
                {
                    # If there are child objects for subscriptions we need to retrieve them as well.
                    if(($bicepHandlerObject.scope.resourceGroups).Count -gt 0)
                    {
                        # Loop through the subscriptions and add them to the list.
                        foreach($subscription in $subscriptions)
                        {
                            # Loop through the resource groups at the context of the subscription and add them to the list.
                            Set-AzContext -Subscription $subscription | Out-Null
                            $resourceGroups = Get-AzResourceGroup

                            # Loop through the resource groups and add them to the list.
                            foreach($rg in $bicepHandlerObject.scope.resourceGroups)
                            {
                                $resourceGroup = $resourceGroups | Where-Object {$_.ResourceGroupName -eq $rg}
                                if([string]::IsNullOrEmpty($resourceGroup))
                                {
                                    Write-Warning -Message "The resource group: $($rg) does not exist in subscription: $($subscription.Name). Not storing it in the list."
                                }
                                else
                                {
                                    $rgs += $resourceGroup
                                    $subs += $subscription
                                }
                            }
                        }
                    }
                    else
                    {
                        Write-Warning -Message "No Resource Groups are set."
                    }

                    New-BicepDirectorySubscriptions -subscriptions $subs `
                    -resourceGroups $rgs `
                    -bicepDirectory $bicepDirectory `
                    -StateOnly $StateOnly `
                    -StacksOnly $StacksOnly
                }
            }

        } else {
            $scopeIsSet = $false

            if($SkipManagementGroups -eq $false)
            {
                # Retrieve all available Management Groups through the current logged in identity
                $managementGroups = Get-AzManagementGroup -GroupName $RootManagementGroup -Expand -Recurse
            }

            if(!([string]::IsNullOrEmpty($SubscriptionNames)))
            {
                $subs = @()
                foreach($name in $SubscriptionNames)
                {
                    foreach($azSub in $subscriptions)
                    {
                        if($azSub.Name -eq $name)
                        {
                            $subs += $azSub
                        }
                    }
                }
            }
            else
            {
                $subs = $subscriptions
            }

            # Determine if we need to create the folder structure on the highest scope-level Management Groups or one lower, Subscriptions.
            if($SkipManagementGroups -eq $true -and $scopeIsSet -eq $false)
            {
                New-BicepDirectorySubscriptions -subscriptions $subs `
                -bicepDirectory $bicepDirectory `
                -StateOnly $StateOnly `
                -StacksOnly $StacksOnly
            }
            
            if($SkipManagementGroups -eq $false -and $scopeIsSet -eq $false)
            {
                New-BicepDirectoryManagementGroups -ManagementGroups $managementGroups `
                -bicepDirectory $bicepDirectory `
                -StateOnly $StateOnly `
                -StacksOnly $StacksOnly
            }
        }

        if(!($bicepHandlerObject.directoryHash) -and !($bicepHandlerObject.files))
        {
            $bicepHandlerObject | Add-Member -MemberType NoteProperty -Name 'files' -Value @()

            ($bicepHandlerObject | ConvertTo-Json -Depth 25) | Out-File -FilePath $bicepHandlerFile -Force
        }

    } catch {
        throw $_.Exception.Message
    }
}
Export-ModuleMember -Function Build-BicepDirectory
#EndRegion - Build-BicepDirectory.ps1
#Region - Compare-BicepDirectory.ps1
function Compare-BicepDirectory {
    # ...
    param(
        [Parameter(Mandatory = $false)]
        [string]$TenantId = (Get-AzTenant | Select-Object -ExpandProperty Id),

        [Parameter(Mandatory = $false)]
        [string]$bicepDirectory = (Get-Location).Path,

        [Parameter(Mandatory = $false)]
        [string]$ContextSubId = ''
    )

    Deploy-BicepDirectory -TenantId $TenantId `
    -bicepDirectory $bicepDirectory `
    -ContextSubId $ContextSubId `
    -WhatIf
}
Export-ModuleMember -Function Compare-BicepDirectory
#EndRegion - Compare-BicepDirectory.ps1
#Region - Deploy-BicepDirectory.ps1
function Deploy-BicepDirectory {
    # ...
    param(
        [Parameter(Mandatory = $false)]
        [string]$TenantId = (Get-AzTenant | Select-Object -ExpandProperty Id),

        [Parameter(Mandatory = $false)]
        [string]$bicepDirectory = (Get-Location).Path,

        [Parameter(Mandatory = $false)]
        [string]$ContextSubId = '',

        [Parameter(Mandatory = $false)]
        [switch]$WhatIf
    )

    # Set the Tenant ID for the AzContext
    # Set-AzContext -TenantId $TenantId | Out-Null

    # Set the Custom Subscription ID for the AzContext when the default Subscription ID is invalid or wrong for retrieving management groups and/or subscriptions
    if(!([string]::IsNullOrEmpty($contextSubId)))
    {
        Set-AzContext -SubscriptionId $contextSubId | Out-Null
    }
    
    if(Test-Path -Path $bicepHandlerFile -ErrorAction SilentlyContinue)
    {
        Clear-Variable -Name bicepHandlerFile
    }

    # Else we need to have the bicep handler file in place.
    if([string]::IsNullOrEmpty($bicepHandlerFile))
    {
        $bicepHandlerFile = Get-BicepHandlerFile -bicepDirectory $bicepDirectory
    }
    
    # Retrieving all .bicep files in the current (state) folder
    $bicepFiles = Get-ChildItem -Path $bicepDirectory -Filter '*.bicep' -Force -Recurse

    # Store all results to return at the end
    $results = @()

    # Empty object where the bicep Directory current hashes will be stored in
    $stateObject = @()

    foreach($bicepFile in $bicepFiles)
    {
        $hashValue = Get-FileHash -Path $bicepFile.FullName -Algorithm SHA256 | Select-Object -ExpandProperty Hash

        $stateObject += @{
            path = $bicepFile.FullName | Resolve-Path -Relative
            hash = $hashValue
        }
    }

    # Get the current state to verify if there are any changes against the new state
    if(Test-Path -Path $bicepHandlerFile)
    {
        $currentState = Get-Content -Path $bicepHandlerFile | ConvertFrom-Json

        if($currentState.changes.Count -eq 0)
        {
            # First we check if files are changed

            foreach($currentStateItem in $currentState.files)
            {
                if(!(Test-Path -Path $currentStateItem.path))
                {
                    Write-Warning -Message "The file $($currentStateItem.path) has been removed or cannot be found in the bicephandler.json. We will auto cleanup the file from the bicephandler.json."

                    # Get the current State files and if only 1 remains we need to make sure it is an array

                    $filesArray = $currentState.files
                    $filesArray = $filesArray | Where-Object {$_.path -ne $currentStateItem.path}
                    
                    if($filesArray.Count -eq 1)
                    {
                        $currentState.files = @($filesArray)
                    }
                    else
                    {
                        $currentState.files = $filesArray
                    }

                    ## Output the result to the bicephandler.json
                    $currentState | ConvertTo-Json -Depth 25 | Out-File -FilePath $bicepHandlerFile -Force
                }
                else
                {
                    foreach($stateItem in $stateObject)
                    {
                        # If hash does not match it is changed, we match the path to verify if this is the same file and update the hash in the state
                        if($stateItem.hash -ne $currentStateItem.hash -and $stateItem.path -eq $currentStateItem.path)
                        {
                            Write-Host "Changes detected in $($stateItem.path)"
                            $currentStateItem.hash = $stateItem.hash

                            if($WhatIf)
                            {
                                $result = Deploy-BicepFile -ChangedFile $stateItem.path `
                                    -TenantId $currentState.tenantId `
                                    -autoCreateResourceGroups $currentState.autoCreateResourceGroups `
                                    -defaultRegion $currentState.defaultRegion `
                                    -WhatIf
                            }
                            else
                            {
                                $result = Deploy-BicepFile -ChangedFile $stateItem.path `
                                    -TenantId $currentState.tenantId `
                                    -autoCreateResourceGroups $currentState.autoCreateResourceGroups `
                                    -defaultRegion $currentState.defaultRegion

                                if($result.ProvisioningState -eq 'Succeeded')
                                {
                                    $currentState | ConvertTo-Json -Depth 25 | Out-File -FilePath $bicepHandlerFile -Force
                                }
                                else
                                {
                                    Write-Warning -Message "Deployment failed, please check the verbose logging in the console."
                                }
                            }

                            $result | Add-Member -MemberType NoteProperty -Name 'bicepFile' -Value $stateItem.path

                            $results += $result
                        }
        
                        # If hash does match but the file/path does not match it has been relocated, so we update the path
                        if($stateItem.hash -eq $currentStateItem.hash -and $stateItem.path -ne $currentStateItem.path)
                        {
                            $currentStateItem.path = $stateItem.path
                        }
                    }
                }
            }

            # Second we start checking if there are any new files added to the state

            foreach($stateItem in $stateObject)
            {
                if(!($currentState.files | Where-Object {$_.path -eq $stateItem.path}))
                {
                    $currentState.files += @(
                        @{
                            path = $stateItem.path
                            hash = $stateItem.hash
                        }
                    )

                    if($WhatIf)
                    {
                        $result = Deploy-BicepFile -ChangedFile $stateItem.path -TenantId $currentState.tenantId -autoCreateResourceGroups $currentState.autoCreateResourceGroups -WhatIf
                    }
                    else
                    {
                        $result = Deploy-BicepFile -ChangedFile $stateItem.path -TenantId $currentState.tenantId -autoCreateResourceGroups $currentState.autoCreateResourceGroups

                        if($result.ProvisioningState -eq 'Succeeded')
                        {
                            $currentState | ConvertTo-Json -Depth 25 | Out-File -FilePath $bicepHandlerFile -Force
                        }
                        
                        if($result.ProvisioningState -ne 'Succeeded' -and !($WhatIf))
                        {
                            Write-Warning -Message "Deployment failed, please check the verbose logging in the console."
                        }
                    }

                    $result | Add-Member -MemberType NoteProperty -Name 'bicepFile' -Value $stateItem.path
                    
                    $results += $result
                }
            }
        }
        else
        {
            foreach($change in $currentState.changes)
            {
                foreach($bicepFile in $change.order)
                {
                    if($currentState.files -like "*$bicepFile*")
                    {
                        # File is found, update the hash

                        $file = $currentState.files | Where-Object {$_.path -eq $bicepFile}
                        $file.hash = ($stateObject | Where-Object {$_.path -eq $bicepFile}).hash

                        # Do a WhatIf or actual deployment

                        if($WhatIf)
                        {
                            $result = Deploy-BicepFile -ChangedFile ($stateObject | Where-Object {$_.path -eq $bicepFile}).path `
                            -TenantId $currentState.tenantId `
                            -autoCreateResourceGroups $currentState.autoCreateResourceGroups `
                            -WhatIf
                        }
                        else
                        {
                            $result = Deploy-BicepFile -ChangedFile ($stateObject | Where-Object {$_.path -eq $bicepFile}).path `
                            -TenantId $currentState.tenantId `
                            -autoCreateResourceGroups $currentState.autoCreateResourceGroups

                            if($result.ProvisioningState -eq 'Succeeded')
                            {
                                # First we update the current change by removing the successfull deployed bicepFile

                                ## If the remaining order is of count 2 we still want to keep the array

                                if($change.order.Count -eq 2)
                                {
                                    $change.order = @($change.order | Where-Object {$_ -ne $bicepFile})
                                }
                                else
                                {
                                    $change.order = $change.order | Where-Object {$_ -ne $bicepFile}
                                }
                                
                                ## In the case no more files are left in the change we remove the change from the bicephandler.json

                                if($currentState.changes.Count -gt 1 -and ($change.order.Count -eq 0 -or $null -eq $change.order))
                                {
                                    $currentState.changes = $currentState.changes | Where-Object {$_.uniqueID -ne $change.uniqueID}
                                }

                                if($currentState.changes.Count -eq 1 -and ($change.order.Count -eq 0 -or $null -eq $change.order))
                                {
                                    $currentState.changes = @($currentState.changes | Where-Object {$_.uniqueID -ne $change.uniqueID})
                                }

                                # Remove the changes property as soon as it hits 0

                                if($currentState.changes.Count -eq 0)
                                {
                                    $currentState = $currentState | Select-Object -Property * -ExcludeProperty changes
                                }

                                # Here we update the bicephandler.json file with the new state

                                $currentState | ConvertTo-Json -Depth 25 | Out-File -FilePath $bicepHandlerFile -Force
                            }
                            
                            if($result.ProvisioningState -ne 'Succeeded' -and !($WhatIf))
                            {
                                Write-Warning -Message "Deployment failed, please check the verbose logging in the console."
                            }
                        }

                        $results += $result
                    }
                    else
                    {
                        # New files are found, update the bicephandler.json

                        foreach($newStateFile in $stateObject)
                        {
                            if($newStateFile.path -eq $bicepFile)
                            {
                                $currentState.files += @(
                                    @{
                                        path = $newStateFile.path
                                        hash = $newStateFile.hash
                                    }
                                )
                            }
                        }
                        
                        # Do a WhatIf or actual deployment
                        
                        if($WhatIf)
                        {
                            $result = Deploy-BicepFile -ChangedFile $bicepFile `
                            -TenantId $currentState.tenantId `
                            -autoCreateResourceGroups $currentState.autoCreateResourceGroups `
                            -WhatIf
                        }
                        else
                        {
                            $result = Deploy-BicepFile -ChangedFile $bicepFile `
                            -TenantId $currentState.tenantId `
                            -autoCreateResourceGroups $currentState.autoCreateResourceGroups

                            if($result.ProvisioningState -eq 'Succeeded')
                            {
                                # First we update the current change by removing the successfull deployed bicepFile

                                ## If the remaining order is of count 2 we still want to keep the array

                                if($change.order.Count -eq 2)
                                {
                                    $change.order = @($change.order | Where-Object {$_ -ne $bicepFile})
                                }
                                else
                                {
                                    $change.order = $change.order | Where-Object {$_ -ne $bicepFile}
                                }
                                
                                ## In the case no more files are left in the change we remove the change from the bicephandler.json

                                if($change.order.Count -eq 0)
                                {
                                    $currentState.changes = $currentState.changes | Where-Object {$_.uniqueID -ne $change.uniqueID}
                                }

                                # Remove the changes property as soon as it hits 0

                                if($currentState.changes.Count -eq 0)
                                {
                                    $currentState = $currentState | Select-Object -Property * -ExcludeProperty changes
                                }

                                # Here we update the bicephandler.json file with the new state

                                $currentState | ConvertTo-Json -Depth 25 | Out-File -FilePath $bicepHandlerFile -Force
                            }
                            
                            if($result.ProvisioningState -ne 'Succeeded' -and !($WhatIf))
                            {
                                Write-Warning -Message "Deployment failed, please check the verbose logging in the console."
                                Write-Warning -Message "Since we are using a custom installation order we will exit now!"
                                exit 0
                            }
                        }

                        $results += $result
                    }
                }
            }
        }
    }

    return $results
}
Export-ModuleMember -Function Deploy-BicepDirectory
#EndRegion - Deploy-BicepDirectory.ps1
#Region - Group-BicepDirectory.ps1
function Group-BicepDirectory {
    # ...
    param(
        [Parameter(Mandatory = $false)]
        [string]$TenantId = (Get-AzTenant | Select-Object -ExpandProperty Id),

        [Parameter(Mandatory = $false)]
        [string]$bicepDirectory = (Get-Location).Path,

        [Parameter(Mandatory = $false)]
        [string]$ContextSubId = '',

        [Parameter(Mandatory = $false)]
        [switch]$WhatIf
    )

    # Set the Tenant ID for the AzContext
    # Set-AzContext -TenantId $TenantId | Out-Null

    # Set the Custom Subscription ID for the AzContext when the default Subscription ID is invalid or wrong for retrieving management groups and/or subscriptions
    if(!([string]::IsNullOrEmpty($contextSubId)))
    {
        Set-AzContext -SubscriptionId $contextSubId | Out-Null
    }
    
    if(Test-Path -Path $bicepHandlerFile -ErrorAction SilentlyContinue)
    {
        Clear-Variable -Name bicepHandlerFile
    }

    # Else we need to have the bicep handler file in place.
    if([string]::IsNullOrEmpty($bicepHandlerFile))
    {
        $bicepHandlerFile = Get-BicepHandlerFile -bicepDirectory $bicepDirectory
    }
    
    # Retrieving all .bicep files in the current (state) folder
    $bicepFiles = Get-ChildItem -Path $bicepDirectory -Filter '*.bicep' -Force -Recurse

    # Store all Bicep Files in the installation order variable
    $installationOrder = @()
    $uniqueID = $($(New-Guid).Guid)

    # Prompt to verify we are going to use a custom installation order?
    $customInstallationOrder = Read-Host -Prompt "Do you want to use a custom installation order? (Y/N)"
    
    $uniqueIDPrompt = Read-Host -Prompt "Which unique ID do you want to use for this custom installation order? ($uniqueID)"
    if(!([string]::IsNullOrEmpty($uniqueIDPrompt)))
    {
        $uniqueID = $uniqueIDPrompt
    }

    if($customInstallationOrder -eq "Y")
    {
        # Empty object where the bicep Directory current hashes will be stored in
        $stateObject = @()

        foreach($bicepFile in $bicepFiles)
        {
            $hashValue = Get-FileHash -Path $bicepFile.FullName -Algorithm SHA256 | Select-Object -ExpandProperty Hash

            $stateObject += @{
                path = $bicepFile.FullName | Resolve-Path -Relative
                hash = $hashValue
            }
        }

        # Get the current state to verify if there are any changes against the new state
        if(Test-Path -Path $bicepHandlerFile)
        {
            $currentState = Get-Content -Path $bicepHandlerFile | ConvertFrom-Json

            # First we start checking if there are any files changed in the Bicep Directory

            foreach($currentStateItem in $currentState.files)
            {
                foreach($stateItem in $stateObject)
                {
                    # If hash does not match it is changed, we match the path to verify if this is the same file and update the hash in the state
                    if($stateItem.hash -ne $currentStateItem.hash -and $stateItem.path -eq $currentStateItem.path)
                    {
                        $installationOrder += $stateItem.path
                    }
                }
            }

            # Second we start checking if there are any new files added to the state

            foreach($stateItem in $stateObject)
            {
                if(!($currentState.files | Where-Object {$_.path -eq $stateItem.path}))
                {
                    $installationOrder += $stateItem.path
                }
            }
        }
        else
        {
            Write-Host "No bicephandler.json file found in the directory: $bicepDirectory"
        }

        if($installationOrder.Count -gt 0)
        {
            if([string]::IsNullOrEmpty($currentState.changes.uniqueID))
            {
                $comparison = $null
            }
            else
            {
                $comparison = Compare-Object -ReferenceObject $currentState.changes.uniqueID -DifferenceObject $uniqueID -IncludeEqual | Where-Object {$_.SideIndicator -eq "=="}
            }

            if($comparison.SideIndicator -eq "==")
            {
                foreach($change in $currentState.changes)
                {
                    if($change.uniqueID -eq $uniqueID)
                    {
                        # Prompt to verify we found a unique ID already present in the bicephandler.json file, do you want to update the changes for this change?
                        $overridePrompt = Read-Host -Prompt "Do you want to update the existing order? (Y/N)"

                        if($overridePrompt -eq "Y")
                        {
                            $orderObject = $change
                        }
                        else
                        {
                            Write-Host "Exiting ..."
                            exit 0
                        }
                    }
                }
            }
            else
            {
                foreach($change in $currentState.changes)
                {
                    $comparison = Compare-Object -ReferenceObject $change.order -DifferenceObject $installationOrder -IncludeEqual

                    if(!($comparison.SideIndicator -ne "=="))
                    {
                        Write-Host "Found the same changes in the bicephandler.json file, but with a different unique ID: $($change.uniqueID)"
                        Write-Host "Exiting ..."

                        exit 0
                    }
                }
            }
            
            Write-Host "The following files have been changed or added to the bicep directory: $bicepDirectory"
            Write-Host ""

            foreach($installationOrderItem in $installationOrder)
            {
                Write-Host $installationOrderItem
            }

            Write-Host ""
            Write-Host "The order for these changes are stored in the bicephandler.json file in the directory: $bicepDirectory"

            # Here we determine if the changes property exists and if it needs to be created or not
            
            if(($currentState | Get-Member -Name "changes") -and $orderObject.Count -eq 0)
            {
                $order = @{
                    uniqueID  = $uniqueID
                    order     = $installationOrder
                }
                $customOrderObject = [pscustomobject]$order

                $currentState.changes += @($customOrderObject)
            }
            
            if(!($currentState | Get-Member -Name "changes") -and $orderObject.Count -eq 0)
            {
                $order = @{
                    uniqueID  = $uniqueID
                    order     = $installationOrder
                }
                $customOrderObject = [pscustomobject]$order
                
                $currentState | Add-Member -MemberType NoteProperty -Name "changes" -Value @($customOrderObject)
            }
            
            if($orderObject.Count -gt 0)
            {
                $orderObject.Order = $installationOrder
            }
        }
        else
        {
            Write-Host "No changes detected in the bicep directory: $bicepDirectory"
        }

        $currentState | ConvertTo-Json -Depth 25 | Out-File -FilePath $bicepHandlerFile -Force
    }
    else
    {
        Write-Host "Exiting ..."
    }
}
Export-ModuleMember -Function Group-BicepDirectory
#EndRegion - Group-BicepDirectory.ps1
#Region - Initialize-BicepDirectory.ps1

function Initialize-BicepDirectory {
    # ...
    param(
        [Parameter(Mandatory = $false)]
        [string]$TenantId = (Get-AzTenant | Select-Object -ExpandProperty Id),

        [Parameter(Mandatory = $false)]
        [string]$bicepDirectory = ''
    )

    try {

        # Start with clean variables
        if(Test-Path -Path $bicepHandlerFile -ErrorAction SilentlyContinue)
        {
            Clear-Variable -Name bicepHandlerFile
        }

        # First we need to have the bicep directory.
        if([string]::IsNullOrEmpty($bicepDirectory))
        {
            $bicepDirectory = Get-BicepDirectory
        }
        
        # Next is to create the bicephandler.json so we can determine the folder structure later on. This file is a empty file.
        $bicepHandlerFile = Join-Path $bicepDirectory "bicephandler.json"

        # Check which default region we want to use for the deployments
        $defaultRegionPrompt = Read-Host -Prompt "What is your default deployment region? Default is westeurope"
        
        if(!([string]::IsNullOrEmpty($defaultRegionPrompt)))
        {
            $defaultRegion = $defaultRegionPrompt
        }
        else
        {
            $defaultRegion = "westeurope"
        }

        # Create the content for the bicephandler.json file
        $bicepHandlerContent = @{
            tenantId = $TenantId
            autoCreateResourceGroups = $true
            managementGroupsOnly = $false
            defaultRegion = $defaultRegion
        }

        ## We are verifying if the bicepHandlerFile is already there or not. And if not we will create it.
        if(Test-Path -Path $bicepHandlerFile)
        {
            Write-Warning "There already is a bicephandler.json file in the directory: $bicepDirectory"
        } else {
            New-Item -Path $bicepHandlerFile -ItemType File -Value (ConvertTo-Json -InputObject $bicepHandlerContent) -Force | Out-Null

            Write-Host "Created the bicephandler.json file in the directory: $bicepDirectory"
        }
    } catch {
        throw $_.Exception.Message
    }
}
Export-ModuleMember -Function Initialize-BicepDirectory
#EndRegion - Initialize-BicepDirectory.ps1
#Region - Show-BicepDirectory.ps1
function Show-BicepDirectory {
    # ...
    param(
        [Parameter(Mandatory = $false)]
        [string]$TenantId = (Get-AzTenant | Select-Object -ExpandProperty Id),

        [Parameter(Mandatory = $false)]
        [string]$bicepDirectory = (Get-Location).Path,

        [Parameter(Mandatory = $false)]
        [string]$ContextSubId = ''
    )

    # Write first log message
    Write-Host "Getting Bicep Directory Status..."

    # Set the Tenant ID for the AzContext
    Set-AzContext -TenantId $TenantId | Out-Null

    # Set the Custom Subscription ID for the AzContext when the default Subscription ID is invalid or wrong for retrieving management groups and/or subscriptions
    if(!([string]::IsNullOrEmpty($contextSubId)))
    {
        Set-AzContext -SubscriptionId $contextSubId | Out-Null
    }

    if(Test-Path -Path $bicepHandlerFile -ErrorAction SilentlyContinue)
    {
        Clear-Variable -Name bicepHandlerFile
    }

    # Else we need to have the bicep handler file in place.
    if([string]::IsNullOrEmpty($bicepHandlerFile))
    {
        $bicepHandlerFile = Get-BicepHandlerFile -bicepDirectory $bicepDirectory
    }
    
    # Define the branches diff by using the source and diff branch
    # The Diff Branch is holding all the changes. The Source Branch is the main branch holding all the production content.
    
    # Retrieving all .bicep files in the current bicep directory
    $bicepFiles = Get-ChildItem -Path $bicepDirectory -Filter '*.bicep' -Force -Recurse

    # Empty object where the state will be stored in
    $stateObject = @()

    foreach($bicepFile in $bicepFiles)
    {
        $hashValue = Get-FileHash -Path $bicepFile.FullName -Algorithm SHA256 | Select-Object -ExpandProperty Hash

        $stateObject += @{
            path = $bicepFile.FullName | Resolve-Path -Relative
            hash = $hashValue
        }
    }

    # A list of all new files
    $newItems = @()

    # A list of all changed files
    $changedItems = @()

    # Get the current state to verify if there are any changes against the new state
    if(Test-Path -Path $bicepHandlerFile)
    {
        $currentState = Get-Content -Path $bicepHandlerFile | ConvertFrom-Json

        # First we start checking which changes have been made to the state

        foreach($currentStateItem in $currentState.files)
        {
            foreach($stateItem in $stateObject)
            {
                # If hash does not match it is changed, we match the path to verify if this is the same file and update the hash in the state
                if($stateItem.hash -ne $currentStateItem.hash -and $stateItem.path -eq $currentStateItem.path)
                {
                    $changedItems += $currentStateItem.path
                }
            }
        }

        # Second we start checking if there are any new files added to the state

        foreach($stateItem in $stateObject)
        {
            if(!($currentState.files | Where-Object {$_.path -eq $stateItem.path}))
            {
                $newItems += $stateItem.path
            }
        }
    }

Clear-Host

Write-Host @"
                                     _ _ _
                                    | | | | |
                 __ _____ _ __| |_ ___| | |
                 \ \ /\ / / _ \| '__| __/ _ \ | |
                  \ V V / (_) | | | || __/ | |
                   \_/\_/ \___/|_| \__\___|_|_|


-------------------- Bicep Directory Status -------------------

Bicep Directory: $bicepDirectory

Changed Items:
"@

if($changedItems.Count -gt 0)
{
    foreach($item in $changedItems)
    {
        $item
    }
} else {
Write-Host @"
- No items found.
"@

}
Write-Host @"

New Items:
"@

if($newItems.Count -gt 0)
{
    foreach($item in $newItems)
    {
        $item
    }
} else {
Write-Host @"
- No items found.
"@

}
Write-Host @"

-------------------------------------------------------------
"@

}
Export-ModuleMember -Function Show-BicepDirectory
#EndRegion - Show-BicepDirectory.ps1
### --- PRIVATE FUNCTIONS --- ###
#Region - Deploy-BicepFile.ps1
function Deploy-BicepFile {
    # ...

    param(
        [Parameter(Mandatory = $false)]
        [string]$TenantId = (Get-AzTenant | Select-Object -ExpandProperty Id),

        [Parameter(Mandatory = $true)]
        [string]$changedFile,

        [Parameter(Mandatory = $false)]
        [string]$defaultRegion,

        [Parameter(Mandatory = $false)]
        [bool]$autoCreateResourceGroups = $true,

        [Parameter(Mandatory = $false)]
        [switch]$WhatIf
    )

    # Set the default Scope location
    $bicepFile = (Get-ChildItem -Path $changedFile).FullName

    # Set ErrorActionPreference to Stop to catch specific errors
    $ErrorActionPreference = 'Stop'

    Write-Host "Deployment for file: $bicepFile"

    if(Test-Path -Path $bicepFile)
    {
        # Here we determine if this deployment Type is a .state or .stack
        $stateOrStack = Split-Path -Path $bicepFile -Parent
        $deploymentName = (Split-Path -Path $bicepFile -Leaf) -replace "\W"
        $deploymentType = Split-Path -Path $stateOrStack -Leaf

        # Next we determine where the deployment Level the .state or .stack is located
        if($deploymentType -ne '.state' -and $deploymentType -ne '.stack')
        {
            $region = $deploymentType
            
            Write-Host "Found Region: $region"

            $deploymentType = Split-Path -Path $stateOrStack -Parent
            $deploymentType = Split-Path -Path $deploymentType -Leaf
        }

        # Here we determine at what level the deployment is going to be executed for the .state folders
        if($deploymentType -eq '.state')
        {
            $deploymentLevelParent = Split-Path -Path $stateOrStack -Parent
            $deploymentLevel = Split-Path -Path $deploymentLevelParent -Parent
            $deploymentLevel = Split-Path -Path $deploymentLevel -Leaf

            if(!([string]::IsNullOrEmpty($TenantId)))
            {
                # Retrieve all available Management Groups within the AzContext
                $managementGroups = Get-AzManagementGroup -ErrorAction SilentlyContinue
            
                # Retrieve all available Subscriptions within the AzContext
                $subscriptions = Get-AzSubscription | Where-Object {$_.HomeTenantId -eq $TenantId}
            }

            # Below is an example of a tenant wide deployment
            if(!($managementGroups.Name -like "$deploymentLevel") -and !($subscriptions.Name -like "$deploymentLevel") -and !([string]::IsNullOrEmpty($region)))
            {
                $deploymentName = ($deploymentName + "-" + $region)
                $deploymentName = $deploymentName.SubString(0, [System.Math]::Min(64, $deploymentName.Length))
                
                Write-Host "Deployment Type: tenant"
                Write-Host "Deployment Name: $deploymentName"

                if($WhatIf)
                {
                    try {
                        $deploymentObject = Get-AzTenantDeploymentWhatIfResult -Name $deploymentName `
                        -Location $region `
                        -TemplateFile $bicepFile
                    } catch {
                        $deploymentObject = $_.Exception.Message

                        return $deploymentObject
                    }

                    return $deploymentObject
                }
                else
                {
                    try {
                        $deploymentObject = New-AzTenantDeployment -Name $deploymentName `
                        -Location $region `
                        -TemplateFile $bicepFile `
                        -ErrorAction Continue
                    } catch {
                        $deploymentObject = $_.Exception.Message

                        return $deploymentObject
                    }

                    return $deploymentObject
                }
            }

            # Below is an example of a management group deployment
            if($managementGroups.Name -like "$deploymentLevel" -and !([string]::IsNullOrEmpty($region)))
            {
                $managementGroup = $managementGroups | Where-Object { $_.Name -eq $deploymentLevel }

                Write-Host "Management Group: $($managementGroup.DisplayName)"

                if($managementGroup -and !([string]::IsNullOrEmpty($region)))
                {
                    $deploymentName = ($deploymentName + "-" + $region + "-" + $deploymentLevel)
                    $deploymentName = $deploymentName.SubString(0, [System.Math]::Min(64, $deploymentName.Length))
                    
                    Write-Host "Deployment Name: $deploymentName"

                    if($WhatIf)
                    {
                        try {
                            $deploymentObject = Get-AzManagementGroupDeploymentWhatIfResult -Name $deploymentName `
                            -Location $region `
                            -ManagementGroupId $deploymentLevel `
                            -TemplateFile $bicepFile
                        } catch {
                            $deploymentObject = $_.Exception.Message

                            return $deploymentObject
                        }

                        return $deploymentObject
                    }
                    else
                    {
                        try {
                            $deploymentObject = New-AzManagementGroupDeployment -Name $deploymentName `
                            -Location $region `
                            -ManagementGroupId $deploymentLevel `
                            -TemplateFile $bicepFile
                        } catch {
                            $deploymentObject = $_.Exception.Message

                            return $deploymentObject
                        }
                        
                        return $deploymentObject
                    }
                }
            }

            # Below is an example of a subscription deployment
            if($subscriptions.Name -like "$deploymentLevel" -and !([string]::IsNullOrEmpty($region)))
            {
                $subscriptionId = Join-Path -Path (Split-Path -Path $deploymentLevelParent -Parent) -ChildPath '.id'
                $subscriptionId = Get-Content -Path $subscriptionId
                
                $subscription = $subscriptions | Where-Object { $_.Id -eq $subscriptionId -or $_.Name -eq $deploymentLevel }

                Write-Host "Subscription Name: $($subscription.Name)"
                Write-Host "Subscription ID: $($subscription.Id)"

                if($subscription -and !([string]::IsNullOrEmpty($region)))
                {
                    Set-AzContext -Subscription $subscription | Out-Null
                    
                    $deploymentName = ($deploymentName + "-" + $region + "-" + $subscription.Id)
                    $deploymentName = $deploymentName.SubString(0, [System.Math]::Min(64, $deploymentName.Length))
                    
                    Write-Host "Deployment Name: $deploymentName"

                    if($WhatIf)
                    {
                        try {
                            $deploymentObject = Get-AzDeploymentWhatIfResult -Name $deploymentName `
                            -Location $region `
                            -TemplateFile $bicepFile
                        } catch {
                            $deploymentObject = $_.Exception.Message

                            return $deploymentObject
                        }

                        return $deploymentObject
                    }
                    else
                    {
                        try {
                            $deploymentObject = New-AzSubscriptionDeployment -Name $deploymentName `
                            -Location $region `
                            -TemplateFile $bicepFile
                        } catch {
                            $deploymentObject = $_.Exception.Message

                            return $deploymentObject
                        }

                        return $deploymentObject
                    }
                }
            }

            # Below is an example of a resource group deployment
            if($managementGroups.Name -notlike "$deploymentLevel" -and $subscriptions.Name -notlike "$deploymentLevel" -and [string]::IsNullOrEmpty($region))
            {
                ## Resource Group Deployment Level
    
                $resourceGroup = Split-Path -Path $deploymentLevelParent -Leaf
                $resourceGroupFolder = $deploymentLevelParent
                
                Write-Host "Resource Group: $resourceGroup"
    
                $subscriptionFolder = Split-Path -Path $deploymentLevelParent -Parent
                $deploymentLevel = Split-Path -Path $deploymentLevelParent -Parent
                $deploymentLevel = Split-Path -Path $deploymentLevel -Leaf
    
                ## Since the previous check failed we must have a Resource Group Deployment Level
                ## So this next check must pass, else we cannot determine the Deployment Level
    
                if($subscriptions.Name -like "*$deploymentLevel")
                {
                    ### We have a Subscription Deployment Level
    
                    $subscriptionId = Join-Path -Path $subscriptionFolder -ChildPath '.id'
                    $subscriptionId = Get-Content -Path $subscriptionId
                    
                    $subscription = $subscriptions | Where-Object { $_.Id -eq $subscriptionId -or $_.Name -eq $deploymentLevel }
    
                    if($subscription)
                    {
                        Set-AzContext -Subscription $subscription | Out-Null
                        
                        if($WhatIf)
                        {
                            try {

                                ## Resource Group Validation
                                #region
                                #
                                # Here we determine if the Azure Resource Group exists so we can compare the deployment. Else we exit the deployment process.
                                #
                                
                                    $AzResourceGroup = Get-AzResourceGroup -Name $resourceGroup -ErrorAction SilentlyContinue
                                    if([string]::IsNullOrEmpty($AzResourceGroup) -and $autoCreateResourceGroups -eq $true)
                                    {
                                        Write-Host "No Resource Group can be found ..."
                                        if(!([string]::IsNullOrEmpty($defaultRegion)))
                                        {
                                            $location = $defaultRegion
                                        }

                                        if(Test-Path -Path "$resourceGroupFolder/.location")
                                        {
                                            $location = Get-Content -Path "$resourceGroupFolder/.location"
                                        }

                                        New-AzResourceGroup -Name $resourceGroup `
                                        -Location $location `
                                        -Confirm:$false | Out-Null

                                        Write-Host "Waiting 5 seconds for the resource group to be created and processed in Azure..."
                                        Start-Sleep -Seconds 5
                                    }

                                    ## Check again if the resource group exists

                                    $AzResourceGroup = Get-AzResourceGroup -Name $resourceGroup -ErrorAction SilentlyContinue
                                    if([string]::IsNullOrEmpty($AzResourceGroup))
                                    {
                                        $deploymentString = "Resource Group: $resourceGroup does not exist and cannot be created. Exiting the deployment process."
                                        
                                        Write-Host $deploymentString

                                        $deploymentObject = $deploymentString
                                    }
                                    else
                                    {
                                        $deploymentObject = Get-AzResourceGroupDeploymentWhatIfResult -ResourceGroupName $resourceGroup `
                                        -TemplateFile $bicepFile `
                                        -ResultFormat FullResourcePayloads `
                                        -Verbose
                                    }

                                #endregion
                                ##
                                
                            } catch {
                                $deploymentObject = $_.Exception.Message

                                return $deploymentObject
                            }
    
                            return $deploymentObject
                        }
                        else
                        {
                            try {

                                ## Resource Group Validation
                                #region
                                #
                                # Here we determine if the Azure Resource Group exists and if not we create one if the autoCreateResourceGroups settings in the bicephandler.json is set to $true
                                # After the above we again verify if the resource group exists so that the deployment can take place. Else we exit the deployment process.
                                #
                                
                                    $AzResourceGroup = Get-AzResourceGroup -Name $resourceGroup -ErrorAction SilentlyContinue
                                    if([string]::IsNullOrEmpty($AzResourceGroup) -and $autoCreateResourceGroups -eq $true)
                                    {
                                        Write-Host "No Resource Group can be found ..."
                                        if(!([string]::IsNullOrEmpty($defaultRegion)))
                                        {
                                            $location = $defaultRegion
                                        }

                                        if(Test-Path -Path "$resourceGroupFolder/.location")
                                        {
                                            $location = Get-Content -Path "$resourceGroupFolder/.location"
                                        }

                                        New-AzResourceGroup -Name $resourceGroup `
                                        -Location $location `
                                        -Confirm:$false | Out-Null

                                        Write-Host "Waiting 5 seconds for the resource group to be created and processed in Azure..."
                                        Start-Sleep -Seconds 5
                                    }

                                    ## Check again if the resource group exists

                                    $AzResourceGroup = Get-AzResourceGroup -Name $resourceGroup -ErrorAction SilentlyContinue
                                    if([string]::IsNullOrEmpty($AzResourceGroup))
                                    {
                                        $deploymentString = "Resource Group: $resourceGroup does not exist and cannot be created. Exiting the deployment process."
                                        
                                        Write-Host $deploymentString

                                        $deploymentObject = $deploymentString
                                    }
                                    else
                                    {
                                        $deploymentObject = New-AzResourceGroupDeployment -ResourceGroupName $resourceGroup `
                                        -TemplateFile $bicepFile `
                                        -Verbose
                                    }

                                #endregion
                                ##
                            
                            } catch {
                                $deploymentObject = $_.Exception.Message
                                
                                return $deploymentObject
                            }
                            
                            return $deploymentObject
                        }
                    }
                }
            }
        }

        # Here we determine at what level the deployment is going to be executed for the .stack folders
        if($deploymentType -eq '.stack')
        {

            $deploymentLevelParent = Split-Path -Path $stateOrStack -Parent
            $deploymentLevel = Split-Path -Path $deploymentLevelParent -Parent
            $deploymentLevel = Split-Path -Path $deploymentLevel -Leaf

            if($managementGroups.Name -like "*$deploymentLevel" -or $subscriptions.Name -like "*$deploymentLevel")
            {
                # We have a Management Group or Subscription Deployment Level

                # Below is an example of a management group deployment
                if($managementGroups.Name -like "$deploymentLevel")
                {
                    $managementGroup = $managementGroups | Where-Object { $_.Name -eq $deploymentLevel }

                    if($managementGroup)
                    {
                        $deploymentName = ($deploymentName + "-" + $region + "-" + $deploymentLevel)
                        $deploymentName = $deploymentName.SubString(0, [System.Math]::Min(64, $deploymentName.Length))
                        
                        New-AzManagementGroupDeployment -Name $deploymentName `
                        -Location $region `
                        -ManagementGroupId $deploymentLevel `
                        -TemplateFile $bicepFile
                    }
                }

                # Below is an example of a subscription deployment
                if($subscriptions.Name -like "$deploymentLevel")
                {
                    $subscriptionId = Join-Path -Path (Split-Path -Path $deploymentLevelParent -Parent) -ChildPath '.id'
                    $subscriptionId = Get-Content -Path $subscriptionId
                    
                    $subscription = $subscriptions | Where-Object { $_.Id -eq $subscriptionId -or $_.Name -eq $deploymentLevel }

                    if($subscription)
                    {
                        Set-AzContext -Subscription $subscription
                        
                        $deploymentName = ($deploymentName + "-" + $region + "-" + $subscription.Id)
                        $deploymentName = $deploymentName.SubString(0, [System.Math]::Min(64, $deploymentName.Length))
                        
                        New-AzSubscriptionDeployment -Name $deploymentName `
                        -Location $region `
                        -TemplateFile $bicepFile
                    }
                }
            }
            else
            {
                # Resource Group Deployment Level
                
                $resourceGroup = Split-Path -Path $deploymentLevelParent -Parent
                $resourceGroup = Split-Path -Path $resourceGroup -Leaf
                
                $deploymentLevel = Split-Path -Path $deploymentLevelParent -Parent
                $subscriptionFolder = Split-Path -Path $deploymentLevel -Parent
                $deploymentLevel = Split-Path -Path $deploymentLevel -Parent
                $deploymentLevel = Split-Path -Path $deploymentLevel -Leaf

                # Since the previous check failed we must have a Resource Group Deployment Level
                # So this next check must pass, else we cannot determine the Deployment Level

                if($subscriptions.Name -like "*$deploymentLevel")
                {
                    # We have a Subscription Deployment Level

                    $subscriptionId = Join-Path -Path $subscriptionFolder -ChildPath '.id'
                    $subscriptionId = Get-Content -Path $subscriptionId
                    
                    $subscription = $subscriptions | Where-Object { $_.Id -eq $subscriptionId -or $_.Name -eq $deploymentLevel }

                    if($subscription)
                    {
                        Set-AzContext -Subscription $subscription

                        $resourceGroups = Get-AzResourceGroup
                        $resourceGroup = $resourceGroups | Where-Object { $_.ResourceGroupName -eq $resourceGroup }
                        if($resourceGroup)
                        {
                            New-AzResourceGroupDeployment -ResourceGroupName $resourceGroup.ResourceGroupName `
                            -TemplateFile $bicepFile
                        }
                    }
                }
            }
        }
    }
    else
    {
        Write-Host "Bicep file not found: $bicepFile"
    }
}
#EndRegion - Deploy-BicepFile.ps1
#Region - Get-BicepDirectory.ps1
function Get-BicepDirectory {
    # ...
    try {

        # Check if current Directory is fine for the bicep directory.
        $bicepDirectory = (Join-Path -Path ((Get-Location).Path) -ChildPath (Get-AzTenant | Where-Object {$_.Id -eq (Get-AzTenant | Select-Object -ExpandProperty Id)} | Select-Object -ExpandProperty Name))
        $currentDirectory = Read-Host -Prompt "Do you want to use this directory to handle the BICEP deployments: $bicepDirectory"

        if(!([string]::IsNullOrEmpty($currentDirectory)))
        {
            $bicepDirectory = $currentDirectory
        }

        Write-Host "Current Directory for your BICEP deployments: $bicepDirectory"
        
        return $bicepDirectory
        
    } catch {
        throw $_.Exception.Message
    }
}
#EndRegion - Get-BicepDirectory.ps1
#Region - Get-BicepHandlerFile.ps1
function Get-BicepHandlerFile {
    # ...
    param(
        [Parameter(Mandatory = $true)]
        [string]$bicepDirectory
    )
    
    try {
        $bicepHandlerFile = Join-Path $bicepDirectory "bicephandler.json"

        return $bicepHandlerFile
        
    } catch {
        throw $_.Exception.Message
    }
}
#EndRegion - Get-BicepHandlerFile.ps1
#Region - New-BicepDirectoryManagementGroups.ps1
function New-BicepDirectoryManagementGroups {
    # ...
    param(
        [Parameter(Mandatory = $true)]
        [object]$managementGroups,

        [Parameter(Mandatory = $false)]
        [object]$subscriptions,

        [Parameter(Mandatory = $false)]
        [object]$resourceGroups,

        [Parameter(Mandatory = $true)]
        [string]$bicepDirectory,

        [Parameter(Mandatory = $true)]
        [bool]$StateOnly,

        [Parameter(Mandatory = $true)]
        [bool]$StacksOnly,

        [Parameter(Mandatory = $false)]
        [bool]$managementGroupsOnly = $false
    )

    try {
        # Loop through the management groups and create folders for each one and its children recursively
        foreach ($mg in $managementGroups) {
            Write-Progress -Id 0 "Processing $($mg.DisplayName)"

            $mgFolderPath = Join-Path -Path $bicepDirectory -ChildPath $mg.Name
            if(!(Test-Path -Path $mgFolderPath))
            {
                New-Item -ItemType Directory -Path $mgFolderPath | Out-Null
        
                New-StateFoldersandFiles -StacksOnly $StacksOnly -StateOnly $StateOnly -folderPath $mgFolderPath
            }
        
            # Recursively create folders for the children of the management group
            foreach ($child in $mg.Children) {
                Write-Progress -Id 1 -ParentId 0 "Processing $($child.DisplayName)"
                if($child.Type -eq 'Microsoft.Management/managementGroups')
                {
                    $childFolderPath = Join-Path -Path $mgFolderPath -ChildPath $child.Name
                    if(!(Test-Path -Path $childFolderPath))
                    {
                        New-Item -ItemType Directory -Path $childFolderPath | Out-Null
                        
                        New-StateFoldersandFiles -StacksOnly $StacksOnly -StateOnly $StateOnly -folderPath $childFolderPath
                    }

                    New-ManagementGroupFolders -ManagementGroup $child -ParentFolderPath $childFolderPath -StacksOnly $StacksOnly -StateOnly $StateOnly
                }
        
                if($child.Type -eq '/subscriptions' -and $managementGroupsOnly -eq $false)
                {
                    if($subscriptions.Count -gt 0 -and $subscriptions.Id -like "*$($child.Name)*")
                    {
                        Set-AzContext -SubscriptionId $child.Name | Out-Null

                        $subFolderPath = Join-Path -Path $mgFolderPath -ChildPath $child.DisplayName
                        if(!(Test-Path -Path $subFolderPath))
                        {
                            New-Item -ItemType Directory -Path $subFolderPath | Out-Null
                            
                            New-StateFoldersandFiles -StacksOnly $StacksOnly -StateOnly $StateOnly -folderPath $subFolderPath
                            
                            $subFolderId = Join-Path -Path $subFolderPath -ChildPath '.id'
                            New-Item -ItemType File -Path $subFolderId -Value $child.Name -Force | Out-Null
                        }
    
                        $rgs = Get-AzResourceGroup
                        foreach($rg in $rgs)
                        {
                            if($resourceGroups.ResourceGroupName -like "*$($rg.ResourceGroupName)*")
                            {
                                Write-Progress -Id 2 -ParentId 1 "Step $($rg.ResourceGroupName)"
                                $resourceGroupFolderPath = Join-Path -Path $subFolderPath -ChildPath $rg.ResourceGroupName
                                if(!(Test-Path -Path $resourceGroupFolderPath))
                                {
                                    New-Item -ItemType Directory -Path $resourceGroupFolderPath | Out-Null
                                    
                                    New-StateFoldersandFiles -StacksOnly $StacksOnly -StateOnly $StateOnly -folderPath $resourceGroupFolderPath
                                }
                            }
                        }
                    }
                    
                    if([string]::IsNullOrEmpty($subscriptions))
                    {
                        Set-AzContext -SubscriptionId $child.Name | Out-Null

                        $subFolderPath = Join-Path -Path $mgFolderPath -ChildPath $child.DisplayName
                        if(!(Test-Path -Path $subFolderPath))
                        {
                            New-Item -ItemType Directory -Path $subFolderPath | Out-Null
                            
                            New-StateFoldersandFiles -StacksOnly $StacksOnly -StateOnly $StateOnly -folderPath $subFolderPath
                            
                            $subFolderId = Join-Path -Path $subFolderPath -ChildPath '.id'
                            New-Item -ItemType File -Path $subFolderId -Value $child.Name -Force | Out-Null
                        }
    
                        $rgs = Get-AzResourceGroup
                        foreach($rg in $rgs)
                        {
                            Write-Progress -Id 2 -ParentId 1 "Step $($rg.ResourceGroupName)"
                            $resourceGroupFolderPath = Join-Path -Path $subFolderPath -ChildPath $rg.ResourceGroupName
                            if(!(Test-Path -Path $resourceGroupFolderPath))
                            {
                                New-Item -ItemType Directory -Path $resourceGroupFolderPath | Out-Null
                                
                                New-StateFoldersandFiles -StacksOnly $StacksOnly -StateOnly $StateOnly -folderPath $resourceGroupFolderPath
                            }
                        }
                    }
                }
            }
        }
    } catch {
        throw $_.Exception.Message
    }
}
#EndRegion - New-BicepDirectoryManagementGroups.ps1
#Region - New-BicepDirectorySubscriptions.ps1
function New-BicepDirectorySubscriptions {
    # ...
    param(
        [Parameter(Mandatory = $true)]
        [object]$subscriptions,

        [Parameter(Mandatory = $false)]
        [object]$resourceGroups,

        [Parameter(Mandatory = $true)]
        [string]$bicepDirectory,

        [Parameter(Mandatory = $true)]
        [bool]$StateOnly,

        [Parameter(Mandatory = $true)]
        [bool]$StacksOnly
    )

    try {
        # Loop through the management groups and create folders for each one and its children recursively
        foreach ($sub in $subscriptions) {
            Write-Progress -Id 0 "Processing $($sub.Name)"
            $subFolderPath = Join-Path -Path $bicepDirectory -ChildPath $sub.Name
            if(!(Test-Path -Path $subFolderPath))
            {
                New-Item -ItemType Directory -Path $subFolderPath | Out-Null
        
                New-StateFoldersandFiles -StacksOnly $StacksOnly -StateOnly $StateOnly -folderPath $subFolderPath

                $subFolderId = Join-Path -Path $subFolderPath -ChildPath '.id'
                New-Item -ItemType File -Path $subFolderId -Value $sub.Id -Force | Out-Null
            }
            
            Set-AzContext -Subscription $sub | Out-Null

            if($resourceGroups.Count -eq 0)
            {
                $resourceGroups = Get-AzResourceGroup
            }

            foreach($resourceGroup in $resourceGroups)
            {
                if($resourceGroup.ResourceId -like "*$($sub.Id)*")
                {
                    Write-Progress -Id 1 -ParentId 0 "Step $($resourceGroup.ResourceGroupName)"
                    $rgFolderPath = Join-Path -Path $subFolderPath -ChildPath $resourceGroup.ResourceGroupName
                    if(!(Test-Path -Path $rgFolderPath))
                    {
                        New-Item -ItemType Directory -Path $rgFolderPath | Out-Null
                        
                        New-StateFoldersandFiles -StacksOnly $StacksOnly -StateOnly $StateOnly -folderPath $rgFolderPath
                    }
                }
            }
        }
    } catch {
        throw $_.Exception.Message
    }
}
#EndRegion - New-BicepDirectorySubscriptions.ps1
#Region - New-ManagementGroupFolders.ps1
function New-ManagementGroupFolders {
    param (
        [Parameter(Mandatory = $true)]
        [object]$ManagementGroup,

        [Parameter(Mandatory = $true)]
        [string]$ParentFolderPath,

        [Parameter(Mandatory = $false)]
        [bool]$StacksOnly,

        [Parameter(Mandatory = $false)]
        [bool]$StateOnly
    )

    # Recursively create folders for the children of the management group, subscriptions and resource groups.
    # Subscription and Resource Groups can be excluded.
    foreach ($child in $ManagementGroup.Children) {
        if($child.Type -eq 'Microsoft.Management/managementGroups')
        {
            $childFolderPath = Join-Path -Path $ParentFolderPath -ChildPath $child.Name
            if(!(Test-Path -Path $childFolderPath))
            {
                New-Item -ItemType Directory -Path $childFolderPath | Out-Null
                
                New-StateFoldersandFiles -StacksOnly $StacksOnly -StateOnly $StateOnly -folderPath $childFolderPath
            }
        }

        if($child.Type -eq '/subscriptions')
        {
            Set-AzContext -SubscriptionId $child.Name | Out-Null

            $subFolderPath = Join-Path -Path $ParentFolderPath -ChildPath $child.DisplayName
            if(!(Test-Path -Path $subFolderPath))
            {
                New-Item -ItemType Directory -Path $subFolderPath | Out-Null
                
                New-StateFoldersandFiles -StacksOnly $StacksOnly -StateOnly $StateOnly -folderPath $subFolderPath

                $subFolderId = Join-Path -Path $subFolderPath -ChildPath '.id'
                New-Item -ItemType File -Path $subFolderId -Value $child.Name -Force | Out-Null
            }

            $resourceGroups = Get-AzResourceGroup
            foreach($resourceGroup in $resourceGroups)
            {
                $resourceGroupFolderPath = Join-Path -Path $subFolderPath -ChildPath $resourceGroup.ResourceGroupName
                if(!(Test-Path -Path $resourceGroupFolderPath))
                {
                    New-Item -ItemType Directory -Path $resourceGroupFolderPath | Out-Null
                    
                    New-StateFoldersandFiles -StacksOnly $StacksOnly -StateOnly $StateOnly -folderPath $resourceGroupFolderPath
                }
            }
        }
        
        New-ManagementGroupFolders -ManagementGroup $child -ParentFolderPath $childFolderPath
    }
}
#EndRegion - New-ManagementGroupFolders.ps1
#Region - New-StateFoldersandFiles.ps1
function New-StateFoldersandFiles {
    param (
        [Parameter(Mandatory = $true)]
        [string]$folderPath,

        [Parameter(Mandatory = $false)]
        [bool]$StacksOnly,

        [Parameter(Mandatory = $false)]
        [bool]$StateOnly
    )

    if($StacksOnly -eq $false)
    {
        $stateFolderPath = Join-Path -Path $folderPath -ChildPath '.state'
        New-Item -ItemType Directory -Path $stateFolderPath | Out-Null
    
        $dummyFolderPath = Join-Path -Path $stateFolderPath -ChildPath '.gitkeep'
        New-Item -ItemType File -Path $dummyFolderPath | Out-Null
    }

    if($StateOnly -eq $false)
    {
        $stackFolderPath = Join-Path -Path $folderPath -ChildPath '.stack'
        New-Item -ItemType Directory -Path $stackFolderPath | Out-Null
    
        $dummyFolderPath = Join-Path -Path $stackFolderPath -ChildPath '.gitkeep'
        New-Item -ItemType File -Path $dummyFolderPath | Out-Null
    }
}
#EndRegion - New-StateFoldersandFiles.ps1