diff --git a/src/bicep/add-ons/imaging/modules/automationAccount.bicep b/src/bicep/add-ons/imaging/modules/automationAccount.bicep index a1a245797..a5cba3da9 100644 --- a/src/bicep/add-ons/imaging/modules/automationAccount.bicep +++ b/src/bicep/add-ons/imaging/modules/automationAccount.bicep @@ -73,6 +73,7 @@ param wsusServer string var parameters = { arcGisProInstaller: arcGisProInstaller + computeGalleryImageResourceId: computeGalleryImageResourceId computeGalleryResourceId: computeGalleryResourceId containerName: containerName customizations: string(customizations) @@ -111,7 +112,7 @@ var parameters = { officeInstaller: officeInstaller replicaCount: string(replicaCount) resourceGroupName: resourceGroupName - computeGalleryImageResourceId: computeGalleryImageResourceId + resourceManagerUri: environment().resourceManager sourceImageType: sourceImageType storageAccountResourceId: storageAccountResourceId subnetResourceId: subnetResourceId @@ -131,7 +132,6 @@ var parameters = { } var privateEndpointName = 'pe-${automationAccountName}' var runbookName = 'New-AzureZeroTrustImageBuild' -var storageEndpoint = environment().suffixes.storage var subscriptionId = subscription().subscriptionId var tenantId = subscription().tenantId @@ -205,7 +205,21 @@ resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneG } } -resource runCommand 'Microsoft.Compute/virtualMachines/runCommands@2023-07-01' = { +resource runBook 'Microsoft.Automation/automationAccounts/runbooks@2023-11-01' = { + parent: automationAccount + name: runbookName + properties: { + runbookType: 'PowerShell' + logProgress: true + logVerbose: true + } + tags: union( + contains(tags, 'Microsoft.Automation/automationAccounts/runbooks') ? tags['Microsoft.Automation/automationAccounts/runbooks'] : {}, + mlzTags + ) +} + +resource updateRunBook 'Microsoft.Compute/virtualMachines/runCommands@2023-07-01' = { name: 'runbook' location: location tags: union( @@ -218,96 +232,57 @@ resource runCommand 'Microsoft.Compute/virtualMachines/runCommands@2023-07-01' = asyncExecution: false parameters: [ { - name: 'AutomationAccountName' - value: automationAccountName - } - { - name: 'ContainerName' - value: containerName - } - { - name: 'Environment' - value: environment().name - } - { - name: 'ResourceGroupName' - value: resourceGroup().name + name: 'RunBookResourceId' + value: runBook.id } { - name: 'RunbookName' - value: runbookName + name: 'ResourceManagerUri' + value: environment().resourceManager } { - name: 'StorageAccountName' - value: split(storageAccountResourceId, '/')[8] - } - { - name: 'StorageEndpoint' - value: storageEndpoint - } - { - name: 'SubscriptionId' - value: subscription().subscriptionId - } - { - name: 'TenantId' - value: tenant().tenantId + name: 'RunbBookScriptContent' + value: loadTextContent('../scripts/New-AzureZeroTrustImageBuild.ps1') } { name: 'UserAssignedIdentityClientId' value: userAssignedIdentityClientId } - { - name: 'UserAssignedIdentityObjectId' - value: userAssignedIdentityPrincipalId - } ] source: { script: ''' - param ( - [string]$AutomationAccountName, - [string]$ContainerName, - [string]$Environment, - [string]$ResourceGroupName, - [string]$RunbookName, - [string]$StorageAccountName, - [string]$StorageEndpoint, - [string]$SubscriptionId, - [string]$TenantId, - [string]$UserAssignedIdentityClientId, - [string]$UserAssignedIdentityObjectId + param( + [string]$ResourceManagerUri, + [string]$RunBookResourceId, + [string]$RunBookScriptContent, + [string]$UserAssignedIdentityClientId ) $ErrorActionPreference = 'Stop' $WarningPreference = 'SilentlyContinue' - $BlobName = 'New-AzureZeroTrustImageBuild.ps1' - $StorageAccountUrl = "https://" + $StorageAccountName + ".blob." + $StorageEndpoint + "/" - $TokenUri = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=$StorageAccountUrl&object_id=$UserAssignedIdentityObjectId" - $AccessToken = ((Invoke-WebRequest -Headers @{Metadata=$true} -Uri $TokenUri -UseBasicParsing).Content | ConvertFrom-Json).access_token - $File = "$env:windir\temp\$BlobName" - do - { - try - { - Write-Output "Download Attempt $i" - Invoke-WebRequest -Headers @{"x-ms-version"="2017-11-09"; Authorization ="Bearer $AccessToken"} -Uri "$StorageAccountUrl$ContainerName/$BlobName" -OutFile $File - } - catch [System.Net.WebException] - { - Start-Sleep -Seconds 60 - $i++ - if($i -gt 10){throw} - continue - } - catch - { - $Output = $_ | select * - Write-Output $Output - throw + + Try { + # Fix the resource manager URI since only AzureCloud contains a trailing slash + $ResourceManagerUriFixed = if($ResourceManagerUri[-1] -eq '/'){$ResourceManagerUri.Substring(0,$ResourceManagerUri.Length - 1)} else {$ResourceManagerUri} + + # Get an access token for Azure resources + $AzureManagementAccessToken = (Invoke-RestMethod ` + -Headers @{Metadata="true"} ` + -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token + + # Set header for Azure Management API + $AzureManagementHeader = @{ + 'Content-Type'='application/json' + 'Authorization'='Bearer ' + $AzureManagementAccessToken } + + # Upload Content to Draft + Invoke-RestMethod -Headers $AzureManagementHeader -Method 'PUT' -Uri $($ResourceManagerUriFixed + $RunBookResourceId + '/draft/content?api-version=2023-11-01') -Body $RunBookScriptContent + + # Publish the RunBook + Invoke-RestMethod -Headers $AzureManagementHeader -Method 'POST' -Uri $($ResourceManagerUriFixed + $RunBookResourceId + '/publish?api-version=2023-11-01') + } + catch { + throw } - until(Test-Path -Path $File) - Connect-AzAccount -Environment $Environment -Tenant $TenantId -Subscription $SubscriptionId -Identity -AccountId $UserAssignedIdentityClientId | Out-Null - Import-AzAutomationRunbook -Name $RunbookName -Path $File -Type PowerShell -AutomationAccountName $AutomationAccountName -ResourceGroupName $ResourceGroupName -Published -Force | Out-Null ''' } } @@ -341,7 +316,7 @@ resource jobSchedule 'Microsoft.Automation/automationAccounts/jobSchedules@2022- } } dependsOn: [ - runCommand + updateRunBook ] } @@ -371,7 +346,7 @@ resource hybridRunbookWorker 'Microsoft.Automation/automationAccounts/hybridRunb vmResourceId: virtualMachine.id } dependsOn: [ - runCommand + updateRunBook ] } @@ -391,7 +366,7 @@ resource extension_HybridWorker 'Microsoft.Compute/virtualMachines/extensions@20 } } dependsOn: [ - runCommand + updateRunBook ] } @@ -420,6 +395,6 @@ resource extension_JsonADDomainExtension 'Microsoft.Compute/virtualMachines/exte } dependsOn: [ extension_HybridWorker - runCommand + updateRunBook ] } diff --git a/src/bicep/add-ons/imaging/modules/buildAutomation.bicep b/src/bicep/add-ons/imaging/modules/buildAutomation.bicep index 0cf245288..fd615fa27 100644 --- a/src/bicep/add-ons/imaging/modules/buildAutomation.bicep +++ b/src/bicep/add-ons/imaging/modules/buildAutomation.bicep @@ -136,17 +136,17 @@ module managementVM 'managementVM.bicep' = { name: 'management-vm-${deploymentNameSuffix}' scope: resourceGroup(subscriptionId, resourceGroupName) params: { - containerName: containerName + diskEncryptionSetResourceId: diskEncryptionSetResourceId hybridUseBenefit: hybridUseBenefit localAdministratorPassword: localAdministratorPassword localAdministratorUsername: localAdministratorUsername location: location mlzTags: mlzTags - storageAccountName: split(storageAccountResourceId, '/')[8] + subnetResourceId: subnetResourceId tags: tags - userAssignedIdentityPrincipalId: userAssignedIdentityPrincipalId + userAssignedIdentityResourceId: userAssignedIdentityResourceId virtualMachineName: managementVirtualMachineName } diff --git a/src/bicep/add-ons/imaging/modules/generalizeVirtualMachine.bicep b/src/bicep/add-ons/imaging/modules/generalizeVirtualMachine.bicep index 88cbf31c7..002cfc08e 100644 --- a/src/bicep/add-ons/imaging/modules/generalizeVirtualMachine.bicep +++ b/src/bicep/add-ons/imaging/modules/generalizeVirtualMachine.bicep @@ -33,50 +33,64 @@ resource generalizeVirtualMachine 'Microsoft.Compute/virtualMachines/runCommands asyncExecution: false parameters: [ { - name: 'Environment' - value: environment().name - } - { - name: 'ResourceGroupName' - value: resourceGroupName - } - { - name: 'SubscriptionId' - value: subscription().subscriptionId - } - { - name: 'TenantId' - value: tenant().tenantId + name: 'ResourceManagerUri' + value: environment().resourceManager } { name: 'UserAssignedIdentityClientId' value: userAssignedIdentityClientId } { - name: 'VirtualMachineName' - value: imageVirtualMachine.name + name: 'VmResourceId' + value: imageVirtualMachine.id } ] source: { script: ''' param( - [string]$Environment, - [string]$ResourceGroupName, - [string]$SubscriptionId, - [string]$TenantId, - [string]$UserAssignedIdentityClientId, - [string]$VirtualMachineName + [Parameter(Mandatory=$true)] + [string]$ResourceManagerUri, + + [Parameter(Mandatory=$true)] + [string]$UserAssignedIdentityClientId, + + [Parameter(Mandatory=$true)] + [string]$VmResourceId ) + $ErrorActionPreference = 'Stop' - Connect-AzAccount -Environment $Environment -Tenant $TenantId -Subscription $SubscriptionId -Identity -AccountId $UserAssignedIdentityClientId | Out-Null - $PowerStatus = '' - while ($PowerStatus -ne 'VM stopped') - { - Start-Sleep -Seconds 5 - $PowerStatus = (Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -Status).Statuses[1].DisplayStatus + $WarningPreference = 'SilentlyContinue' + + Try { + # Fix the resource manager URI since only AzureCloud contains a trailing slash + $ResourceManagerUriFixed = if($ResourceManagerUri[-1] -eq '/'){$ResourceManagerUri.Substring(0,$ResourceManagerUri.Length - 1)} else {$ResourceManagerUri} + + # Get an access token for Azure resources + $AzureManagementAccessToken = (Invoke-RestMethod ` + -Headers @{Metadata="true"} ` + -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token + + # Set header for Azure Management API + $AzureManagementHeader = @{ + 'Content-Type'='application/json' + 'Authorization'='Bearer ' + $AzureManagementAccessToken + } + + # Stop the VM + $null = Invoke-RestMethod -Headers $AzureManagementHeader -Method 'Post' -Uri $($ResourceManagerUriFixed + $VmResourceId + '/powerOff?api-version=2024-03-01') + # Wait for it to show as stopped in Azure + Do { + Start-Sleep -Seconds 5 + $VmStatus = Invoke-RestMethod -Headers $AzureManagementHeader -Method 'Get' -Uri $($ResourceManagerUriFixed + $VmResourceId + '/instanceView?api-version=2024-03-01') + $VMPowerState = ($VMStatus.statuses | Where-Object {$_.code -like 'PowerState*'}).displayStatus + + } Until ($VMPowerState -eq 'VM stopped') + # Generatlize the VM + $null = Invoke-RestMethod -Headers $AzureManagementHeader -Method 'Post' -Uri $($ResourceManagerUriFixed + $VmResourceId + '/generalize?api-version=2024-03-01') + } + catch { + throw } - Set-AzVm -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -Generalized - Start-Sleep -Seconds 30 ''' } } diff --git a/src/bicep/add-ons/imaging/modules/imageBuild.bicep b/src/bicep/add-ons/imaging/modules/imageBuild.bicep index 342787308..27bbdd4fb 100644 --- a/src/bicep/add-ons/imaging/modules/imageBuild.bicep +++ b/src/bicep/add-ons/imaging/modules/imageBuild.bicep @@ -81,17 +81,14 @@ module managementVM 'managementVM.bicep' = name: 'management-vm-${deploymentNameSuffix}' scope: resourceGroup(subscriptionId, resourceGroupName) params: { - containerName: containerName diskEncryptionSetResourceId: diskEncryptionSetResourceId hybridUseBenefit: hybridUseBenefit localAdministratorPassword: localAdministratorPassword localAdministratorUsername: localAdministratorUsername location: location mlzTags: mlzTags - storageAccountName: split(storageAccountResourceId, '/')[8] subnetResourceId: subnetResourceId tags: tags - userAssignedIdentityPrincipalId: userAssignedIdentityPrincipalId userAssignedIdentityResourceId: userAssignedIdentityResourceId virtualMachineName: managementVirtualMachineName } diff --git a/src/bicep/add-ons/imaging/modules/managementVM.bicep b/src/bicep/add-ons/imaging/modules/managementVM.bicep index ec1bdf962..d6492bdf9 100644 --- a/src/bicep/add-ons/imaging/modules/managementVM.bicep +++ b/src/bicep/add-ons/imaging/modules/managementVM.bicep @@ -3,7 +3,7 @@ Copyright (c) Microsoft Corporation. Licensed under the MIT License. */ -param containerName string +//param containerName string param diskEncryptionSetResourceId string param hybridUseBenefit bool @secure() @@ -12,10 +12,10 @@ param localAdministratorPassword string param localAdministratorUsername string param location string param mlzTags object -param storageAccountName string +//param storageAccountName string param subnetResourceId string param tags object -param userAssignedIdentityPrincipalId string +//param userAssignedIdentityPrincipalId string param userAssignedIdentityResourceId string param virtualMachineName string @@ -122,7 +122,7 @@ resource virtualMachine 'Microsoft.Compute/virtualMachines@2022-03-01' = { licenseType: hybridUseBenefit ? 'Windows_Server' : null } } - +/* resource modules 'Microsoft.Compute/virtualMachines/runCommands@2023-03-01' = { name: 'appAzModules' location: location @@ -209,5 +209,6 @@ resource modules 'Microsoft.Compute/virtualMachines/runCommands@2023-03-01' = { } } } +*/ output name string = virtualMachine.name diff --git a/src/bicep/add-ons/imaging/modules/microsoftUpdates.bicep b/src/bicep/add-ons/imaging/modules/microsoftUpdates.bicep index a5142d3ed..717a4e5f7 100644 --- a/src/bicep/add-ons/imaging/modules/microsoftUpdates.bicep +++ b/src/bicep/add-ons/imaging/modules/microsoftUpdates.bicep @@ -105,81 +105,82 @@ resource microsoftUpdates 'Microsoft.Compute/virtualMachines/runCommands@2023-03 } Write-Output "Searching for Updates..." $SearchResult = $UpdateSearcher.Search($Criteria) - If ($SearchResult.Updates.Count -eq 0) { - Write-Output "There are no applicable updates." - Write-Output "Now Exiting" - Exit $ExitCode - } - Write-Output "List of applicable items found for this computer:" - For ($i = 0; $i -lt $SearchResult.Updates.Count; $i++) { - $Update = $SearchResult.Updates[$i] - Write-Output "$($i + 1) > $($update.Title)" - } - $AtLeastOneAdded = $false - $ExclusiveAdded = $false - $UpdatesToDownload = New-Object -ComObject Microsoft.Update.UpdateColl - Write-Output "Checking search results:" - For ($i = 0; $i -lt $SearchResult.Updates.Count; $i++) { - $Update = $SearchResult.Updates[$i] - $AddThisUpdate = $false - If ($ExclusiveAdded) { - Write-Output "$($i + 1) > skipping: '$($update.Title)' because an exclusive update has already been selected." - } Else { - $AddThisUpdate = $true - } - if ($ExcludePreviewUpdates -and $update.Title -like '*Preview*') { - Write-Output "$($i + 1) > Skipping: '$($update.Title)' because it is a preview update." - $AddThisUpdate = $false - } - If ($AddThisUpdate) { - $PropertyTest = 0 - $ErrorActionPreference = 'SilentlyContinue' - $PropertyTest = $Update.InstallationBehavior.Impact - $ErrorActionPreference = 'Stop' - If ($PropertyTest -eq 2) { - If ($AtLeastOneAdded) { - Write-Output "$($i + 1) > skipping: '$($update.Title)' because it is exclusive and other updates are being installed first." - $AddThisUpdate = $false + If ($SearchResult.Updates.Count -gt 0) { + Write-Output "List of applicable items found for this computer:" + For ($i = 0; $i -lt $SearchResult.Updates.Count; $i++) { + $Update = $SearchResult.Updates[$i] + Write-Output "$($i + 1) > $($update.Title)" + } + $AtLeastOneAdded = $false + $ExclusiveAdded = $false + $UpdatesToDownload = New-Object -ComObject Microsoft.Update.UpdateColl + Write-Output "Checking search results:" + For ($i = 0; $i -lt $SearchResult.Updates.Count; $i++) { + $Update = $SearchResult.Updates[$i] + $AddThisUpdate = $false + If ($ExclusiveAdded) { + Write-Output "$($i + 1) > skipping: '$($update.Title)' because an exclusive update has already been selected." + } Else { + $AddThisUpdate = $true + } + if ($ExcludePreviewUpdates -and $update.Title -like '*Preview*') { + Write-Output "$($i + 1) > Skipping: '$($update.Title)' because it is a preview update." + $AddThisUpdate = $false + } + If ($AddThisUpdate) { + $PropertyTest = 0 + $ErrorActionPreference = 'SilentlyContinue' + $PropertyTest = $Update.InstallationBehavior.Impact + $ErrorActionPreference = 'Stop' + If ($PropertyTest -eq 2) { + If ($AtLeastOneAdded) { + Write-Output "$($i + 1) > skipping: '$($update.Title)' because it is exclusive and other updates are being installed first." + $AddThisUpdate = $false + } } } - } - If ($AddThisUpdate) { - Write-Output "$($i + 1) > adding: '$($update.Title)'" - $UpdatesToDownload.Add($Update) | out-null - $AtLeastOneAdded = $true - $ErrorActionPreference = 'SilentlyContinue' - $PropertyTest = $Update.InstallationBehavior.Impact - $ErrorActionPreference = 'Stop' - If ($PropertyTest -eq 2) { - Write-Output "This update is exclusive; skipping remaining updates" - $ExclusiveAdded = $true + If ($AddThisUpdate) { + Write-Output "$($i + 1) > adding: '$($update.Title)'" + $UpdatesToDownload.Add($Update) | out-null + $AtLeastOneAdded = $true + $ErrorActionPreference = 'SilentlyContinue' + $PropertyTest = $Update.InstallationBehavior.Impact + $ErrorActionPreference = 'Stop' + If ($PropertyTest -eq 2) { + Write-Output "This update is exclusive; skipping remaining updates" + $ExclusiveAdded = $true + } + } + } + $UpdatesToInstall = New-Object -ComObject Microsoft.Update.UpdateColl + Write-Output "Downloading updates..." + $Downloader = $UpdateSession.CreateUpdateDownloader() + $Downloader.Updates = $UpdatesToDownload + $Downloader.Download() + Write-Output "Successfully downloaded updates:" + For ($i = 0; $i -lt $UpdatesToDownload.Count; $i++) { + $Update = $UpdatesToDownload[$i] + If ($Update.IsDownloaded -eq $true) { + Write-Output "$($i + 1) > $($update.title)" + $UpdatesToInstall.Add($Update) | out-null + } + } + If ($UpdatesToInstall.Count -gt 0) { + Write-Output "Now installing updates..." + $Installer = $UpdateSession.CreateUpdateInstaller() + $Installer.Updates = $UpdatesToInstall + $InstallationResult = $Installer.Install() + $Text = ConvertFrom-InstallationResult -Result $InstallationResult.ResultCode + Write-Output "Installation Result: $($Text)" + If ($InstallationResult.RebootRequired) { + Write-Output "Atleast one update requires a reboot to complete the installation." } } - } - $UpdatesToInstall = New-Object -ComObject Microsoft.Update.UpdateColl - Write-Output "Downloading updates..." - $Downloader = $UpdateSession.CreateUpdateDownloader() - $Downloader.Updates = $UpdatesToDownload - $Downloader.Download() - Write-Output "Successfully downloaded updates:" - For ($i = 0; $i -lt $UpdatesToDownload.Count; $i++) { - $Update = $UpdatesToDownload[$i] - If ($Update.IsDownloaded -eq $true) { - Write-Output "$($i + 1) > $($update.title)" - $UpdatesToInstall.Add($Update) | out-null - } - } - If ($UpdatesToInstall.Count -gt 0) { - Write-Output "Now installing updates..." - $Installer = $UpdateSession.CreateUpdateInstaller() - $Installer.Updates = $UpdatesToInstall - $InstallationResult = $Installer.Install() - $Text = ConvertFrom-InstallationResult -Result $InstallationResult.ResultCode - Write-Output "Installation Result: $($Text)" - If ($InstallationResult.RebootRequired) { - Write-Output "Atleast one update requires a reboot to complete the installation." - } } + Else { + Write-Output "There are no applicable updates." + } + If ($service -eq 'MU') { Reg.exe DELETE "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v AllowMUUpdateService /f } Elseif ($Service -eq 'WSUS' -and $WSUSServer) { diff --git a/src/bicep/add-ons/imaging/modules/removeRunCommands.bicep b/src/bicep/add-ons/imaging/modules/removeRunCommands.bicep index 96ed8b037..bdb477324 100644 --- a/src/bicep/add-ons/imaging/modules/removeRunCommands.bicep +++ b/src/bicep/add-ons/imaging/modules/removeRunCommands.bicep @@ -36,7 +36,7 @@ resource customScriptExtension 'Microsoft.Compute/virtualMachines/extensions@202 timestamp: timestamp } protectedSettings: { - commandToExecute: 'powershell -ExecutionPolicy Unrestricted -File Remove-AzureRunCommands.ps1 -Environment ${environment().name} -ResourceGroupName ${resourceGroup().name} -RunCommands ${runCommands} -SubscriptionId ${subscription().subscriptionId} -TenantId ${tenant().tenantId} -UserAssignedIdentityClientId ${userAssignedIdentityClientId} -VirtualMachineName ${virtualMachineName}' + commandToExecute: 'powershell -ExecutionPolicy Unrestricted -File Remove-AzureRunCommands.ps1 -ResourceManagerUri ${environment().resourceManager} -RunCommands ${runCommands} -UserAssignedIdentityClientId ${userAssignedIdentityClientId} -VmResourceId ${virtualMachine.id}' fileUris: [ 'https://${storageAccountName}.blob.${storageEndpoint}/${containerName}/Remove-AzureRunCommands.ps1' ] diff --git a/src/bicep/add-ons/imaging/modules/removeVirtualMachine.bicep b/src/bicep/add-ons/imaging/modules/removeVirtualMachine.bicep index db1a3d03b..2fac05655 100644 --- a/src/bicep/add-ons/imaging/modules/removeVirtualMachine.bicep +++ b/src/bicep/add-ons/imaging/modules/removeVirtualMachine.bicep @@ -36,28 +36,16 @@ resource removeVirtualMachine 'Microsoft.Compute/virtualMachines/runCommands@202 value: string(enableBuildAutomation) } { - name: 'Environment' - value: environment().name + name: 'ResourceManagerUri' + value: environment().resourceManager } { - name: 'ImageVmName' - value: imageVirtualMachine.name + name: 'ImageVmResourceId' + value: imageVirtualMachine.id } { - name: 'ManagementVmName' - value: virtualMachine.name - } - { - name: 'ResourceGroupName' - value: resourceGroup().name - } - { - name: 'SubscriptionId' - value: subscription().subscriptionId - } - { - name: 'TenantId' - value: tenant().tenantId + name: 'ManagementVmResourceId' + value: virtualMachine.id } { name: 'UserAssignedIdentityClientId' @@ -67,21 +55,50 @@ resource removeVirtualMachine 'Microsoft.Compute/virtualMachines/runCommands@202 source: { script: ''' param( - [string]$EnableBuildAutomation, - [string]$Environment, - [string]$ImageVmName, - [string]$ManagementVmName, - [string]$ResourceGroupName, - [string]$SubscriptionId, - [string]$TenantId, - [string]$UserAssignedIdentityClientId + [Parameter(Mandatory=$false)] + [string]$EnableBuildAutomation, + + [Parameter(Mandatory=$true)] + [string]$ResourceManagerUri, + + [Parameter(Mandatory=$true)] + [string]$UserAssignedIdentityClientId, + + [Parameter(Mandatory=$true)] + [string]$ImageVmResourceId, + + [Parameter(Mandatory=$true)] + [string]$ManagementVmResourceId ) + $ErrorActionPreference = 'Stop' - Connect-AzAccount -Environment $Environment -Tenant $TenantId -Subscription $SubscriptionId -Identity -AccountId $UserAssignedIdentityClientId | Out-Null - Remove-AzVM -ResourceGroupName $ResourceGroupName -Name $ImageVmName -Force - if($EnableBuildAutomation -eq 'false') - { - Remove-AzVM -ResourceGroupName $ResourceGroupName -Name $ManagementVmName -NoWait -Force -AsJob + $WarningPreference = 'SilentlyContinue' + + Try { + # Fix the resource manager URI since only AzureCloud contains a trailing slash + $ResourceManagerUriFixed = if($ResourceManagerUri[-1] -eq '/'){$ResourceManagerUri.Substring(0,$ResourceManagerUri.Length - 1)} else {$ResourceManagerUri} + + # Get an access token for Azure resources + $AzureManagementAccessToken = (Invoke-RestMethod ` + -Headers @{Metadata="true"} ` + -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token + + # Set header for Azure Management API + $AzureManagementHeader = @{ + 'Content-Type'='application/json' + 'Authorization'='Bearer ' + $AzureManagementAccessToken + } + + # Delete Image VM + $null = Invoke-RestMethod -Headers $AzureManagementHeader -Method 'DELETE' -Uri $($ResourceManagerUriFixed + $ImageVmResourceId + '?api-version=2024-03-01') + if($EnableBuildAutomation -eq 'false') { + # Delete the Management VM (Don't wait to prevent deployment failure.) + $ScriptBlock = {Invoke-RestMethod -Headers $AzureManagementHeader -Method 'DELETE' -Uri $($ResourceManagerUriFixed + $ManagementVmResourceId + '?api-version=2024-03-01')} + $null = Start-Job -ScriptBlock $ScriptBlock + } + } + catch { + throw } ''' } diff --git a/src/bicep/add-ons/imaging/modules/restartVirtualMachine.bicep b/src/bicep/add-ons/imaging/modules/restartVirtualMachine.bicep index 12e11f224..a7611398a 100644 --- a/src/bicep/add-ons/imaging/modules/restartVirtualMachine.bicep +++ b/src/bicep/add-ons/imaging/modules/restartVirtualMachine.bicep @@ -33,48 +33,63 @@ resource restartVirtualMachine 'Microsoft.Compute/virtualMachines/runCommands@20 asyncExecution: false parameters: [ { - name: 'Environment' - value: environment().name - } - { - name: 'ResourceGroupName' - value: resourceGroupName - } - { - name: 'SubscriptionId' - value: subscription().subscriptionId - } - { - name: 'TenantId' - value: tenant().tenantId + name: 'ResourceManagerUri' + value: environment().resourceManager } { name: 'UserAssignedIdentityClientId' value: userAssignedIdentityClientId } { - name: 'VirtualMachineName' - value: imageVirtualMachine.name + name: 'VmResourceId' + value: imageVirtualMachine.id } ] source: { script: ''' param( - [string]$Environment, - [string]$ResourceGroupName, - [string]$SubscriptionId, - [string]$TenantId, - [string]$UserAssignedIdentityClientId, - [string]$VirtualMachineName + [Parameter(Mandatory=$true)] + [string]$ResourceManagerUri, + + [Parameter(Mandatory=$true)] + [string]$UserAssignedIdentityClientId, + + [Parameter(Mandatory=$true)] + [string]$VmResourceId ) + $ErrorActionPreference = 'Stop' - Connect-AzAccount -Environment $Environment -Tenant $TenantId -Subscription $SubscriptionId -Identity -AccountId $UserAssignedIdentityClientId | Out-Null - Restart-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName - $AgentStatus = $Null - while ($Null -eq $AgentStatus) - { - Start-Sleep -Seconds 5 - $AgentStatus = (Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VirtualMachineName -Status).VMAgent + $WarningPreference = 'SilentlyContinue' + + Try { + # Fix the resource manager URI since only AzureCloud contains a trailing slash + $ResourceManagerUriFixed = if($ResourceManagerUri[-1] -eq '/'){$ResourceManagerUri.Substring(0,$ResourceManagerUri.Length - 1)} else {$ResourceManagerUri} + + # Get an access token for Azure resources + $AzureManagementAccessToken = (Invoke-RestMethod ` + -Headers @{Metadata="true"} ` + -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token + + # Set header for Azure Management API + $AzureManagementHeader = @{ + 'Content-Type'='application/json' + 'Authorization'='Bearer ' + $AzureManagementAccessToken + } + + # Restart the VM + $null = Invoke-RestMethod -Headers $AzureManagementHeader -Method 'Post' -Uri $($ResourceManagerUriFixed + $VmResourceId + '/restart?api-version=2024-03-01') + $lastProvisioningState = "" + $VmStatus = Invoke-RestMethod -Headers $AzureManagementHeader -Method 'Get' -Uri $($ResourceManagerUriFixed + $VmResourceId + '/instanceView?api-version=2024-03-01') + $provisioningState = ($VMStatus.statuses | Where-Object {$_.code -like 'PowerState*'}).code + While ($provisioningState -ne "PowerState/running") { + $lastProvisioningState = $provisioningState + Start-Sleep -Seconds 5 + $VmStatus = Invoke-RestMethod -Headers $AzureManagementHeader -Method 'Get' -Uri $($ResourceManagerUriFixed + $VmResourceId + '/instanceView?api-version=2024-03-01') + $provisioningState = ($VMStatus.statuses | Where-Object {$_.code -like 'PowerState*'}).code + } + } + catch { + throw } ''' } diff --git a/src/bicep/add-ons/imaging/scripts/New-AzureZeroTrustImageBuild.ps1 b/src/bicep/add-ons/imaging/scripts/New-AzureZeroTrustImageBuild.ps1 index 142216563..ec7baeb16 100644 --- a/src/bicep/add-ons/imaging/scripts/New-AzureZeroTrustImageBuild.ps1 +++ b/src/bicep/add-ons/imaging/scripts/New-AzureZeroTrustImageBuild.ps1 @@ -6,128 +6,153 @@ param( $ErrorActionPreference = 'Stop' -try -{ +try { # Convert JSON string to PowerShell $Values = $Parameters.Replace('\"', '"') | ConvertFrom-Json # Set Variables - $DestinationGalleryName = $Values.computeGalleryResourceId.Split('/')[8] $DestinationGalleryResourceGroupName = $Values.computeGalleryResourceId.Split('/')[4] $DestinationImageDefinitionName = $Values.imageDefinitionName + $UserAssignedIdentityClientId = $Values.userAssignedIdentityClientId + $ResourceManagerUri = $Values.resourceManagerUri - # Import Modules - Import-Module -Name 'Az.Accounts','Az.Compute','Az.Resources' - Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Imported the required modules." + # Fix the resource manager URI since only AzureCloud contains a trailing slash + $ResourceManagerUriFixed = if ($ResourceManagerUri[-1] -eq '/') { $ResourceManagerUri.Substring(0, $ResourceManagerUri.Length - 1) } else { $ResourceManagerUri } - # Disable saving of Azure Context - Disable-AzContextAutosave -Scope Process | Out-Null - Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Disabled saving of Azure Context." + # Get an access token for Azure resources + $AzureManagementAccessToken = (Invoke-RestMethod ` + -Headers @{Metadata = "true" } ` + -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token - # Connect to Azure using the System Assigned Identity - $AzureContext = (Connect-AzAccount -Environment $Values.environmentName -Subscription $Values.subscriptionId -Tenant $Values.tenantId -Identity -AccountId $Values.userAssignedIdentityClientId).Context - Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Connected to Azure." - - # Cleanup previous image build - $RunCommandNames = @('generalizeVirtualMachine','removeVirtualMachine','restartVirtualMachine') - foreach($RunCommandName in $RunCommandNames) - { - Remove-AzVMRunCommand -ResourceGroupName $Values.resourceGroupName -VMName $Values.managementVirtualMachineName -RunCommandName $RunCommandName - Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Removed '$RunCommandName' Run Command" - } + Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Obtained OATH token for Azure." + + # Set header for Azure Management API + $AzureManagementHeader = @{ + 'Content-Type' = 'application/json' + 'Authorization' = 'Bearer ' + $AzureManagementAccessToken + } + $RunCommandNames = @('generalizeVirtualMachine', 'removeVirtualMachine', 'restartVirtualMachine') + foreach ($RunCommandName in $RunCommandNames) { + Invoke-RestMethod ` + -Headers $AzureManagementHeader ` + -Method 'Delete' ` + -Uri $($ResourceManagerUriFixed + '/subscriptions/' + $Values.subscriptionId + '/resourceGroups/' + $Values.resourceGroupName + '/providers/Microsoft.Compute/virtualMachines/' + $Values.managementVirtualMachineName + '/runCommands/' + $RunCommandName + '?api-version=2024-03-01') + Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Removed '$RunCommandName' Run Command" + } # Get date on the latest image gallery version - $CurrentImageVersionDate = (Get-AzGalleryImageVersion -ResourceGroupName $DestinationGalleryResourceGroupName -GalleryName $DestinationGalleryName -GalleryImageDefinitionName $DestinationImageDefinitionName -DefaultProfile $AzureContext | Where-Object {$_.ProvisioningState -eq 'Succeeded'}).PublishingProfile.PublishedDate | Sort-Object | Select-Object -Last 1 - Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Compute Gallery Image (Destination), Latest Version Date: $CurrentImageVersionDate." + $GalleryImageVersions = (Invoke-RestMethod ` + -Headers $AzureManagementHeader ` + -Method 'Get' ` + -Uri $($ResourceManagerUriFixed + $Values.computeGalleryResourceId + '/images/' + $DestinationImageDefinitionName + '/versions?api-version=2023-07-03')).value | Where-Object { $_.properties.provisioningState -eq 'Succeeded' } + $CurrentImageVersionDate = $GalleryImageVersions.Properties.PublishingProfile.PublishedDate | Sort-Object | Select-Object -Last 1 + Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Compute Gallery Image (Destination), Latest Version Date: $CurrentImageVersionDate." - switch($Values.sourceImageType) - { - 'AzureComputeGallery' { - # Set Variables - $SourceGalleryName = $Values.computeGalleryImageResourceId.Split('/')[8] - $SourceGalleryResourceGroupName = $Values.computeGalleryImageResourceId.Split('/')[4] - $SourceImageDefinitionName = $Values.computeGalleryImageResourceId.Split('/')[10] + switch ($Values.sourceImageType) { + 'AzureComputeGallery' { - # Get the date of the latest image definition version - $SourceImageVersionDate = (Get-AzGalleryImageVersion -ResourceGroupName $SourceGalleryResourceGroupName -GalleryName $SourceGalleryName -GalleryImageDefinitionName $SourceImageDefinitionName -DefaultProfile $AzureContext | Where-Object {$_.PublishingProfile.ExcludeFromLatest -eq $false -and $_.ProvisioningState -eq 'Succeeded'}).PublishingProfile.PublishedDate | Sort-Object | Select-Object -Last 1 - Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Compute Gallery Image (Source), Latest Version Date: $SourceImageVersionDate." - } - 'AzureMarketplace' { - # Get the date of the latest marketplace image version - $ImageVersionDateRaw = (Get-AzVMImage -Location $Values.location -PublisherName $Values.marketplaceImagePublisher -Offer $Values.marketplaceImageOffer -Skus $Values.marketplaceImageSku -DefaultProfile $AzureContext | Sort-Object -Property 'Version' -Descending | Select-Object -First 1).Version.Split('.')[-1] - $Year = '20' + $ImageVersionDateRaw.Substring(0,2) - $Month = $ImageVersionDateRaw.Substring(2,2) - $Day = $ImageVersionDateRaw.Substring(4,2) - $SourceImageVersionDate = Get-Date -Year $Year -Month $Month -Day $Day -Hour 00 -Minute 00 -Second 00 - Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Marketplace Image (Source), Latest Version Date: $SourceImageVersionDate." - } - } + # Get the date of the latest image definition version + $SourceGalleryImageVersions = (Invoke-RestMethod ` + -Headers $AzureManagementHeader ` + -Method 'Get' ` + -Uri $($ResourceManagerUriFixed + $Values.computeGalleryImageResourceId + '/versions?api-version=2023-07-03')).value | Where-Object { $_.PublishingProfile.ExcludeFromLatest -eq $false -and $_.properties.provisioningState -eq 'Succeeded' } + $SourceImageVersionDate = $SourceGalleryImageVersions.Properties.PublishingProfile.PublishedDate | Sort-Object | Select-Object -Last 1 + Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Compute Gallery Image (Source), Latest Version Date: $SourceImageVersionDate." + } + 'AzureMarketplace' { + # Get the date of the latest marketplace image version + $MarketPlaceImageVersions = Invoke-RestMethod ` + -Header $AzureManagementHeader ` + -Method 'Get' ` + -Uri $($ResourceManagerUriFixed + '/subscriptions/' + $Values.subscriptionId + '/providers/Microsoft.Compute/locations/' + $Values.location + '/publishers/' + $Values.marketplaceImagePublisher + '/artifacttypes/vmimage/offers/' + $Values.marketplaceImageOffer + '/skus/' + $Values.marketplaceImageSku + '/versions?api-version=2024-03-01') + $ImageVersionDateRaw = ($MarketPlaceImageVersions | Sort-Object -Property 'Name' -Descending | Select-Object -First 1).Name.Split('.')[-1] + $Year = '20' + $ImageVersionDateRaw.Substring(0, 2) + $Month = $ImageVersionDateRaw.Substring(2, 2) + $Day = $ImageVersionDateRaw.Substring(4, 2) + $SourceImageVersionDate = Get-Date -Year $Year -Month $Month -Day $Day -Hour 00 -Minute 00 -Second 00 + Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Marketplace Image (Source), Latest Version Date: $SourceImageVersionDate." + } + } # If the latest source image was released after the last image build then trigger a new image build - if($SourceImageVersionDate -gt $CurrentImageVersionDate -or !$CurrentImageVersionDate) - { + if ($SourceImageVersionDate -gt $CurrentImageVersionDate -or !$CurrentImageVersionDate) { Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Image build initiated with a new source image version." $TemplateParameters = @{ - arcGisProInstaller = $Values.arcGisProInstaller - computeGalleryImageResourceId = $Values.computeGalleryImageResourceId - computeGalleryName = $Values.computeGalleryResourceId.Split('/')[8] - containerName = $Values.containerName - diskEncryptionSetResourceId = $Values.diskEncryptionSetResourceId - enableBuildAutomation = if($Values.enableBuildAutomation -eq 'true'){$true}else{$false} - excludeFromLatest = $Values.excludeFromLatest - hybridUseBenefit = if($Values.hybridUseBenefit -eq 'true'){$true}else{$false} - imageDefinitionName = $Values.imageDefinitionName - imageMajorVersion = [int]$Values.imageMajorVersion - imagePatchVersion = [int]$Values.imagePatchVersion - imageVirtualMachineName = $Values.imageVirtualMachineName - installAccess = if($Values.installAccess -eq 'true'){$true}else{$false} - installArcGisPro = if($Values.installArcGisPro -eq 'true'){$true}else{$false} - installExcel = if($Values.installExcel -eq 'true'){$true}else{$false} - installOneDrive = if($Values.installOneDrive -eq 'true'){$true}else{$false} - installOneNote = if($Values.installOneNote -eq 'true'){$true}else{$false} - installOutlook = if($Values.installOutlook -eq 'true'){$true}else{$false} - installPowerPoint = if($Values.installPowerPoint -eq 'true'){$true}else{$false} - installProject = if($Values.installProject -eq 'true'){$true}else{$false} - installPublisher = if($Values.installPublisher -eq 'true'){$true}else{$false} - installSkypeForBusiness = if($Values.installSkypeForBusiness -eq 'true'){$true}else{$false} - installTeams = if($Values.installTeams -eq 'true'){$true}else{$false} - installVirtualDesktopOptimizationTool = if($Values.installVirtualDesktopOptimizationTool -eq 'true'){$true}else{$false} - installVisio = if($Values.installVisio -eq 'true'){$true}else{$false} - installWord = if($Values.installWord -eq 'true'){$true}else{$false} - keyVaultName = $Values.keyVaultName - managementVirtualMachineName = $Values.managementVirtualMachineName - marketplaceImageOffer = $Values.marketplaceImageOffer - marketplaceImagePublisher = $Values.marketplaceImagePublisher - marketplaceImageSKU = $Values.marketplaceImageSKU - msrdcwebrtcsvcInstaller = $Values.msrdcwebrtcsvcInstaller - officeInstaller = $Values.officeInstaller - replicaCount = [int]$Values.replicaCount - resourceGroupName = $Values.resourceGroupName - runbookExecution = $true - sourceImageType = $Values.sourceImageType - storageAccountResourceId = $Values.storageAccountResourceId - subnetResourceId = $Values.subnetResourceId - teamsInstaller = $Values.teamsInstaller - userAssignedIdentityClientId = $Values.userAssignedIdentityClientId - userAssignedIdentityPrincipalId = $Values.userAssignedIdentityPrincipalId - userAssignedIdentityResourceId = $Values.userAssignedIdentityResourceId - vcRedistInstaller = $Values.vcRedistInstaller - vDOTInstaller = $Values.vDOTInstaller - virtualMachineSize = $Values.virtualMachineSize - } - if($Values.customizations -ne '[]'){$TemplateParameters.Add('customizations', $Values.customizations)} - if($Values.mlzTags -ne '{}'){$TemplateParameters.Add('mlzTags', $Values.mlzTags)} - if($Values.tags -ne '{}'){$TemplateParameters.Add('tags', $Values.tags)} - New-AzDeployment -Location $Values.location -TemplateSpecId $Values.templateSpecResourceId -TemplateParameterObject $TemplateParameters -DefaultProfile $AzureContext + arcGisProInstaller = $Values.arcGisProInstaller + computeGalleryImageResourceId = $Values.computeGalleryImageResourceId + computeGalleryName = $Values.computeGalleryResourceId.Split('/')[8] + containerName = $Values.containerName + diskEncryptionSetResourceId = $Values.diskEncryptionSetResourceId + enableBuildAutomation = if ($Values.enableBuildAutomation -eq 'true') { $true }else { $false } + excludeFromLatest = $Values.excludeFromLatest + hybridUseBenefit = if ($Values.hybridUseBenefit -eq 'true') { $true }else { $false } + imageDefinitionName = $Values.imageDefinitionName + imageMajorVersion = [int]$Values.imageMajorVersion + imagePatchVersion = [int]$Values.imagePatchVersion + imageVirtualMachineName = $Values.imageVirtualMachineName + installAccess = if ($Values.installAccess -eq 'true') { $true }else { $false } + installArcGisPro = if ($Values.installArcGisPro -eq 'true') { $true }else { $false } + installExcel = if ($Values.installExcel -eq 'true') { $true }else { $false } + installOneDrive = if ($Values.installOneDrive -eq 'true') { $true }else { $false } + installOneNote = if ($Values.installOneNote -eq 'true') { $true }else { $false } + installOutlook = if ($Values.installOutlook -eq 'true') { $true }else { $false } + installPowerPoint = if ($Values.installPowerPoint -eq 'true') { $true }else { $false } + installProject = if ($Values.installProject -eq 'true') { $true }else { $false } + installPublisher = if ($Values.installPublisher -eq 'true') { $true }else { $false } + installSkypeForBusiness = if ($Values.installSkypeForBusiness -eq 'true') { $true }else { $false } + installTeams = if ($Values.installTeams -eq 'true') { $true }else { $false } + installVirtualDesktopOptimizationTool = if ($Values.installVirtualDesktopOptimizationTool -eq 'true') { $true }else { $false } + installVisio = if ($Values.installVisio -eq 'true') { $true }else { $false } + installWord = if ($Values.installWord -eq 'true') { $true }else { $false } + keyVaultName = $Values.keyVaultName + managementVirtualMachineName = $Values.managementVirtualMachineName + marketplaceImageOffer = $Values.marketplaceImageOffer + marketplaceImagePublisher = $Values.marketplaceImagePublisher + marketplaceImageSKU = $Values.marketplaceImageSKU + msrdcwebrtcsvcInstaller = $Values.msrdcwebrtcsvcInstaller + officeInstaller = $Values.officeInstaller + replicaCount = [int]$Values.replicaCount + resourceGroupName = $Values.resourceGroupName + runbookExecution = $true + sourceImageType = $Values.sourceImageType + storageAccountResourceId = $Values.storageAccountResourceId + subnetResourceId = $Values.subnetResourceId + teamsInstaller = $Values.teamsInstaller + userAssignedIdentityClientId = $Values.userAssignedIdentityClientId + userAssignedIdentityPrincipalId = $Values.userAssignedIdentityPrincipalId + userAssignedIdentityResourceId = $Values.userAssignedIdentityResourceId + vcRedistInstaller = $Values.vcRedistInstaller + vDOTInstaller = $Values.vDOTInstaller + virtualMachineSize = $Values.virtualMachineSize + } + if ($Values.customizations -ne '[]') { $TemplateParameters.Add('customizations', $Values.customizations) } + if ($Values.mlzTags -ne '{}') { $TemplateParameters.Add('mlzTags', $Values.mlzTags) } + if ($Values.tags -ne '{}') { $TemplateParameters.Add('tags', $Values.tags) } + + $Body = @{ + 'location' = $Values.location + 'properties' = @{ + 'mode' = 'Incremental' + 'templateLink' = @{ + 'id' = $Value.templateSpecResourceId + } + 'parameters' = $TemplateParameters + } + } + $Timestamp = Get-Date -Format 'yyyy.MM.dd-HH.mm.ss' + Invoke-RestMethod ` + -Header $AzureManagementHeader ` + -Body ($Body | ConvertTo-Json) ` + -Method 'PUT' ` + -Uri $($ResourceManagerUriFixed + '/subscriptions/' + $Values.subscriptionId + '/providers/Microsoft.Resources/deployments/imageBuild_' + $timeStamp + '?api-version=2021-04-01') + Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Image build succeeded. New image version available in the destination Compute Gallery." } - else - { + else { Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Image build not required. The source image version is older than the latest destination image version." } } -catch -{ +catch { Write-Output "$DestinationImageDefinitionName | $DestinationGalleryResourceGroupName | Image build failed. Review the deployment errors in the Azure Portal and correct the issue." Write-Output $($Error[0] | Select-Object *) throw diff --git a/src/bicep/add-ons/imaging/scripts/Remove-AzureRunCommands.ps1 b/src/bicep/add-ons/imaging/scripts/Remove-AzureRunCommands.ps1 new file mode 100644 index 000000000..6ef619b43 --- /dev/null +++ b/src/bicep/add-ons/imaging/scripts/Remove-AzureRunCommands.ps1 @@ -0,0 +1,43 @@ +param( + [Parameter(Mandatory=$true)] + [string]$ResourceManagerUri, + + [Parameter(Mandatory=$true)] + [array]$RunCommands, + + [Parameter(Mandatory=$true)] + [string]$UserAssignedIdentityClientId, + + + [Parameter(Mandatory=$true)] + [string]$VmResourceId +) + +$ErrorActionPreference = 'Stop' +$WarningPreference = 'SilentlyContinue' + +Try { + # Fix the resource manager URI since only AzureCloud contains a trailing slash + $ResourceManagerUriFixed = if($ResourceManagerUri[-1] -eq '/'){$ResourceManagerUri.Substring(0,$ResourceManagerUri.Length - 1)} else {$ResourceManagerUri} + + # Get an access token for Azure resources + $AzureManagementAccessToken = (Invoke-RestMethod ` + -Headers @{Metadata="true"} ` + -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token + + # Set header for Azure Management API + $AzureManagementHeader = @{ + 'Content-Type'='application/json' + 'Authorization'='Bearer ' + $AzureManagementAccessToken + } + + ForEach($RunCommand in $RunCommands) { + Invoke-RestMethod ` + -Headers $AzureManagementHeader ` + -Method 'Delete' ` + -Uri $($ResourceManagerUriFixed + $VmResourceId + '/runCommands/' + $RunCommand + '?api-version=2024-03-01') + } +} +catch { + throw +} \ No newline at end of file