Skip to content

Commit

Permalink
Add-On Imaging: Improvements, Bug Fixes, and Software Bundler (#1063)
Browse files Browse the repository at this point in the history
* Fixes

* updated some logic

* moved $Path around

* fixed default value for update service

* Updates and fixes to customizations and bundle ps1

---------

Co-authored-by: Jason Masten <jamasten@microsoft.com>
  • Loading branch information
chbragg and jamasten authored Aug 10, 2024
1 parent c35513e commit deb948c
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 35 deletions.
2 changes: 2 additions & 0 deletions src/bicep/add-ons/imaging/modules/automationAccount.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ param excludeFromLatest bool
param hybridUseBenefit bool
param imageDefinitionName string
param imageMajorVersion int
param imageMinorVersion int
param imagePatchVersion int
param imageVirtualMachineName string
param installAccess bool
Expand Down Expand Up @@ -84,6 +85,7 @@ var parameters = {
hybridUseBenefit: hybridUseBenefit
imageDefinitionName: imageDefinitionName
imageMajorVersion: string(imageMajorVersion)
imageMinorVersion: string(imageMinorVersion)
imagePatchVersion: string(imagePatchVersion)
imageVirtualMachineName: imageVirtualMachineName
installAccess: string(installAccess)
Expand Down
3 changes: 3 additions & 0 deletions src/bicep/add-ons/imaging/modules/buildAutomation.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ param excludeFromLatest bool
param hybridUseBenefit bool
param imageDefinitionName string
param imageMajorVersion int
param imageMinorVersion int
param imagePatchVersion int
param imageVirtualMachineName string
param installAccess bool
Expand Down Expand Up @@ -149,6 +150,7 @@ module managementVM 'managementVM.bicep' = {

userAssignedIdentityResourceId: userAssignedIdentityResourceId
virtualMachineName: managementVirtualMachineName
virtualMachineSize: virtualMachineSize
}
}

Expand All @@ -175,6 +177,7 @@ module automationAccount 'automationAccount.bicep' = {
hybridUseBenefit: hybridUseBenefit
imageDefinitionName: imageDefinitionName
imageMajorVersion: imageMajorVersion
imageMinorVersion: imageMinorVersion
imagePatchVersion: imagePatchVersion
imageVirtualMachineName: imageVirtualMachineName
installAccess: installAccess
Expand Down
65 changes: 43 additions & 22 deletions src/bicep/add-ons/imaging/modules/customizations.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ param virtualMachineName string

var installAccessVar = '${installAccess}installAccess'
var installers = customizations
var installExcelVar = '${installExcel}installWord'
var installExcelVar = '${installExcel}installExcel'
var installOneDriveVar = '${installOneDrive}installOneDrive'
var installOneNoteVar = '${installOneNote}installOneNote'
var installOutlookVar = '${installOutlook}installOutlook'
Expand All @@ -54,7 +54,7 @@ resource virtualMachine 'Microsoft.Compute/virtualMachines@2022-11-01' existing

@batchSize(1)
resource applications 'Microsoft.Compute/virtualMachines/runCommands@2023-03-01' = [
for installer in installers: {
for installer in installers: if (installer.enabled) {
parent: virtualMachine
name: 'app-${installer.name}'
location: location
Expand Down Expand Up @@ -111,32 +111,46 @@ resource applications 'Microsoft.Compute/virtualMachines/runCommands@2023-03-01'
$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
$BlobFileName = $BlobName.Split("/")[-1]
New-Item -Path $env:windir\temp -Name $Installer -ItemType "directory" -Force
New-Item -Path $env:windir\temp\$Installer -Name 'Files' -ItemType "directory" -Force
$InstallerDirectory = "$env:windir\temp\$Installer"
Write-Host "Setting file copy to install directory: $InstallerDirectory"
Set-Location -Path $InstallerDirectory
Write-Host "Invoking WebClient download for file : $BlobFileName"
#Invoking WebClient to download blobs because it is more efficient than Invoke-WebRequest for large files.
$WebClient = New-Object System.Net.WebClient
$WebClient.Headers.Add('x-ms-version', '2017-11-09')
$webClient.Headers.Add("Authorization", "Bearer $AccessToken")
$webClient.DownloadFile("$StorageAccountUrl$ContainerName/$BlobName", "$env:windir\temp\$Installer\Files\$Blobname")
$webClient.DownloadFile("$StorageAccountUrl$ContainerName/$BlobName", "$InstallerDirectory\$BlobName")
Start-Sleep -Seconds 30
Set-Location -Path $env:windir\temp\$Installer
if($Blobname -like ("*.exe"))
$Path = (Get-ChildItem -Path "$InstallerDirectory\$BlobName" -Recurse | Where-Object {$_.Name -eq "$BlobName"}).FullName
if($BlobName -like ("*.exe"))
{
Start-Process -FilePath $env:windir\temp\$Installer\Files\$Blobname -ArgumentList $Arguments -NoNewWindow -Wait -PassThru
$status = Get-WmiObject -Class Win32_Product | Where-Object Name -like "*$($installer)*"
if($status)
Start-Process -FilePath $InstallerDirectory\$BlobName -ArgumentList $Arguments -NoNewWindow -Wait -PassThru
$wmistatus = Get-WmiObject -Class Win32_Product | Where-Object Name -like "*$($Installer)*"
if($wmistatus)
{
Write-Host $status.Name "is installed"
Write-Host $wmistatus.Name "is installed"
}
$regstatus = Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' | Where-Object { $_.DisplayName -like "*$($Installer)*" }
if($regstatus)
{
Write-Host $regstatus.DisplayName "is installed"
}
$regstatusWow6432 = Get-ItemProperty 'HKLM:\Software\WOW6432Node\*' | Where-Object { $_.PSChildName -like "*$($Installer)*" }
if($regstatusWow6432)
{
Write-Host $regstatusWow6432.PSChildName "is installed"
}
else
{
Write-host $Installer "did not install properly, please check arguments"
}
}
if($Blobname -like ("*.msi"))
if($BlobName -like ("*.msi"))
{
Set-Location -Path $env:windir\temp\$Installer\Files
Start-Process -FilePath msiexec.exe -ArgumentList $Arguments -Wait
Write-Host "Invoking msiexec.exe for install path : $Path"
Start-Process -FilePath msiexec.exe -ArgumentList "/i $Path $Arguments" -Wait
$status = Get-WmiObject -Class Win32_Product | Where-Object Name -like "*$($installer)*"
if($status)
{
Expand All @@ -147,22 +161,29 @@ resource applications 'Microsoft.Compute/virtualMachines/runCommands@2023-03-01'
Write-host $Installer "did not install properly, please check arguments"
}
}
if($Blobname -like ("*.bat"))
if($BlobName -like ("*.bat"))
{
Start-Process -FilePath cmd.exe -ArgumentList $env:windir\temp\$Installer\Files\$Arguments -Wait
Start-Process -FilePath cmd.exe -ArgumentList $InstallerDirectory\$Arguments -Wait
}
if($Blobname -like ("*.ps1"))
if($BlobName -like ("*.ps1"))
{
Start-Process -FilePath PowerShell.exe -ArgumentList $env:windir\temp\$Installer\Files\$Arguments -Wait
if($BlobName -like ("Install-BundleSoftware.ps1"))
{
Start-Process -FilePath PowerShell.exe -ArgumentList "-File $Path -UserAssignedIdentityObjectId $UserAssignedIdentityObjectId -StorageAccountName $StorageAccountName -ContainerName $ContainerName -StorageEndpoint $StorageEndpoint $Arguments" -Wait
}
else
{
Start-Process -FilePath PowerShell.exe -ArgumentList "-File $Path $Arguments" -Wait
}
}
if($Blobname -like ("*.zip"))
if($BlobName -like ("*.zip"))
{
Set-Location -Path $env:windir\temp\$Installer\Files
Expand-Archive -Path $env:windir\temp\$Installer\Files\$Blobname -DestinationPath $env:windir\temp\$Installer\Files -Force
Remove-Item -Path .\$Blobname -Force -Recurse
Expand-Archive -Path $InstallerDirectory\$BlobName -DestinationPath $InstallerDirectory -Force
Remove-Item -Path .\$BlobName -Force -Recurse
}
Write-Host "Removing $Installer Files"
Remove-item $env:windir\temp\$Installer -Force -Recurse -Confirm:$false
#Start-Sleep -Seconds 5
Remove-item $env:windir\temp\$Installer -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue
'''
}
}
Expand Down
9 changes: 5 additions & 4 deletions src/bicep/add-ons/imaging/modules/imageBuild.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ param excludeFromLatest bool = true
param hybridUseBenefit bool = false
param imageDefinitionName string
param imageMajorVersion int
param imageMinorVersion int
param imagePatchVersion int
param imageVirtualMachineName string
param installAccess bool = false
Expand Down Expand Up @@ -55,7 +56,7 @@ param storageAccountResourceId string
param subnetResourceId string
param tags object = {}
param teamsInstaller string = ''
param updateService string = 'MicrosoftUpdate'
param updateService string = 'MU'
param userAssignedIdentityClientId string
param userAssignedIdentityPrincipalId string
param userAssignedIdentityResourceId string
Expand All @@ -64,8 +65,7 @@ param vDOTInstaller string = ''
param virtualMachineSize string
param wsusServer string = ''

var autoImageVersion = '${imageMajorVersion}.${imageSuffix}.${imagePatchVersion}'
var imageSuffix = take(deploymentNameSuffix, 9)
var autoImageVersion = '${imageMajorVersion}.${imageMinorVersion}.${imagePatchVersion}'
var storageAccountName = split(storageAccountResourceId, '/')[8]
var storageEndpoint = environment().suffixes.storage
var subscriptionId = subscription().subscriptionId
Expand All @@ -91,6 +91,7 @@ module managementVM 'managementVM.bicep' =
tags: tags
userAssignedIdentityResourceId: userAssignedIdentityResourceId
virtualMachineName: managementVirtualMachineName
virtualMachineSize: virtualMachineSize
}
}

Expand Down Expand Up @@ -191,7 +192,7 @@ module microsoftUdpates 'microsoftUpdates.bicep' =
]
}

module restartVirtualMachine2 'restartVirtualMachine.bicep' = {
module restartVirtualMachine2 'restartVirtualMachine.bicep' = if (installUpdates) {
name: 'restart-vm-2-${deploymentNameSuffix}'
scope: resourceGroup(subscriptionId, resourceGroupName)
params: {
Expand Down
2 changes: 1 addition & 1 deletion src/bicep/add-ons/imaging/modules/keyVault.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ resource keyVault 'Microsoft.KeyVault/vaults@2021-10-01' = {
enabledForTemplateDeployment: true
enabledForDiskEncryption: false
enableRbacAuthorization: true
enableSoftDelete: false
enableSoftDelete: true
networkAcls: {
bypass: 'AzureServices'
defaultAction: 'Deny'
Expand Down
7 changes: 5 additions & 2 deletions src/bicep/add-ons/imaging/modules/managementVM.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ param tags object
//param userAssignedIdentityPrincipalId string
param userAssignedIdentityResourceId string
param virtualMachineName string
param virtualMachineSize string

resource networkInterface 'Microsoft.Network/networkInterfaces@2023-04-01' = {
name: 'nic-${virtualMachineName}'
Expand Down Expand Up @@ -53,14 +54,16 @@ resource virtualMachine 'Microsoft.Compute/virtualMachines@2022-03-01' = {
mlzTags
)
identity: {
type: 'UserAssigned'
type: 'SystemAssigned, UserAssigned'
//A System Assigned Identity is required for the Hybrid Runbook Worker Extension
//https://learn.microsoft.com/en-us/azure/automation/troubleshoot/extension-based-hybrid-runbook-worker#scenario-hybrid-worker-deployment-fails-due-to-system-assigned-identity-not-enabled
userAssignedIdentities: {
'${userAssignedIdentityResourceId}': {}
}
}
properties: {
hardwareProfile: {
vmSize: 'Standard_D2s_v3'
vmSize: virtualMachineSize
}
osProfile: {
computerName: virtualMachineName
Expand Down
134 changes: 134 additions & 0 deletions src/bicep/add-ons/imaging/scripts/Install-BundleSoftware.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<#
.SYNOPSIS
This script installs software on an Azure Virtual Machine from a Storage Account.
.DESCRIPTION
The Install-BundleSoftware function installs specified piece of software from a
Storage Account. It determines the software installation method by first downloading
the software installation file name and parameters from the Bundle Manifest json file.
.PARAMETER UserAssignedIdentityObjectId
Specifies the user Assigned Identity Object Id that is assigned to the virtual machine
and has access to the storage account.
.PARAMETER StorageAccountName
Specifies the storage account name where the software installation files are stored.
.PARAMETER ContainerName
Specifies the container in the storage account where the software installation files
are stored.
.PARAMETER StorageEndpoint
Specifies the endpoint for the storage account. This changes depending on which cloud
the storage account is in. Ex: core.windows.net, core.chinacloudapi.cn, core.cloudapi.de
core.usgovcloudapi.net, etc.
.PARAMETER BundleManifestBlob
Specifies the blob name of the Bundle Manifest json file that contains the software
installation file names and parameters.
.EXAMPLE
$UserAssignedIdentityObjectId = '00000000-0000-0000-0000-000000000000'
$StorageAccountName = 'myStorageAccount'
$ContainerName = 'myContainer'
$StorageEndpoint = 'core.windows.net'
$BundleManifestBlob = 'BundleManifest.json'
Install-BundleSoftware.ps1 -UserAssignedIdentityObjectId $UserAssignedIdentityObjectId
-StorageAccountName $StorageAccountName -ContainerName $ContainerName -StorageEndpoint
$StorageEndpoint -BundleManifestBlob $BundleManifestBlob
#>

param(
[string]$UserAssignedIdentityObjectId,
[string]$StorageAccountName,
[string]$ContainerName,
[string]$StorageEndpoint,
[string]$BundleManifestBlob
)
$ErrorActionPreference = 'Stop'
$WarningPreference = 'SilentlyContinue'
$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

$BundleManifest = "$env:windir\temp\bundlemanifest.json"
Invoke-WebRequest -Headers @{"x-ms-version"="2017-11-09"; Authorization ="Bearer $AccessToken"} -Uri "$StorageAccountUrl$ContainerName$BundleManifestBlob" -OutFile $BundleManifest
$Bundle = Get-Content -Raw -Path $BundleManifest | ConvertFrom-Json
Start-Sleep -Seconds 5

foreach ($item in $Bundle) {

If($true -eq $item.Enabled) {
$Installer = $item.name
$BlobName = $item.blobName
$Arguments = $item.arguments
Write-Host "Downloading $Installer from $BlobName"
$BlobFileName = $BlobName.Split("/")[-1]
New-Item -Path $env:windir\temp -Name $Installer -ItemType "directory" -Force
$InstallerDirectory = "$env:windir\temp\$Installer"
Write-Host "Setting file copy to install directory: $InstallerDirectory"
Set-Location -Path $InstallerDirectory
Write-Host "Invoking WebClient download for file : $BlobFileName"
#Invoking WebClient to download blobs because it is more efficient than Invoke-WebRequest for large files.
$WebClient = New-Object System.Net.WebClient
$WebClient.Headers.Add('x-ms-version', '2017-11-09')
$webClient.Headers.Add("Authorization", "Bearer $AccessToken")
$webClient.DownloadFile("$StorageAccountUrl$ContainerName/$BlobName", "$InstallerDirectory\$BlobName")
Start-Sleep -Seconds 30
$Path = (Get-ChildItem -Path "$InstallerDirectory\$BlobName" -Recurse | Where-Object {$_.Name -eq "$BlobName"}).FullName
if($BlobName -like ("*.exe"))
{
Start-Process -FilePath $InstallerDirectory\$BlobName -ArgumentList $Arguments -NoNewWindow -Wait -PassThru
$wmistatus = Get-WmiObject -Class Win32_Product | Where-Object Name -like "*$($Installer)*"
if($wmistatus)
{
Write-Host $wmistatus.Name "is installed"
}
$regstatus = Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' | Where-Object { $_.DisplayName -like "*$($Installer)*" }
if($regstatus)
{
Write-Host $regstatus.DisplayName "is installed"
}
$regstatusWow6432 = Get-ItemProperty 'HKLM:\Software\WOW6432Node\*' | Where-Object { $_.PSChildName -like "*$($Installer)*" }
if($regstatusWow6432)
{
Write-Host $regstatusWow6432.PSChildName "is installed"
}
else
{
Write-host $Installer "did not install properly, please check arguments"
}
}
if($BlobName -like ("*.msi"))
{
Write-Host "Invoking msiexec.exe for install path : $Path"
Start-Process -FilePath msiexec.exe -ArgumentList "/i $Path $Arguments" -Wait
$status = Get-WmiObject -Class Win32_Product | Where-Object Name -like "*$($installer)*"
if($status)
{
Write-Host $status.Name "is installed"
}
else
{
Write-host $Installer "did not install properly, please check arguments"
}
}
if($BlobName -like ("*.bat"))
{
Start-Process -FilePath cmd.exe -ArgumentList $InstallerDirectory\$Arguments -Wait
}
if($BlobName -like ("*.ps1") -and $BlobName -notlike ("Install-BundleSoftware.ps1"))
{
Start-Process -FilePath PowerShell.exe -ArgumentList "-File $Path $Arguments" -Wait
}
if($BlobName -like ("*.zip"))
{
Expand-Archive -Path $InstallerDirectory\$BlobName -DestinationPath $InstallerDirectory -Force
Remove-Item -Path .\$BlobName -Force -Recurse
}
Write-Host "Removing $Installer Files"
Start-Sleep -Seconds 5
Remove-item $InstallerDirectory -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue
}
}
Loading

0 comments on commit deb948c

Please sign in to comment.