Add-On Imaging: Improvements, Bug Fixes, and Software Bundler (#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>
This commit is contained in:
Chris Bragg 2024-08-09 23:24:32 -04:00 коммит произвёл GitHub
Родитель c35513e209
Коммит deb948cd4d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
10 изменённых файлов: 250 добавлений и 40 удалений

Просмотреть файл

@ -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
@ -84,6 +85,7 @@ var parameters = {
hybridUseBenefit: hybridUseBenefit
imageDefinitionName: imageDefinitionName
imageMajorVersion: string(imageMajorVersion)
imageMinorVersion: string(imageMinorVersion)
imagePatchVersion: string(imagePatchVersion)
imageVirtualMachineName: imageVirtualMachineName
installAccess: string(installAccess)

Просмотреть файл

@ -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
@ -149,6 +150,7 @@ module managementVM 'managementVM.bicep' = {
userAssignedIdentityResourceId: userAssignedIdentityResourceId
virtualMachineName: managementVirtualMachineName
virtualMachineSize: virtualMachineSize
}
}
@ -175,6 +177,7 @@ module automationAccount 'automationAccount.bicep' = {
hybridUseBenefit: hybridUseBenefit
imageDefinitionName: imageDefinitionName
imageMajorVersion: imageMajorVersion
imageMinorVersion: imageMinorVersion
imagePatchVersion: imagePatchVersion
imageVirtualMachineName: imageVirtualMachineName
installAccess: installAccess

Просмотреть файл

@ -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'
@ -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
@ -111,18 +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
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)
{
@ -133,36 +161,29 @@ resource applications 'Microsoft.Compute/virtualMachines/runCommands@2023-03-01'
Write-host $Installer "did not install properly, please check arguments"
}
}
if($Blobname -like ("*.msi"))
if($BlobName -like ("*.bat"))
{
Set-Location -Path $env:windir\temp\$Installer\Files
Start-Process -FilePath msiexec.exe -ArgumentList $Arguments -Wait
$status = Get-WmiObject -Class Win32_Product | Where-Object Name -like "*$($installer)*"
if($status)
Start-Process -FilePath cmd.exe -ArgumentList $InstallerDirectory\$Arguments -Wait
}
if($BlobName -like ("*.ps1"))
{
if($BlobName -like ("Install-BundleSoftware.ps1"))
{
Write-Host $status.Name "is installed"
Start-Process -FilePath PowerShell.exe -ArgumentList "-File $Path -UserAssignedIdentityObjectId $UserAssignedIdentityObjectId -StorageAccountName $StorageAccountName -ContainerName $ContainerName -StorageEndpoint $StorageEndpoint $Arguments" -Wait
}
else
{
Write-host $Installer "did not install properly, please check arguments"
Start-Process -FilePath PowerShell.exe -ArgumentList "-File $Path $Arguments" -Wait
}
}
if($Blobname -like ("*.bat"))
if($BlobName -like ("*.zip"))
{
Start-Process -FilePath cmd.exe -ArgumentList $env:windir\temp\$Installer\Files\$Arguments -Wait
}
if($Blobname -like ("*.ps1"))
{
Start-Process -FilePath PowerShell.exe -ArgumentList $env:windir\temp\$Installer\Files\$Arguments -Wait
}
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
'''
}
}

Просмотреть файл

@ -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
@ -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
@ -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
@ -91,6 +91,7 @@ module managementVM 'managementVM.bicep' =
tags: tags
userAssignedIdentityResourceId: userAssignedIdentityResourceId
virtualMachineName: managementVirtualMachineName
virtualMachineSize: virtualMachineSize
}
}
@ -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: {

Просмотреть файл

@ -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'

Просмотреть файл

@ -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}'
@ -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

Просмотреть файл

@ -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
}
}

Просмотреть файл

@ -0,0 +1,32 @@
[
{
"name": "Git",
"blobName": "Git-2.45.2-64-bit.exe",
"arguments": "/NORESTART /VERYSILENT /SUPPRESSMSGBOXES /FORCECLOSEAPPLICATIONS",
"enabled": false
},
{
"name": "VSCode",
"blobName": "VSCodeSetup-x64-1.91.1.exe",
"arguments": "/NORESTART /VERYSILENT /SUPPRESSMSGBOXES /MERGETASKS=!runcode",
"enabled": false
},
{
"name": "ScreenCaptureProtection",
"blobName": "Set-ScreenCaptureProtection.ps1",
"arguments": "",
"enabled": false
},
{
"name": "AzureStorageExplorer",
"blobName": "StorageExplorer-windows-x64.exe",
"arguments": "/NORESTART /VERYSILENT /SUPPRESSMSGBOXES /FORCECLOSEAPPLICATIONS /ALLUSERS",
"enabled": false
},
{
"name": "AzureCLI",
"blobName": "azure-cli-2.62.0-x64.msi",
"arguments": "/quiet /norestart",
"enabled": false
}
]

Просмотреть файл

@ -12,8 +12,15 @@ param computeGalleryImageResourceId string = ''
@description('The name of the container in the storage account where the installer files are located.')
param containerName string
@description('The array of customizations to apply to the image.')
param customizations array = []
@description('The array of customizations to apply to the image. Limit of 25 runCommands per virtual machine applies. Depending on other features used, the limit may be lower.')
param customizations array = [
{
name: 'InstallBundle'
blobName: 'Install-BundleSoftware.ps1'
arguments: '-BundleManifestBlob bundlemanifest.json'
enabled: false
}
]
@description('Choose whether to deploy a diagnostic setting for the Activity Log.')
param deployActivityLogDiagnosticSetting bool = false
@ -79,6 +86,9 @@ param imageDefinitionNamePrefix string
@description('The major version for the name of the image version resource.')
param imageMajorVersion int
@description('The minor version for the name of the image version resource.')
param imageMinorVersion int
@description('The patch version for the name of the image version resource.')
param imagePatchVersion int
@ -285,7 +295,7 @@ module baseline 'modules/baseline.bicep' = {
exemptPolicyAssignmentIds: exemptPolicyAssignmentIds
location: location
mlzTags: tier3.outputs.mlzTags
resourceGroupName: tier3.outputs.namingConvention.resourceGroup
resourceGroupName: tier3.outputs.resourceGroupName
storageAccountResourceId: storageAccountResourceId
subscriptionId: subscriptionId
tags: tags
@ -315,6 +325,7 @@ module buildAutomation 'modules/buildAutomation.bicep' = if (enableBuildAutomati
hybridUseBenefit: hybridUseBenefit
imageDefinitionName: imageDefinitionName
imageMajorVersion: imageMajorVersion
imageMinorVersion: imageMinorVersion
imagePatchVersion: imagePatchVersion
imageVirtualMachineName: replace(tier3.outputs.namingConvention.virtualMachine, tier3.outputs.tokens.service, 'b')
installAccess: installAccess
@ -332,7 +343,7 @@ module buildAutomation 'modules/buildAutomation.bicep' = if (enableBuildAutomati
installVirtualDesktopOptimizationTool: installVirtualDesktopOptimizationTool
installVisio: installVisio
installWord: installWord
keyVaultName: tier3.outputs.namingConvention.keyVault
keyVaultName: tier3.outputs.keyVaultName
keyVaultPrivateDnsZoneResourceId: keyVaultPrivateDnsZoneResourceId
localAdministratorPassword: localAdministratorPassword
localAdministratorUsername: localAdministratorUsername
@ -347,7 +358,7 @@ module buildAutomation 'modules/buildAutomation.bicep' = if (enableBuildAutomati
officeInstaller: officeInstaller
oUPath: oUPath
replicaCount: replicaCount
resourceGroupName: tier3.outputs.namingConvention.resourceGroup
resourceGroupName: tier3.outputs.resourceGroupName
sourceImageType: sourceImageType
storageAccountResourceId: storageAccountResourceId
subnetResourceId: tier3.outputs.subnetResourceId
@ -381,6 +392,7 @@ module imageBuild 'modules/imageBuild.bicep' = {
hybridUseBenefit: hybridUseBenefit
imageDefinitionName: imageDefinitionName
imageMajorVersion: imageMajorVersion
imageMinorVersion: imageMinorVersion
imagePatchVersion: imagePatchVersion
imageVirtualMachineName: replace(tier3.outputs.namingConvention.virtualMachine, tier3.outputs.tokens.service, 'b')
installAccess: installAccess
@ -410,7 +422,7 @@ module imageBuild 'modules/imageBuild.bicep' = {
msrdcwebrtcsvcInstaller: msrdcwebrtcsvcInstaller
officeInstaller: officeInstaller
replicaCount: replicaCount
resourceGroupName: tier3.outputs.namingConvention.resourceGroup
resourceGroupName: tier3.outputs.resourceGroupName
sourceImageType: sourceImageType
storageAccountResourceId: storageAccountResourceId
subnetResourceId: tier3.outputs.subnetResourceId

Просмотреть файл

@ -292,10 +292,12 @@ module defenderForCloud '../../modules/defender-for-cloud.bicep' =
output diskEncryptionSetResourceId string = customerManagedKeys.outputs.diskEncryptionSetResourceId
output keyVaultUri string = customerManagedKeys.outputs.keyVaultUri
output keyVaultName string = customerManagedKeys.outputs.keyVaultName
output locatonProperties object = logic.outputs.locationProperties
output mlzTags object = logic.outputs.mlzTags
output namingConvention object = logic.outputs.tiers[0].namingConvention
output privateDnsZones array = logic.outputs.privateDnsZones
output resourceGroupName string = rg.outputs.name
output resourcePrefix string = azureFirewall.tags.resourcePrefix
output storageEncryptionKeyName string = customerManagedKeys.outputs.storageKeyName
output subnetResourceId string = networking.outputs.subnetResourceId