bdh.psm1
### --- PUBLIC FUNCTIONS --- ### #Region - Build-BicepDirectory.ps1 function Build-BicepDirectory { # ... param( [Parameter(Mandatory = $true)] [string]$TenantId, [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)) { if(!([string]::IsNullOrEmpty($bicepHandlerFile))) { Clear-Variable -Name bicepHandlerFile } # First we need to have the bicep directory. Write-Host "Get-BicepDirectory" $bicepDirectory = Get-BicepDirectory -TenantId $TenantId # Second we need to have the bicep handler file in place. if([string]::IsNullOrEmpty($bicepHandlerFile)) { $bicepHandlerFile = Get-BicepHandlerFile -bicepDirectory $bicepDirectory } } else { if(!([string]::IsNullOrEmpty($bicepHandlerFile))) { 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 no subscriptions found we cannot continue if($null -eq $subscriptions) { Write-Error "No subscriptions found for tenant: $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, [Parameter(Mandatory = $false)] [string]$bicepDirectory = (Get-Location).Path, [Parameter(Mandatory = $false)] [string]$ContextSubId = '' ) # Get Tenant ID based on the bicephandler.json file if([string]::IsNullOrEmpty($TenantId)) { $TenantId = Get-Content -Path "$bicepDirectory/bicephandler.json" | ConvertFrom-Json | Select-Object -ExpandProperty tenantId } 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, [Parameter(Mandatory = $false)] [string]$bicepDirectory = (Get-Location).Path, [Parameter(Mandatory = $false)] [string]$ContextSubId = '', [Parameter(Mandatory = $false)] [switch]$WhatIf ) # Get Tenant ID based on the bicephandler.json file if([string]::IsNullOrEmpty($TenantId)) { $TenantId = Get-Content -Path "$bicepDirectory/bicephandler.json" | ConvertFrom-Json | Select-Object -ExpandProperty tenantId } # 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(!([string]::IsNullOrEmpty($bicepHandlerFile))) { 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, [Parameter(Mandatory = $false)] [string]$bicepDirectory = (Get-Location).Path, [Parameter(Mandatory = $false)] [string]$ContextSubId = '', [Parameter(Mandatory = $false)] [switch]$WhatIf ) # Get Tenant ID based on the bicephandler.json file if([string]::IsNullOrEmpty($TenantId)) { $TenantId = Get-Content -Path "$bicepDirectory/bicephandler.json" | ConvertFrom-Json | Select-Object -ExpandProperty tenantId } # 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(!([string]::IsNullOrEmpty($bicepHandlerFile))) { 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 - Show-BicepDirectory.ps1 function Show-BicepDirectory { # ... param( [Parameter(Mandatory = $false)] [string]$TenantId, [Parameter(Mandatory = $false)] [string]$bicepDirectory = (Get-Location).Path, [Parameter(Mandatory = $false)] [string]$ContextSubId = '' ) # Get Tenant ID based on the bicephandler.json file if([string]::IsNullOrEmpty($TenantId)) { $TenantId = Get-Content -Path "$bicepDirectory/bicephandler.json" | ConvertFrom-Json | Select-Object -ExpandProperty tenantId } # 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(!([string]::IsNullOrEmpty($bicepHandlerFile))) { 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 | Out-Null $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 | Out-Null $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 { # ... param( [Parameter(Mandatory = $true)] [string]$TenantId ) 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 $TenantId} | 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 - 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(!([string]::IsNullOrEmpty($bicepHandlerFile))) { 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 } } #EndRegion - Initialize-BicepDirectory.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 |