private/New-AzEnvironmentInfrastructure.ps1

function New-AzEnvironmentInfrastructure {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [string]$EnvironmentName,
    [Parameter(Mandatory)]
    [string]$ResourceGroupName,
    [Parameter(Mandatory)]
    [string]$Location, # Used for deployment metadata and as default for resources
    [Parameter(Mandatory)]
    [string]$PlanManagedIdentityName,
    [Parameter(Mandatory)]
    [string]$ApplyManagedIdentityName,
    [Parameter(Mandatory)]
    [string]$GitHubOwner,
    [Parameter(Mandatory)]
    [string]$GitHubRepo,
    [Parameter(Mandatory)]
    [string]$PlanEnvName,
    [Parameter(Mandatory)]
    [string]$ApplyEnvName,
    [Parameter(Mandatory)] # Subscription ID is required for subscription-level deployments
    [string]$ArmSubscriptionId
  )

  $bicepTemplateFile = Join-Path $PSScriptRoot '..', 'templates', 'environment-infra.bicep' # Updated template path
  if (-not (Test-Path $bicepTemplateFile)) {
    throw "Bicep template file not found at '$bicepTemplateFile'."
  }
  $resolvedBicepTemplateFile = Resolve-Path $bicepTemplateFile -ErrorAction Stop

  Write-Host "[az-bootstrap] Deploying Azure infrastructure for '$EnvironmentName' environment via Bicep template '$resolvedBicepTemplateFile' at subscription scope..."

  $bicepParams = @{
    resourceGroupName         = $ResourceGroupName
    location                  = $Location # Bicep template uses this for RG and MI location
    planManagedIdentityName       = $PlanManagedIdentityName # For the primary/plan MI
    applyManagedIdentityName  = $ApplyManagedIdentityName
    gitHubOwner               = $GitHubOwner.ToLower()
    gitHubRepo                = $GitHubRepo.ToLower()
    gitHubPlanEnvironmentName = $PlanEnvName.ToLower()
    gitHubApplyEnvironmentName = $ApplyEnvName.ToLower()
  }
  $bicepParamsJson = $bicepParams | ConvertTo-Json -Depth 5 -Compress

  Write-Verbose "[az-bootstrap] Bicep parameters for subscription deployment: $bicepParamsJson"

  $deploymentName = "AzBootstrap-EnvInfra-${EnvironmentName}-$(Get-Date -Format 'yyyyMMddHHmmssff')"
  
  # Using Start-Process for better stream handling with Azure CLI
  $azCliArgs = @(
    "deployment", "sub", "create",
    "--name", $deploymentName,
    "--location", $Location, # Location for the deployment metadata
    "--template-file", $resolvedBicepTemplateFile,
    "--parameters", $bicepParamsJson,
    "--subscription", $ArmSubscriptionId,
    "--output", "json"
  )

  Write-Verbose "[az-bootstrap] Executing: az $($azCliArgs -join ' ')"
  $stdoutfile = New-TemporaryFile
  $stderrfile = New-TemporaryFile
  $process = Start-Process "az" -ArgumentList $azCliArgs -Wait -NoNewWindow -PassThru -RedirectStandardOutput $stdoutfile -RedirectStandardError $stderrfile
  $stdout = Get-Content $stdoutfile -Raw
  $stderr = Get-Content $stderrfile -ErrorAction SilentlyContinue
  Remove-Item $stdoutfile, $stderrfile -ErrorAction SilentlyContinue

  if ($process.ExitCode -ne 0) {
    Write-Error "[az-bootstrap] Bicep subscription deployment failed for environment '$EnvironmentName'. Exit Code: $($process.ExitCode)"
    Write-Error "[az-bootstrap] Standard Error: $stderr"
    Write-Error "[az-bootstrap] Standard Output (may contain JSON error from Azure): $stdout"
    throw "Bicep subscription deployment for environment '$EnvironmentName' failed."
  }

  $deploymentOutput = $stdout | ConvertFrom-Json -ErrorAction SilentlyContinue
  if (-not $deploymentOutput -or -not $deploymentOutput.properties -or -not $deploymentOutput.properties.outputs) {
      Write-Error "[az-bootstrap] Bicep deployment outputs not found or failed to parse. Raw STDOUT: $stdout"
      throw "Bicep deployment for environment '$EnvironmentName' did not produce expected outputs."
  }

  $planManagedIdentityClientId = $null
  $planManagedIdentityPrincipalId = $null
  $applyManagedIdentityClientId = $null
  $applyManagedIdentityPrincipalId = $null

  if ($deploymentOutput.properties.outputs.planManagedIdentityClientId) {
    $planManagedIdentityClientId = $deploymentOutput.properties.outputs.planManagedIdentityClientId.value
  }
  if ($deploymentOutput.properties.outputs.planManagedIdentityPrincipalId) {
    $planManagedIdentityPrincipalId = $deploymentOutput.properties.outputs.planManagedIdentityPrincipalId.value
  }
  if ($deploymentOutput.properties.outputs.applyManagedIdentityClientId) {
    $applyManagedIdentityClientId = $deploymentOutput.properties.outputs.applyManagedIdentityClientId.value
  }
  if ($deploymentOutput.properties.outputs.applyManagedIdentityPrincipalId) {
    $applyManagedIdentityPrincipalId = $deploymentOutput.properties.outputs.applyManagedIdentityPrincipalId.value
  }

  if (-not $planManagedIdentityClientId -or -not $planManagedIdentityPrincipalId) {
    throw "Failed to retrieve Plan Managed Identity Client ID or Principal ID from Bicep deployment for environment '$EnvironmentName'."
  }
  if (-not $applyManagedIdentityClientId -or -not $applyManagedIdentityPrincipalId) {
    throw "Failed to retrieve Apply Managed Identity Client ID or Principal ID from Bicep deployment for environment '$EnvironmentName'."
  }
  
  Write-Host "[az-bootstrap] Bicep deployment for '$EnvironmentName' succeeded."
  Write-Verbose "[az-bootstrap] Plan MI Client ID: $planManagedIdentityClientId"
  Write-Verbose "[az-bootstrap] Apply MI Client ID: $applyManagedIdentityClientId"
  
  return [PSCustomObject]@{
    PlanManagedIdentityClientId    = $planManagedIdentityClientId
    PlanManagedIdentityPrincipalId = $planManagedIdentityPrincipalId
    ApplyManagedIdentityClientId      = $applyManagedIdentityClientId 
    ApplyManagedIdentityPrincipalId   = $applyManagedIdentityPrincipalId 
  }
}