orchestration/scripts/Invoke-Helper.ps1 (861 lines of code) (raw):

# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. <# .SYNOPSIS The powershell script contains definitions of helper functions to be used across all the deployment scripts. #> #variables $varMoveSubscriptionBicepFilePath = '..\moveSubscription\moveSubscription.bicep' $varAzPortalLink = 'https://portal.azure.com' $varDonotRetryErrorCodes = New-Object Collections.Generic.List[String] $vartimeStamp = Get-Date -Format "yyyyMMddTHHmmssffff" $varTenantDeployment = 'tenant' $varManagementGroupDeployment = 'managementGroup' $varParameters = @{} #variables to support incremental delay for azure resource validation checks (All time in seconds) $varMaxWaitTimeResourceExistsCheck = 1800 $varStartIntervalResourceExistsCheck = 5 $varMaxIntervalResourceExistsCheck = 60 $varIntervalMultiplierResourceExistsCheck = 5 #variables to support retry for known transient errors $varMaxRetryAttemptTransientErrorRetry = 6 $varRetryWaitTimeTransientErrorRetry = 60 $varReservedIpAddressRange = @( "224.0.0.0/4", "255.255.255.255/32", "127.0.0.0/8", "169.254.0.0/16", "168.63.129.16/32", "192.168.1.0", "192.168.1.1", "192.168.1.2", "192.168.1.3", "192.168.1.255" ) <# .Description Login to Azure portal #> function Enter-Login { Write-Information ">>> Initiating a login" -InformationAction Continue Connect-AzAccount } <# .Description Get details of user #> function Get-SignedInUser { $varSignedInUserDetails = Get-AzADUser -SignedIn if (!$varSignedInUserDetails) { Write-Information ">>> No logged in user found." -InformationAction Continue } else { return $varSignedInUserDetails.UserPrincipalName } return $null } <# .Description Confirm the user is owner at the root scope #> function Confirm-UserOwnerPermission { if ($null -ne $varSignedInUser) { Write-Information "`n>>> Checking the owner permissions for user: $varSignedInUser at '/' scope" -InformationAction Continue $varRetrieveOwnerPermissions = Get-AzRoleAssignment ` -SignInName $varSignedInUser ` -Scope "/" ` -RoleDefinitionName "Owner" if ($varRetrieveOwnerPermissions.RoleDefinitionName -ne "Owner") { Write-Information "Signed in user: $varSignedInUser does not have owner permission to the root '/' scope." -InformationAction Continue return $false } else { Write-Information "Signed in user: $varSignedInUser has owner permissions at the root '/' scope." -InformationAction Continue } return $true } else { Write-Error "Logged in user details are empty." -ErrorAction Stop } } <# .Description Assigns the user with Owner permissions at the root scope #> function Set-UserOwnerPermission { Write-Information ">>> Assigning user with Owner permissions." -InformationAction Continue # Assign "Owner" role to the signed-in user at the root scope "/" New-AzRoleAssignment ` -SignInName $varSignedInUser ` -Scope "/" ` -RoleDefinitionName "Owner" } <# .Description Confirm the user is elevated at the root scope. #> function Confirm-UserElevated { if ($null -ne $varSignedInUser) { Write-Information "`n>>> Checking user $varSignedInUser is elevated at '/' scope" -InformationAction Continue $varRetrieveUAAPermissions = Get-AzRoleAssignment ` -SignInName $varSignedInUser ` -Scope "/" ` -RoleDefinitionName "User Access Administrator" if ($varRetrieveUAAPermissions.RoleDefinitionName -ne "User Access Administrator") { Write-Information "Signed in user: $varSignedInUser is not elevated at '/' scope" -InformationAction Continue return $false } Write-Information "Signed in user: $varSignedInUser is elevated at '/' scope" -InformationAction Continue return $true } else { Write-Error "Logged in user details are empty." -ErrorAction Stop } } <# .Description Assigns the user with User Access Administrator permissions at the root scope #> function Set-UserElevatePermissions { Write-Information ">>> Elevating user at root scope." -InformationAction Continue # Elevate access to all Azure Resources for a Global Administrator Invoke-AzRestMethod -Method Post -Uri "https://management.azure.com/providers/Microsoft.Authorization/elevateAccess?api-version=2016-07-01" } <# .Description Confirm the user is elevated at the root scope. #> function Invoke-UserPermissionsConfirmation { param($parPermissionType) Write-Information "`n>>> Confirming user's permissions. This might trigger an auto log out and require the user to login back in a few times" -InformationAction Continue $varUserPermissionsElevated = $false $varWaitTime = 10 $varLoopCounter = 0 while ($varTotalWaitTime -lt $varMaxWaitTimeResourceExistsCheck -and $varUserPermissionsElevated -eq $false) { try { # Log out to refresh the session Get-AzContext | Remove-AzContext -Confirm:$false Connect-AzAccount if ($parPermissionType -eq "Owner") { # check owner permissions of the user $varUserPermissionsElevated = Confirm-UserOwnerPermission } elseif ($parPermissionType -eq "Elevate") { # check user elevated at root scope $varUserPermissionsElevated = Confirm-UserElevated } if ($varUserPermissionsElevated -ne $true) { Write-Information ">>> Checking the permissions after waiting for $varWaitTime secs. Please ensure that you are logged into the appropriate tenant and did not log in to a different tenant during the script execution." -InformationAction Continue $varLoopCounter++ $varWaitTime = New-IncrementalDelay $varWaitTime $varLoopCounter $varTotalWaitTime += $varWaitTime Start-Sleep -Seconds $varWaitTime } } catch { $_.Exception Write-Information ">>> Retrying after waiting for $varWaitTime secs. To stop the retry press Ctrl+C." -InformationAction Continue $varLoopCounter++ $varWaitTime = New-IncrementalDelay $varWaitTime $varLoopCounter $varTotalWaitTime += $varWaitTime Start-Sleep -Seconds $varWaitTime } } } <# .Description Retrieves the error details on failure of deployment from azure #> function Get-FailedDeploymentErrorCodes { param($parManagementGroupId, $parDeploymentName, $parDeploymentScope) $varErrorCodeList = New-Object Collections.Generic.List[String] if ($parDeploymentScope -eq $varTenantDeployment) { $varDeploymentError = Get-AzTenantDeploymentOperation ` -DeploymentName $parDeploymentName | Where-Object { $_.ProvisioningState -eq "Failed" } } else { $varDeploymentError = Get-AzManagementGroupDeploymentOperation ` -ManagementGroupId $parManagementGroupId ` -DeploymentName $parDeploymentName | Where-Object { $_.ProvisioningState -eq "Failed" } } if ($null -ne $varDeploymentError) { if ($varDeploymentError.GetType().IsArray -and $varDeploymentError.count -gt 0) { foreach ($error in $varDeploymentError) { $varErrorDetails = $error.StatusMessage if ($varErrorDetails) { $varErrorCode = Get-ErrorCode $varErrorDetails # add to the list if the error code is not null and does not exists already if ($null -ne $varErrorCode -and !($varErrorCodeList -Contains $varErrorCode)) { $varErrorCodeList.Add($varErrorCode) } } } } else { $varErrorDetails = $varDeploymentError.StatusMessage if ($varErrorDetails) { $varErrorCode = Get-ErrorCode $varErrorDetails # add to the list if the error code is not null and does not exists already if ($null -ne $varErrorCode -and !($varErrorCodeList -Contains $varErrorCode)) { $varErrorCodeList.Add($varErrorCode) } } } } else { return $null } return $varErrorCodeList } <# .Description Checks whether a transient error or not #> function Confirm-Retry { param ($parDeploymentErrorCodes) $varRetry = $true foreach ($varErrorCode in $parDeploymentErrorCodes) { if ($varDonotRetryErrorCodes -contains $varErrorCode) { $varRetry = $false break } } return $varRetry } <# .Description Converts the object to array #> function Convert-ToArray { param ($parObjectToConvert) if ($null -eq $parObjectToConvert -or $parObjectToConvert.Length -eq "0") { return @() } $varObjArray = @() foreach ($varObject in $parObjectToConvert) { $varMap = @{} $varObject.psobject.properties | ForEach-Object { $varMap[$_.Name] = $_.Value } $varObjArray += $varMap } return , $varObjArray } <# .Description Converts the object to a hash table #> function Convert-ToHashTable { param ($parObjectToConvert) if ($null -eq $parObjectToConvert) { return @{} } $varHashTable = @{} $parObjectToConvert.PSObject.properties | ForEach-Object { $varHashTable[$_.Name] = $_.Value } return $varHashTable } <# .Description Moves the Subscriptions from root management group to platform #> function Move-Subscription { param($parParameters, $modDeployBootstrapOutputs) if ($modDeployBootstrapOutputs) { $varConnectivitySubscriptionId = $modDeployBootstrapOutputs.outputs.outConnectivitySubscriptionId.value $varIdentitySubscriptionId = $modDeployBootstrapOutputs.outputs.outIdentitySubscriptionId.value $varManagementSubscriptionId = $modDeployBootstrapOutputs.outputs.outManagementSubscriptionId.value } else { $varConnectivitySubscriptionId = $parParameters.parConnectivitySubscriptionId.value $varIdentitySubscriptionId = $parParameters.parIdentitySubscriptionId.value $varManagementSubscriptionId = $parParameters.parManagementSubscriptionId.value } $parDeploymentPrefix = $parParameters.parDeploymentPrefix.value $parDeploymentSuffix = $parParameters.parDeploymentSuffix.value $varManagementGroupId = "$parDeploymentPrefix$parDeploymentSuffix" $parDeploymentLocation = $parParameters.parDeploymentLocation.value $parameters = @{ parDeploymentPrefix = $parDeploymentPrefix parDeploymentSuffix = $parDeploymentSuffix parConnectivitySubscriptionId = $varConnectivitySubscriptionId parIdentitySubscriptionId = $varIdentitySubscriptionId parManagementSubscriptionId = $varManagementSubscriptionId } $varDeploymentName = "deploy-move-$vartimeStamp" $varLoopCounter = 0 $varRetry = $true while ($varRetry -and $varLoopCounter -lt $varMaxRetryAttemptTransientErrorRetry) { try { Write-Information ">>> Move subscription started" -InformationAction Continue $modMoveSubscription = New-AzManagementGroupDeployment ` -Name $varDeploymentName ` -ManagementGroupId $varManagementGroupId ` -Location $parDeploymentLocation ` -TemplateFile $varMoveSubscriptionBicepFilePath ` -TemplateParameterObject $parameters ` -WarningAction Ignore if (!$modMoveSubscription) { $varRetry = $false Write-Error "Error while executing move subscription" -ErrorAction Stop } Write-Information ">>> Move subscription completed`n" -InformationAction Continue return; } catch { if (!$varRetry) { Write-Error ">>> Error occurred during execution. Please try after addressing the above error." -ErrorAction Stop } else { $varDeploymentErrorCodes = Get-FailedDeploymentErrorCodes $varManagementGroupId $varDeploymentName $varManagementGroupDeployment if ($null -eq $varDeploymentErrorCodes) { $varRetry = $false } else { $varLoopCounter++ $varRetry = Confirm-Retry $varDeploymentErrorCodes if ($varRetry -and $varLoopCounter -lt $varMaxRetryAttemptTransientErrorRetry) { Write-Information ">>> Retrying deployment after waiting for $varRetryWaitTimeTransientErrorRetry secs" -InformationAction Continue Start-Sleep -Seconds $varRetryWaitTimeTransientErrorRetry } else { $varRetry = $false Write-Error ">>> Error occurred in move subscription deployment. Please try after addressing the above error." -ErrorAction Stop } } } } } } <# .Description Caclulates and returns the number of seconds to wait #> function New-IncrementalDelay { param($parDelay, $parDelayIterator) $parDelay = $parDelay + ($parDelayIterator * $varIntervalMultiplierResourceExistsCheck) if ($parDelay -ge $varMaxIntervalResourceExistsCheck) { $parDelay = $varMaxIntervalResourceExistsCheck } return $parDelay } <# .Description Load all the errors from the json file in a hashtable #> function Get-DonotRetryErrorCodes { param () $varErrorFile = Get-Content -Path '../const/doNotRetryErrorCodes.json' | ConvertFrom-Json $varErrorFile.errorCodes | ForEach-Object { $varDonotRetryErrorCodes.add($_.code) } } <# .Description Retrieves the error code on failure of deployment from json object #> function Get-ErrorCode { param ($parErrorString) $varLastIndexOfCode = $parErrorString.LastIndexOf("(Code:") # Find the position of the closing parenthesis after "Code:" $varClosingParenthesisIndex = $parErrorString.IndexOf(")", $varLastIndexOfCode) # Extract the value of 'Code' $varErrorCode = $parErrorString.Substring($varLastIndexOfCode + 6, $varClosingParenthesisIndex - $varLastIndexOfCode - 6).Trim() return $varErrorCode } <# .Description Checks the required parameters are passed based on the deployment #> function Confirm-Parameters($parParameters) { $varMissingParameters = New-Object Collections.Generic.List[String] $varArrayParameters = @("parAllowedLocations", "parAllowedLocationsForConfidentialComputing", "parPolicyDefinitionReferenceIds") Foreach ($varParameter in $parParameters) { if ($varParameter -in $varArrayParameters -and $varParameters.$varParameter.value.count -eq 0) { if (!$parAttendedLogin) { $varMissingParameters.add($varParameter) } else { [string[]] $varArray = @() $varArray = Read-Host "Please enter the list of $varParameter with a comma between each" if ($varArray[0] -eq "") { Write-Error "$varParameter value not found" -ErrorAction Stop } $varParameters.$varParameter.value = $varArray.Split(',') } } elseif (($null -eq $varParameters.$varParameter.value) -or [string]::IsNullOrEmpty($varParameters.$varParameter.value.ToString()) -or ($varParameters.$varParameter.value -eq "{}")) { $varParameters.$varParameter.value = $null if (!$parAttendedLogin) { $varMissingParameters.add($varParameter) } else { $varParameters.$varParameter.value = $(Read-Host -prompt "Please provide the value for $varParameter") if ($varParameters.$varParameter.value -eq "") { Write-Error "$varParameter value not found" -ErrorAction Stop } } } elseif ($varParameters.$varParameter.value.count -gt 1) { $varValue = $varParameters.$varParameter.value if ($varValue -is [array]) { foreach ($val in $varValue) { $result = Confirm-ObjectType($val) if ($result -eq $false) { $varMissingParameters.add($varParameter) } } } elseif ($varValue -is [object]) { $result = Confirm-ObjectType($varValue) if ($result -eq $false) { $varMissingParameters.add($varParameter) } } elseif (($null -eq $varValue) -or [string]::IsNullOrEmpty($varValue) -or ($varValue -eq "{}")) { $varParameters.$varParameter.value = $null return $false } } } if ($varMissingParameters.count -gt 0) { Write-Error "Following parameters are missing : $varMissingParameters" -ErrorAction Stop } # Check Gateway subnet is in the reserved Ip address list. $varGatewaySubnet = $parParameters.parGatewaySubnet.value $varIsGatewayReservedIpAddress = Confirm-IPAddressIsReserved($varGatewaySubnet) if (($null -ne $varGatewaySubnet) -and ($true -eq $varIsGatewayReservedIpAddress)) { Show-IpAddressError("The Gatewary Subnet Ip", $varGatewaySubnet) } # Check Azure Firewall Subnet is in the reserved Ip address list. $varAzureFirewallSubnet = $parParameters.parAzureFirewallSubnet.value $varIsFirewallReservedIpAddress = Confirm-IPAddressIsReserved($varAzureFirewallSubnet) if (($null -ne $varAzureFirewallSubnet) -and ($true -eq $varIsFirewallReservedIpAddress)) { Show-IpAddressError("The Azure Firewall Subnet Ip", $varAzureFirewallSubnet) } # Check parCustomerPolicySets. Confirm-CustomerPolicySets($varParameters.parCustomerPolicySets.value) } <# .Description Show Ip Address error which is in reserved Ip Address range list. #> function Show-IpAddressError($parMessage, $parIp) { Write-Information "$parMessage $parIp is in the reserved IP address list:" -InformationAction Continue foreach ($varIp in $varReservedIpAddressRange) { Write-Information $varIp -InformationAction Continue } Write-Error "Please do not use reserved IP addresses. Update parameters and try again." -ErrorAction Stop } <# .Description Checks/confirms whether the value of Ip Address range is in reserved Ip Address range list. #> function Confirm-IPAddressIsReserved($parIp) { if ($null -eq $parIp) { return $false } Foreach ($varReservedIpAddress in $varReservedIpAddressRange) { try { # Parse the IP address and subnet $varReservedIp = [IPAddress]::Parse($varReservedIpAddress.Split("/")[0]) $varReservedIpRange = $varReservedIpAddress.Split("/")[1] $varIp = [IPAddress]::Parse($parIp.Split("/")[0]) $varIpRange = $parIp.Split("/")[1] # Check if the IP address falls within the reserved IP address $varIsReservedIp = (($varReservedIp.Address -eq $varIp.Address) -and ($varReservedIpRange -eq $varIpRange)) if ($varIsReservedIp) { return $true } } catch { Write-Error $_ -ErrorAction Stop } } return $false } <# .Description Checks the required Object type parameters are passed based on the deployment. #> function Confirm-ObjectType($parParameter) { if (($null -eq $parParameter)) { return $false } $varMembers = $parParameter.PSObject.Properties | Select-Object Name, Value foreach ($varMember in $varMembers) { if (($null -eq $varMember.value) -or [string]::IsNullOrEmpty($varMember.value) -or ($varMember.value -eq "")) { return $false } } return $true } <# .Description Checks that the policy sets are available before assigning #> function Confirm-PolicySetExists { param ($parManagementGroupId, $parPolicySetType) if ($parPolicySetType -eq 'custom') { $varPolicySetsPath = "../../custom/policies/definitions" } else { $varPolicySetsPath = "../../modules/compliance/policySetDefinitions" } $varLoopCounter = 0 $varWaitTime = $varStartIntervalResourceExistsCheck $varTotalWaitTime = 0 while ($varTotalWaitTime -lt $varMaxWaitTimeResourceExistsCheck) { try { Get-ChildItem -Recurse -Path "$varPolicySetsPath" -Filter "*.json" | ForEach-Object { $varPolicyDef = Get-Content $_.PSPath | ConvertFrom-Json -Depth 100 if (($varPolicyDef.properties.policyDefinitions).Count -ne 0) { $parPolicyName = $varPolicyDef.name + ".v" + $varPolicyDef.properties.metadata.version $varPolicySet = Get-AzPolicySetDefinition -Name $parPolicyName -ManagementGroupName $parManagementGroupId if (!$varPolicySet) { Write-Error "$parPolicyName policy set not found." -ErrorAction stop } } } Write-Information ">>> All required policy sets were found." -InformationAction Continue return $true } catch { $varLoopCounter++ $varWaitTime = New-IncrementalDelay $varWaitTime $varLoopCounter Write-Information ">>> Searching for the required policy sets after waiting for $varWaitTime seconds." -InformationAction Continue $varTotalWaitTime += $varWaitTime Start-Sleep -Seconds $varWaitTime } } return $false } <# .Description Checks whether subscriptions are created or not. #> function Confirm-SubscriptionsExists() { param($parConnectivitySubscriptionId, $parIdentitySubscriptionId, $parManagementSubscriptionId) $varLoopCounter = 0 $varWaitTime = $varStartIntervalResourceExistsCheck $varTotalWaitTime = 0 $varSubscriptionExists = $false while ($varTotalWaitTime -lt $varMaxWaitTimeResourceExistsCheck -and $varSubscriptionExists -eq $false) { try { $varConnectivityID = Get-AzSubscription -SubscriptionId $parConnectivitySubscriptionId -WarningAction Ignore $varManagementID = Get-AzSubscription -SubscriptionId $parManagementSubscriptionId -WarningAction Ignore $varIdentityID = Get-AzSubscription -SubscriptionId $parIdentitySubscriptionId -WarningAction Ignore if ((!$varConnectivityID) -or (!$varManagementID) -or (!$varIdentityID)) { Write-Error "Subscription Not Found" -ErrorAction stop } $varSubscriptionExists = $true } catch { $varLoopCounter++ $varWaitTime = New-IncrementalDelay $varWaitTime $varLoopCounter Write-Information ">>>One or more subscription not found. Retrying after $varWaitTime seconds" -InformationAction Continue $varTotalWaitTime += $varWaitTime Start-Sleep -Seconds $varWaitTime } } return $varSubscriptionExists } <# .Description Processing parameters from JSON and creating a hash table #> function Read-ParametersValue($parJsonParamFilePath) { $varSlzParameters = Get-Content -Path $parJsonParamFilePath | ConvertFrom-Json $varAllowEmptyParameters = @("parExpressRouteGatewayConfig", "parVpnGatewayConfig", "parCustomerPolicies") $varSlzParameters.parameters.psobject.properties | ForEach-Object { if (($null -eq $_.value.Value -or $_.value.Value.count -eq 0) -and ($varAllowEmptyParameters -NotContains $_.Name)) { $varParameters.add($_.Name, (new-Object PsObject -property @{value = $_.value.defaultValue; defaultValue = $_.value.defaultValue })) } else { $varParameters.add($_.Name, (new-Object PsObject -property @{value = $_.value.Value; defaultValue = $_.value.defaultValue })) } } return $varParameters } <# .Description Checks Sovereign Landing Zone Prerequisites for the deployment. #> function Confirm-Prerequisites { param($parIsSLZDeployedAtTenantRoot) Write-Information ">>> Checking Sovereign Landing Zone Prerequisites for the deployment" -InformationAction Continue $varConfirmPrerequisites = '.\Confirm-SovereignLandingZonePrerequisites.ps1' & $varConfirmPrerequisites -parAttendedLogin $parAttendedLogin -parIsSLZDeployedAtTenantRoot $parIsSLZDeployedAtTenantRoot -ErrorAction Stop Write-Information ">>> Checking Sovereign Landing Zone Prerequisites is complete." -InformationAction Continue return } <# .Description Show management group information with a link to management group's azure portal #> function Show-ManagementGroupInfo { param($parParameters) if (!$parAttendedLogin) { return } $parDeploymentPrefix = [System.Uri]::EscapeDataString($parParameters.parDeploymentPrefix.value) $parTopLevelManagementGroupName = [System.Uri]::EscapeDataString($parParameters.parTopLevelManagementGroupName.value) $parDeploymentSuffix = [System.Uri]::EscapeDataString($parParameters.parDeploymentSuffix.value) $varTenantId = (Get-AzContext).Tenant.Id $varManagementGroupLink = "$varAzPortalLink/#view/Microsoft_Azure_ManagementGroups/ManagmentGroupDrilldownMenuBlade/~/overview/tenantId/$varTenantId" $varManagementGroupLink = "$varManagementGroupLink/mgId/$parDeploymentPrefix$parDeploymentSuffix/mgDisplayName/$parTopLevelManagementGroupName/mgCanAddOrMoveSubscription~/true/mgParentAccessLevel/Owner/defaultMenuItemId/overview/drillDownMode~/true" $varManagementGroupInfo = "If you want to learn more about your management group, please click following link.`n`n" $varManagementGroupInfo = "$varManagementGroupInfo$varManagementGroupLink`n`n" Write-Information ">>> $varManagementGroupInfo" -InformationAction Continue } <# .Description Show dashboard information with a link to portal dashboard. #> function Show-DashboardInfo { param($parParameters, $modDeployBootstrapOutputs) if ($modDeployBootstrapOutputs) { $varManagementSubscriptionId = $modDeployBootstrapOutputs.outputs.outManagementSubscriptionId.value } else { $varManagementSubscriptionId = $parParameters.parManagementSubscriptionId.value } if (!$parAttendedLogin) { return } $parDeploymentLocation = $parParameters.parDeploymentLocation.value $parDeploymentPrefix = $parParameters.parDeploymentPrefix.value $parDeploymentSuffix = $parParameters.parDeploymentSuffix.value $varSignedInUser = Get-SignedInUser $varResourceGroupName = "$parDeploymentPrefix-rg-dashboards-$parDeploymentLocation$parDeploymentSuffix" $varDashboardName = "$parDeploymentPrefix-Sovereign-Landing-Zone-Dashboard-$parDeploymentLocation$parDeploymentSuffix" $varUserDomain = $varSignedInUser.Substring($varSignedInUser.IndexOf("@")) $varDashboardLink = "$varAzPortalLink/#$varUserDomain/dashboard/arm/subscriptions/$varManagementSubscriptionId" $varDashboardLink = "$varDashboardLink/resourceGroups/$varResourceGroupName/providers/Microsoft.Portal/dashboards/$varDashboardName" $varDashboardInfo = "Now your compliance dashboard is ready for you to get insights. If you want to learn more, please click following link.`n`n$varDashboardLink`n`n" Write-Information ">>> $varDashboardInfo" -InformationAction Continue } <# .Description Register resource provider. #> function Register-ResourceProvider { param($parProviderNamespace) $varResourceProvider = $null $varLoopCounter = 0 Register-AzResourceProvider -ProviderNamespace $parProviderNamespace $varResourceProvider = Get-AzResourceProvider -ProviderNamespace $parProviderNamespace while ($null -eq $varResourceProvider -and $varLoopCounter -lt $varMaxRetryAttemptTransientErrorRetry) { Start-Sleep -Seconds $varRetryWaitTimeTransientErrorRetry $varResourceProvider = Get-AzResourceProvider -ProviderNamespace $parProviderNamespace $varLoopCounter++ } } <# .Description Check the bastion subnet value is provided if the deploy bastion is true. #> function Confirm-BastionRequiredValue { param ($parDeployBastion, $parSubnets) if ($parDeployBastion) { $varAzureBastionSubnet = ($parSubnets | Where-Object { $_.name -eq 'AzureBastionSubnet' }).ipAddressRange if ([string]::IsNullOrEmpty($varAzureBastionSubnet)) { Write-Error ">>> Missing parameter value for Azure Bastion subnet IP address range. Please try after addressing the above error." -ErrorAction Stop } } } <# .Description Get Private DNS Resource Group Id from the Private DNS Zones output #> function Get-PrivateDnsResourceGroupId { param ($parPrivateDnsZones, $parParameters) $varPrivateDnsResourceGroupId = "" $varDNSZonesResourceId = $parPrivateDnsZones.Count -gt 0 ? ($parPrivateDnsZones[0].id).ToString() : "" if (-not [string]::IsNullOrEmpty($varDNSZonesResourceId)) { $parDeploymentLocation = $parParameters.parDeploymentLocation.value $varPattern = "(.*)(?<=$([regex]::escape($parDeploymentLocation)))" $varRegExResult = $varDNSZonesResourceId | Select-String -Pattern $varPattern $varPrivateDnsResourceGroupId = $varRegExResult.Matches[0].Value } return $varPrivateDnsResourceGroupId } <# .Description Get Resource Name from Resource Id #> function Get-ResourceNameFromId { param($parResourceId) $varResourceName = "" if (-not [string]::IsNullOrEmpty($parResourceId)) { $parResourceId = $parResourceId -split '/' $varResourceName = $parResourceId[$parResourceId.Length - 1] } return $varResourceName } <# .Description Get Resource Type from Resource Id #> function Get-ResourceTypefromId { param($parResourceId) $varResourceType = "" if (-not [string]::IsNullOrEmpty($parResourceId)) { $parResourceId = $parResourceId -split '/' $varResourceType = $parResourceId[$parResourceId.Length - 2] } return $varResourceType } <# .DESCRIPTION Create a new object with the output data #> function New-OutputObject { param($parResourceName, $parResourceType, $parResourceId, $parDeploymentName, $parComments) $varDeploymentData = [PSCustomObject]@{ "Resource Name" = $parResourceName "Resource Type" = $parResourceType "Resource Id" = $parResourceId "Deployment Module" = $parDeploymentName "Comments" = $parComments } return $varDeploymentData } <# .Description Update parameter file after deployment #> function Out-DeploymentParameters { param($parDeploymentName, $modDeploymentOutputs, $parManagementGroupId, $parParameters) $varFilename = $parManagementGroupId + "_" + $parParameters.parDeploymentStartTime + ".csv" # Set the path of the folder you want to check/create $varFolderPath = "outputs" # Check if the folder exists if (-Not (Test-Path -Path $varFolderPath -PathType Container)) { # If the folder does not exist, create it New-Item -ItemType Directory -Path $varFolderPath -Force Write-Information "Folder created: $varFolderPath" -InformationAction Continue } $varExistingCsvFilePath = $varFolderPath + "\" + $varFilename $varCsvData = @() if ($parDeploymentName -eq "bootstrap") { $varDeploymentData = New-OutputObject $modDeploymentOutputs.outputs.outManagementSubscriptionName.value "Subscription" $modDeploymentOutputs.outputs.outManagementSubscriptionId.value $parDeploymentName "Used by platform as parManagementSubscriptionId" $varCsvData += $varDeploymentData $varDeploymentData = New-OutputObject $modDeploymentOutputs.outputs.outIdentitySubscriptionName.value "Subscription" $modDeploymentOutputs.outputs.outIdentitySubscriptionId.value $parDeploymentName "Used by platform as parIdentitySubscriptionId" $varCsvData += $varDeploymentData $varDeploymentData = New-OutputObject $modDeploymentOutputs.outputs.outConnectivitySubscriptionName.value "Subscription" $modDeploymentOutputs.outputs.outConnectivitySubscriptionId.value $parDeploymentName "Used by platform as parConnectivitySubscriptionId" $varCsvData += $varDeploymentData $varDeploymentData = New-OutputObject "Billing Scope" "billingScope" $parParameters.parSubscriptionBillingScope.value $parDeploymentName "Can be used for ALZ Vending module as subscriptionBillingScope" $varCsvData += $varDeploymentData foreach ($child in $modDeploymentOutputs.outputs.outLandingZoneChildrenManagementGroupIds.value) { $varResourceName = Get-ResourceNameFromId $child $varResourceType = Get-ResourceTypefromId $child $varDeploymentData = New-OutputObject $varResourceName $varResourceType $child $parDeploymentName "" $varCsvData += $varDeploymentData } foreach ($child in $modDeploymentOutputs.outputs.outPlatformChildrenManagementGroupIds.value) { $varResourceName = Get-ResourceNameFromId $child $varResourceType = Get-ResourceTypefromId $child $varDeploymentData = New-OutputObject $varResourceName $varResourceType $child $parDeploymentName "" $varCsvData += $varDeploymentData } } elseif ($parDeploymentName -eq "platform") { $varPrivateDnsResourceGroupId = Get-PrivateDnsResourceGroupId $modDeploymentOutputs.outputs.outPrivateDNSZones.value $parParameters $varDdosProtectionResourceId = $modDeploymentOutputs.outputs.outDdosProtectionResourceId.value $varLogAnalyticsResourceId = $modDeploymentOutputs.outputs.outLogAnalyticsWorkspaceId.value $varAutomationAccountId = $modDeploymentOutputs.outputs.outAutomationAccountName.value $varHubNetworkId = $modDeploymentOutputs.outputs.outHubVirtualNetworkId.value if (-not [string]::IsNullOrEmpty($varDdosProtectionResourceId)) { $varResourceName = Get-ResourceNameFromId $varDdosProtectionResourceId $varResourceType = Get-ResourceTypefromId $varDdosProtectionResourceId $varDeploymentData = New-OutputObject $varResourceName $varResourceType $varDdosProtectionResourceId $parDeploymentName "Used by platform as parDdosProtectionResourceId" $varCsvData += $varDeploymentData } if (-not [string]::IsNullOrEmpty($varLogAnalyticsResourceId)) { $varResourceName = Get-ResourceNameFromId $varLogAnalyticsResourceId $varResourceType = Get-ResourceTypefromId $varLogAnalyticsResourceId $varDeploymentData = New-OutputObject $varResourceName $varResourceType $varLogAnalyticsResourceId $parDeploymentName "Used by platform as parLogAnalyticsWorkspaceId" $varCsvData += $varDeploymentData } if (-not [string]::IsNullOrEmpty($varPrivateDnsResourceGroupId)) { $varResourceName = Get-ResourceNameFromId $varPrivateDnsResourceGroupId $varResourceType = Get-ResourceTypefromId $varPrivateDnsResourceGroupId $varDeploymentData = New-OutputObject $varResourceName $varResourceType $varPrivateDnsResourceGroupId $parDeploymentName "Used by platform as parPrivateDnsResourceGroupId" $varCsvData += $varDeploymentData } if (-not [string]::isNullOrEmpty($varAutomationAccountId)) { $varResourceName = $varAutomationAccountId $varResourceType = "AutomationAccount" $varDeploymentData = New-OutputObject $varResourceName $varResourceType $varAutomationAccountId $parDeploymentName "Used by platform as parAutomationAccountName" $varCsvData += $varDeploymentData } if (-not [string]::IsNullOrEmpty($varHubNetworkId)) { $varResourceName = Get-ResourceNameFromId $varHubNetworkId $varResourceType = Get-ResourceTypefromId $varHubNetworkId $varDeploymentData = New-OutputObject $varResourceName $varResourceType $varHubNetworkId $parDeploymentName "Used by platform as parHubVirtualNetworkId" $varCsvData += $varDeploymentData } $varDeploymentData = New-OutputObject "DeploymentLocation" "Location" $parParameters.parDeploymentLocation.value $parDeploymentName "Can be used for ALZ Vending module as virtualNetworkLocation" $varCsvData += $varDeploymentData } # If the existing CSV file exists, read its content if (Test-Path -Path $varExistingCsvFilePath) { $varExistingData = Import-Csv -Path $varExistingCsvFilePath } else { # If the file doesn't exist, create an empty array $varExistingData = @() } # Append the new data to the existing data $varUpdatedData = $varExistingData + $varCsvData # Save the updated data to the CSV file $varUpdatedData | Export-Csv -Path $varExistingCsvFilePath -NoTypeInformation } <# .Description Checks whether the Az Resources's version is later than or equal version 7.0.0, which contains breaking changes and could break existing SLZ scripts. #> function Confirm-AzResourceVersion { $varAzResourcesVersion = (Get-InstalledModule -Name Az.Resources).Version.replace("-preview", "") $varVersion = [Version]$varAzResourcesVersion -ge [Version]"7.0.0" return $varVersion } <# .Description Checks whether the file exists or not. #> function Confirm-FileExists { param($parFilePath) # Check if the file path exists if (Test-Path $parFilePath) { Write-Host "The file $parFilePath exists." } else { Write-Error ">>> The file $parFilePath does not exist. Please try again after addressing this error." -ErrorAction Stop } } <# .Description Checks whether the json file format is valid or not. #> function Confirm-JsonFileFormat { param($parFilePath) # Try to convert the JSON file to a PowerShell object try { Get-Content $parFilePath -Raw | ConvertFrom-Json Write-Host "The file $parFilePath contains valid JSON format." } catch { Write-Error ">>> The file $parFilePath contains invalid JSON format. Please try again after addressing this error. Error: $_" -ErrorAction Stop } } <# .Description Checks whether the json file format is valid or not. #> function Confirm-CustomerPolicySets { param($parCustomerPolicySets) # Try to convert the JSON file to a PowerShell object foreach($varCustomerPolicySet in $parCustomerPolicySets) { if ($null -ne $varCustomerPolicySet.policyParameterFilePath -and $varCustomerPolicySet.policyParameterFilePath -ne "") { Confirm-FileExists($varCustomerPolicySet.policyParameterFilePath) Confirm-JsonFileFormat($varCustomerPolicySet.policyParameterFilePath) } } }