Skip to content

Commit 3d74fee

Browse files
chbraggjamasten
andauthored
Add-On Imaging: Improvements, Bug Fixes, and Software Bundler (Azure#1063)
* 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>
1 parent 37dfd7d commit 3d74fee

File tree

10 files changed

+245
-35
lines changed

10 files changed

+245
-35
lines changed

src/bicep/add-ons/imaging/modules/automationAccount.bicep

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ param excludeFromLatest bool
2323
param hybridUseBenefit bool
2424
param imageDefinitionName string
2525
param imageMajorVersion int
26+
param imageMinorVersion int
2627
param imagePatchVersion int
2728
param imageVirtualMachineName string
2829
param installAccess bool
@@ -84,6 +85,7 @@ var parameters = {
8485
hybridUseBenefit: hybridUseBenefit
8586
imageDefinitionName: imageDefinitionName
8687
imageMajorVersion: string(imageMajorVersion)
88+
imageMinorVersion: string(imageMinorVersion)
8789
imagePatchVersion: string(imagePatchVersion)
8890
imageVirtualMachineName: imageVirtualMachineName
8991
installAccess: string(installAccess)

src/bicep/add-ons/imaging/modules/buildAutomation.bicep

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ param excludeFromLatest bool
2424
param hybridUseBenefit bool
2525
param imageDefinitionName string
2626
param imageMajorVersion int
27+
param imageMinorVersion int
2728
param imagePatchVersion int
2829
param imageVirtualMachineName string
2930
param installAccess bool
@@ -149,6 +150,7 @@ module managementVM 'managementVM.bicep' = {
149150

150151
userAssignedIdentityResourceId: userAssignedIdentityResourceId
151152
virtualMachineName: managementVirtualMachineName
153+
virtualMachineSize: virtualMachineSize
152154
}
153155
}
154156

@@ -175,6 +177,7 @@ module automationAccount 'automationAccount.bicep' = {
175177
hybridUseBenefit: hybridUseBenefit
176178
imageDefinitionName: imageDefinitionName
177179
imageMajorVersion: imageMajorVersion
180+
imageMinorVersion: imageMinorVersion
178181
imagePatchVersion: imagePatchVersion
179182
imageVirtualMachineName: imageVirtualMachineName
180183
installAccess: installAccess

src/bicep/add-ons/imaging/modules/customizations.bicep

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ param virtualMachineName string
3737

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

5555
@batchSize(1)
5656
resource applications 'Microsoft.Compute/virtualMachines/runCommands@2023-03-01' = [
57-
for installer in installers: {
57+
for installer in installers: if (installer.enabled) {
5858
parent: virtualMachine
5959
name: 'app-${installer.name}'
6060
location: location
@@ -111,32 +111,46 @@ resource applications 'Microsoft.Compute/virtualMachines/runCommands@2023-03-01'
111111
$StorageAccountUrl = "https://" + $StorageAccountName + ".blob." + $StorageEndpoint + "/"
112112
$TokenUri = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=$StorageAccountUrl&object_id=$UserAssignedIdentityObjectId"
113113
$AccessToken = ((Invoke-WebRequest -Headers @{Metadata=$true} -Uri $TokenUri -UseBasicParsing).Content | ConvertFrom-Json).access_token
114+
$BlobFileName = $BlobName.Split("/")[-1]
114115
New-Item -Path $env:windir\temp -Name $Installer -ItemType "directory" -Force
115-
New-Item -Path $env:windir\temp\$Installer -Name 'Files' -ItemType "directory" -Force
116+
$InstallerDirectory = "$env:windir\temp\$Installer"
117+
Write-Host "Setting file copy to install directory: $InstallerDirectory"
118+
Set-Location -Path $InstallerDirectory
119+
Write-Host "Invoking WebClient download for file : $BlobFileName"
116120
#Invoking WebClient to download blobs because it is more efficient than Invoke-WebRequest for large files.
117121
$WebClient = New-Object System.Net.WebClient
118122
$WebClient.Headers.Add('x-ms-version', '2017-11-09')
119123
$webClient.Headers.Add("Authorization", "Bearer $AccessToken")
120-
$webClient.DownloadFile("$StorageAccountUrl$ContainerName/$BlobName", "$env:windir\temp\$Installer\Files\$Blobname")
124+
$webClient.DownloadFile("$StorageAccountUrl$ContainerName/$BlobName", "$InstallerDirectory\$BlobName")
121125
Start-Sleep -Seconds 30
122-
Set-Location -Path $env:windir\temp\$Installer
123-
if($Blobname -like ("*.exe"))
126+
$Path = (Get-ChildItem -Path "$InstallerDirectory\$BlobName" -Recurse | Where-Object {$_.Name -eq "$BlobName"}).FullName
127+
if($BlobName -like ("*.exe"))
124128
{
125-
Start-Process -FilePath $env:windir\temp\$Installer\Files\$Blobname -ArgumentList $Arguments -NoNewWindow -Wait -PassThru
126-
$status = Get-WmiObject -Class Win32_Product | Where-Object Name -like "*$($installer)*"
127-
if($status)
129+
Start-Process -FilePath $InstallerDirectory\$BlobName -ArgumentList $Arguments -NoNewWindow -Wait -PassThru
130+
$wmistatus = Get-WmiObject -Class Win32_Product | Where-Object Name -like "*$($Installer)*"
131+
if($wmistatus)
128132
{
129-
Write-Host $status.Name "is installed"
133+
Write-Host $wmistatus.Name "is installed"
134+
}
135+
$regstatus = Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' | Where-Object { $_.DisplayName -like "*$($Installer)*" }
136+
if($regstatus)
137+
{
138+
Write-Host $regstatus.DisplayName "is installed"
139+
}
140+
$regstatusWow6432 = Get-ItemProperty 'HKLM:\Software\WOW6432Node\*' | Where-Object { $_.PSChildName -like "*$($Installer)*" }
141+
if($regstatusWow6432)
142+
{
143+
Write-Host $regstatusWow6432.PSChildName "is installed"
130144
}
131145
else
132146
{
133147
Write-host $Installer "did not install properly, please check arguments"
134148
}
135149
}
136-
if($Blobname -like ("*.msi"))
150+
if($BlobName -like ("*.msi"))
137151
{
138-
Set-Location -Path $env:windir\temp\$Installer\Files
139-
Start-Process -FilePath msiexec.exe -ArgumentList $Arguments -Wait
152+
Write-Host "Invoking msiexec.exe for install path : $Path"
153+
Start-Process -FilePath msiexec.exe -ArgumentList "/i $Path $Arguments" -Wait
140154
$status = Get-WmiObject -Class Win32_Product | Where-Object Name -like "*$($installer)*"
141155
if($status)
142156
{
@@ -147,22 +161,29 @@ resource applications 'Microsoft.Compute/virtualMachines/runCommands@2023-03-01'
147161
Write-host $Installer "did not install properly, please check arguments"
148162
}
149163
}
150-
if($Blobname -like ("*.bat"))
164+
if($BlobName -like ("*.bat"))
151165
{
152-
Start-Process -FilePath cmd.exe -ArgumentList $env:windir\temp\$Installer\Files\$Arguments -Wait
166+
Start-Process -FilePath cmd.exe -ArgumentList $InstallerDirectory\$Arguments -Wait
153167
}
154-
if($Blobname -like ("*.ps1"))
168+
if($BlobName -like ("*.ps1"))
155169
{
156-
Start-Process -FilePath PowerShell.exe -ArgumentList $env:windir\temp\$Installer\Files\$Arguments -Wait
170+
if($BlobName -like ("Install-BundleSoftware.ps1"))
171+
{
172+
Start-Process -FilePath PowerShell.exe -ArgumentList "-File $Path -UserAssignedIdentityObjectId $UserAssignedIdentityObjectId -StorageAccountName $StorageAccountName -ContainerName $ContainerName -StorageEndpoint $StorageEndpoint $Arguments" -Wait
173+
}
174+
else
175+
{
176+
Start-Process -FilePath PowerShell.exe -ArgumentList "-File $Path $Arguments" -Wait
177+
}
157178
}
158-
if($Blobname -like ("*.zip"))
179+
if($BlobName -like ("*.zip"))
159180
{
160-
Set-Location -Path $env:windir\temp\$Installer\Files
161-
Expand-Archive -Path $env:windir\temp\$Installer\Files\$Blobname -DestinationPath $env:windir\temp\$Installer\Files -Force
162-
Remove-Item -Path .\$Blobname -Force -Recurse
181+
Expand-Archive -Path $InstallerDirectory\$BlobName -DestinationPath $InstallerDirectory -Force
182+
Remove-Item -Path .\$BlobName -Force -Recurse
163183
}
164184
Write-Host "Removing $Installer Files"
165-
Remove-item $env:windir\temp\$Installer -Force -Recurse -Confirm:$false
185+
#Start-Sleep -Seconds 5
186+
Remove-item $env:windir\temp\$Installer -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue
166187
'''
167188
}
168189
}

src/bicep/add-ons/imaging/modules/imageBuild.bicep

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ param excludeFromLatest bool = true
1717
param hybridUseBenefit bool = false
1818
param imageDefinitionName string
1919
param imageMajorVersion int
20+
param imageMinorVersion int
2021
param imagePatchVersion int
2122
param imageVirtualMachineName string
2223
param installAccess bool = false
@@ -55,7 +56,7 @@ param storageAccountResourceId string
5556
param subnetResourceId string
5657
param tags object = {}
5758
param teamsInstaller string = ''
58-
param updateService string = 'MicrosoftUpdate'
59+
param updateService string = 'MU'
5960
param userAssignedIdentityClientId string
6061
param userAssignedIdentityPrincipalId string
6162
param userAssignedIdentityResourceId string
@@ -64,8 +65,7 @@ param vDOTInstaller string = ''
6465
param virtualMachineSize string
6566
param wsusServer string = ''
6667

67-
var autoImageVersion = '${imageMajorVersion}.${imageSuffix}.${imagePatchVersion}'
68-
var imageSuffix = take(deploymentNameSuffix, 9)
68+
var autoImageVersion = '${imageMajorVersion}.${imageMinorVersion}.${imagePatchVersion}'
6969
var storageAccountName = split(storageAccountResourceId, '/')[8]
7070
var storageEndpoint = environment().suffixes.storage
7171
var subscriptionId = subscription().subscriptionId
@@ -91,6 +91,7 @@ module managementVM 'managementVM.bicep' =
9191
tags: tags
9292
userAssignedIdentityResourceId: userAssignedIdentityResourceId
9393
virtualMachineName: managementVirtualMachineName
94+
virtualMachineSize: virtualMachineSize
9495
}
9596
}
9697

@@ -191,7 +192,7 @@ module microsoftUdpates 'microsoftUpdates.bicep' =
191192
]
192193
}
193194

194-
module restartVirtualMachine2 'restartVirtualMachine.bicep' = {
195+
module restartVirtualMachine2 'restartVirtualMachine.bicep' = if (installUpdates) {
195196
name: 'restart-vm-2-${deploymentNameSuffix}'
196197
scope: resourceGroup(subscriptionId, resourceGroupName)
197198
params: {

src/bicep/add-ons/imaging/modules/keyVault.bicep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ resource keyVault 'Microsoft.KeyVault/vaults@2021-10-01' = {
5656
enabledForTemplateDeployment: true
5757
enabledForDiskEncryption: false
5858
enableRbacAuthorization: true
59-
enableSoftDelete: false
59+
enableSoftDelete: true
6060
networkAcls: {
6161
bypass: 'AzureServices'
6262
defaultAction: 'Deny'

src/bicep/add-ons/imaging/modules/managementVM.bicep

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ param tags object
1818
//param userAssignedIdentityPrincipalId string
1919
param userAssignedIdentityResourceId string
2020
param virtualMachineName string
21+
param virtualMachineSize string
2122

2223
resource networkInterface 'Microsoft.Network/networkInterfaces@2023-04-01' = {
2324
name: 'nic-${virtualMachineName}'
@@ -53,14 +54,16 @@ resource virtualMachine 'Microsoft.Compute/virtualMachines@2022-03-01' = {
5354
mlzTags
5455
)
5556
identity: {
56-
type: 'UserAssigned'
57+
type: 'SystemAssigned, UserAssigned'
58+
//A System Assigned Identity is required for the Hybrid Runbook Worker Extension
59+
//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
5760
userAssignedIdentities: {
5861
'${userAssignedIdentityResourceId}': {}
5962
}
6063
}
6164
properties: {
6265
hardwareProfile: {
63-
vmSize: 'Standard_D2s_v3'
66+
vmSize: virtualMachineSize
6467
}
6568
osProfile: {
6669
computerName: virtualMachineName
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<#
2+
.SYNOPSIS
3+
This script installs software on an Azure Virtual Machine from a Storage Account.
4+
5+
.DESCRIPTION
6+
The Install-BundleSoftware function installs specified piece of software from a
7+
Storage Account. It determines the software installation method by first downloading
8+
the software installation file name and parameters from the Bundle Manifest json file.
9+
10+
.PARAMETER UserAssignedIdentityObjectId
11+
Specifies the user Assigned Identity Object Id that is assigned to the virtual machine
12+
and has access to the storage account.
13+
14+
.PARAMETER StorageAccountName
15+
Specifies the storage account name where the software installation files are stored.
16+
17+
.PARAMETER ContainerName
18+
Specifies the container in the storage account where the software installation files
19+
are stored.
20+
21+
.PARAMETER StorageEndpoint
22+
Specifies the endpoint for the storage account. This changes depending on which cloud
23+
the storage account is in. Ex: core.windows.net, core.chinacloudapi.cn, core.cloudapi.de
24+
core.usgovcloudapi.net, etc.
25+
26+
.PARAMETER BundleManifestBlob
27+
Specifies the blob name of the Bundle Manifest json file that contains the software
28+
installation file names and parameters.
29+
30+
.EXAMPLE
31+
$UserAssignedIdentityObjectId = '00000000-0000-0000-0000-000000000000'
32+
$StorageAccountName = 'myStorageAccount'
33+
$ContainerName = 'myContainer'
34+
$StorageEndpoint = 'core.windows.net'
35+
$BundleManifestBlob = 'BundleManifest.json'
36+
Install-BundleSoftware.ps1 -UserAssignedIdentityObjectId $UserAssignedIdentityObjectId
37+
-StorageAccountName $StorageAccountName -ContainerName $ContainerName -StorageEndpoint
38+
$StorageEndpoint -BundleManifestBlob $BundleManifestBlob
39+
40+
#>
41+
42+
param(
43+
[string]$UserAssignedIdentityObjectId,
44+
[string]$StorageAccountName,
45+
[string]$ContainerName,
46+
[string]$StorageEndpoint,
47+
[string]$BundleManifestBlob
48+
)
49+
$ErrorActionPreference = 'Stop'
50+
$WarningPreference = 'SilentlyContinue'
51+
$StorageAccountUrl = "https://" + $StorageAccountName + ".blob." + $StorageEndpoint + "/"
52+
$TokenUri = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=$StorageAccountUrl&object_id=$UserAssignedIdentityObjectId"
53+
$AccessToken = ((Invoke-WebRequest -Headers @{Metadata=$true} -Uri $TokenUri -UseBasicParsing).Content | ConvertFrom-Json).access_token
54+
55+
$BundleManifest = "$env:windir\temp\bundlemanifest.json"
56+
Invoke-WebRequest -Headers @{"x-ms-version"="2017-11-09"; Authorization ="Bearer $AccessToken"} -Uri "$StorageAccountUrl$ContainerName$BundleManifestBlob" -OutFile $BundleManifest
57+
$Bundle = Get-Content -Raw -Path $BundleManifest | ConvertFrom-Json
58+
Start-Sleep -Seconds 5
59+
60+
foreach ($item in $Bundle) {
61+
62+
If($true -eq $item.Enabled) {
63+
$Installer = $item.name
64+
$BlobName = $item.blobName
65+
$Arguments = $item.arguments
66+
Write-Host "Downloading $Installer from $BlobName"
67+
$BlobFileName = $BlobName.Split("/")[-1]
68+
New-Item -Path $env:windir\temp -Name $Installer -ItemType "directory" -Force
69+
$InstallerDirectory = "$env:windir\temp\$Installer"
70+
Write-Host "Setting file copy to install directory: $InstallerDirectory"
71+
Set-Location -Path $InstallerDirectory
72+
Write-Host "Invoking WebClient download for file : $BlobFileName"
73+
#Invoking WebClient to download blobs because it is more efficient than Invoke-WebRequest for large files.
74+
$WebClient = New-Object System.Net.WebClient
75+
$WebClient.Headers.Add('x-ms-version', '2017-11-09')
76+
$webClient.Headers.Add("Authorization", "Bearer $AccessToken")
77+
$webClient.DownloadFile("$StorageAccountUrl$ContainerName/$BlobName", "$InstallerDirectory\$BlobName")
78+
Start-Sleep -Seconds 30
79+
$Path = (Get-ChildItem -Path "$InstallerDirectory\$BlobName" -Recurse | Where-Object {$_.Name -eq "$BlobName"}).FullName
80+
if($BlobName -like ("*.exe"))
81+
{
82+
Start-Process -FilePath $InstallerDirectory\$BlobName -ArgumentList $Arguments -NoNewWindow -Wait -PassThru
83+
$wmistatus = Get-WmiObject -Class Win32_Product | Where-Object Name -like "*$($Installer)*"
84+
if($wmistatus)
85+
{
86+
Write-Host $wmistatus.Name "is installed"
87+
}
88+
$regstatus = Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' | Where-Object { $_.DisplayName -like "*$($Installer)*" }
89+
if($regstatus)
90+
{
91+
Write-Host $regstatus.DisplayName "is installed"
92+
}
93+
$regstatusWow6432 = Get-ItemProperty 'HKLM:\Software\WOW6432Node\*' | Where-Object { $_.PSChildName -like "*$($Installer)*" }
94+
if($regstatusWow6432)
95+
{
96+
Write-Host $regstatusWow6432.PSChildName "is installed"
97+
}
98+
else
99+
{
100+
Write-host $Installer "did not install properly, please check arguments"
101+
}
102+
}
103+
if($BlobName -like ("*.msi"))
104+
{
105+
Write-Host "Invoking msiexec.exe for install path : $Path"
106+
Start-Process -FilePath msiexec.exe -ArgumentList "/i $Path $Arguments" -Wait
107+
$status = Get-WmiObject -Class Win32_Product | Where-Object Name -like "*$($installer)*"
108+
if($status)
109+
{
110+
Write-Host $status.Name "is installed"
111+
}
112+
else
113+
{
114+
Write-host $Installer "did not install properly, please check arguments"
115+
}
116+
}
117+
if($BlobName -like ("*.bat"))
118+
{
119+
Start-Process -FilePath cmd.exe -ArgumentList $InstallerDirectory\$Arguments -Wait
120+
}
121+
if($BlobName -like ("*.ps1") -and $BlobName -notlike ("Install-BundleSoftware.ps1"))
122+
{
123+
Start-Process -FilePath PowerShell.exe -ArgumentList "-File $Path $Arguments" -Wait
124+
}
125+
if($BlobName -like ("*.zip"))
126+
{
127+
Expand-Archive -Path $InstallerDirectory\$BlobName -DestinationPath $InstallerDirectory -Force
128+
Remove-Item -Path .\$BlobName -Force -Recurse
129+
}
130+
Write-Host "Removing $Installer Files"
131+
Start-Sleep -Seconds 5
132+
Remove-item $InstallerDirectory -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue
133+
}
134+
}

0 commit comments

Comments
 (0)