Create a Windows VM with VM Image Builder by using PowerShell

Applies to: ✔️ Windows VMs

This article demonstrates how to create a customized Windows VM image by using the Azure VM Image Builder PowerShell module.

Prerequisites

If you don't have an Azure subscription, create a Trial before you begin.

If you choose to use PowerShell locally, this article requires that you install the Azure PowerShell module and connect to your Azure account by using the Connect-AzAccount cmdlet. For more information, see Install Azure PowerShell.

Some of the steps require cmdlets from the Az.ImageBuilder module. Install separately by using the following command.

Install-Module -Name Az.ImageBuilder

If you have multiple Azure subscriptions, choose the appropriate subscription in which the resources should be billed. Select a specific subscription by using the Set-AzContext cmdlet.

Set-AzContext -SubscriptionId 00000000-0000-0000-0000-000000000000

Register providers

If you haven't already done so, register the following resource providers to use with your Azure subscription:

  • Microsoft.Compute
  • Microsoft.KeyVault
  • Microsoft.Storage
  • Microsoft.Network
  • Microsoft.VirtualMachineImages
  • Microsoft.ManagedIdentity
  • Microsoft.ContainerInstance
Get-AzResourceProvider -ProviderNamespace Microsoft.Compute, Microsoft.KeyVault, Microsoft.Storage, Microsoft.VirtualMachineImages, Microsoft.Network, Microsoft.ManagedIdentity |
  Where-Object RegistrationState -ne Registered |
    Register-AzResourceProvider

Define variables

Because you'll be using some pieces of information repeatedly, create some variables to store that information:

# Destination image resource group name
$imageResourceGroup = 'myWinImgBuilderRG'

# Azure region
$location = 'ChinaNorth3'

# Name of the image to be created
$imageTemplateName = 'myWinImage'

# Distribution properties of the managed image upon completion
$runOutputName = 'myDistResults'

Create a variable for your Azure subscription ID. To confirm that the subscriptionID variable contains your subscription ID, you can run the second line in the following example:

# Your Azure Subscription ID
$subscriptionID = (Get-AzContext).Subscription.Id
Write-Output $subscriptionID

Create a resource group

Create an Azure resource group by using the New-AzResourceGroup cmdlet. A resource group is a logical container in which Azure resources are deployed and managed as a group.

The following example creates a resource group that's based on the name in the $imageResourceGroup variable in the region that you've specified in the $location variable. This resource group is used to store the image configuration template artifact and the image.

New-AzResourceGroup -Name $imageResourceGroup -Location $location

Create a user identity and set role permissions

Grant Azure image builder permissions to create images in the specified resource group by using the following example. Without this permission, the image build process won't finish successfully.

  1. Create variables for the role definition and identity names. These values must be unique.

    [int]$timeInt = $(Get-Date -UFormat '%s')
    $imageRoleDefName = "Azure Image Builder Image Def $timeInt"
    $identityName = "myIdentity$timeInt"
    
  2. Create a user identity.

    New-AzUserAssignedIdentity -ResourceGroupName $imageResourceGroup -Name $identityName -Location $location
    
  3. Store the identity resource and principal IDs in variables.

    $identityNameResourceId = (Get-AzUserAssignedIdentity -ResourceGroupName $imageResourceGroup -Name $identityName).Id
    $identityNamePrincipalId = (Get-AzUserAssignedIdentity -ResourceGroupName $imageResourceGroup -Name $identityName).PrincipalId
    

Assign permissions for the identity to distribute the images

  1. Download the JSON configuration file, and then modify it based on the settings that are defined in this article.

    $myRoleImageCreationUrl = 'https://raw.githubusercontent.com/azure/azvmimagebuilder/master/solutions/12_Creating_AIB_Security_Roles/aibRoleImageCreation.json'
    $myRoleImageCreationPath = "myRoleImageCreation.json"
    
    Invoke-WebRequest -Uri $myRoleImageCreationUrl -OutFile $myRoleImageCreationPath -UseBasicParsing
    
    $Content = Get-Content -Path $myRoleImageCreationPath -Raw
    $Content = $Content -replace '<subscriptionID>', $subscriptionID
    $Content = $Content -replace '<rgName>', $imageResourceGroup
    $Content = $Content -replace 'Azure Image Builder Service Image Creation Role', $imageRoleDefName
    $Content | Out-File -FilePath $myRoleImageCreationPath -Force
    
  2. Create the role definition.

    New-AzRoleDefinition -InputFile $myRoleImageCreationPath
    
  3. Grant the role definition to the VM Image Builder service principal.

    $RoleAssignParams = @{
      ObjectId = $identityNamePrincipalId
      RoleDefinitionName = $imageRoleDefName
      Scope = "/subscriptions/$subscriptionID/resourceGroups/$imageResourceGroup"
    }
    New-AzRoleAssignment @RoleAssignParams
    

Note

If you receive the error "New-AzRoleDefinition: Role definition limit exceeded. No more role definitions can be created," see Troubleshoot Azure RBAC (role-based access control).

  1. Create the gallery.

    $myGalleryName = 'myImageGallery'
    $imageDefName = 'winSvrImages'
    
    New-AzGallery -GalleryName $myGalleryName -ResourceGroupName $imageResourceGroup -Location $location
    
  2. Create a gallery definition.

    $GalleryParams = @{
      GalleryName = $myGalleryName
      ResourceGroupName = $imageResourceGroup
      Location = $location
      Name = $imageDefName
      OsState = 'generalized'
      OsType = 'Windows'
      Publisher = 'myCo'
      Offer = 'Windows'
      Sku = 'Win2019'
    }
    New-AzGalleryImageDefinition @GalleryParams
    

Create an image

  1. Create a VM Image Builder source object. For valid parameter values, see Find Windows VM images in Azure Marketplace with Azure PowerShell.

    $SrcObjParams = @{
      PlatformImageSource = $true
      Publisher = 'MicrosoftWindowsServer'
      Offer = 'WindowsServer'
      Sku = '2019-Datacenter'
      Version = 'latest'
    }
    $srcPlatform = New-AzImageBuilderTemplateSourceObject @SrcObjParams
    
  2. Create a VM Image Builder distributor object.

    $disObjParams = @{
      SharedImageDistributor = $true
      ArtifactTag = @{tag='dis-share'}
      GalleryImageId = "/subscriptions/$subscriptionID/resourceGroups/$imageResourceGroup/providers/Microsoft.Compute/galleries/$myGalleryName/images/$imageDefName"
      ReplicationRegion = $location
      RunOutputName = $runOutputName
      ExcludeFromLatest = $false
    }
    $disSharedImg = New-AzImageBuilderTemplateDistributorObject @disObjParams
    
  3. Create a VM Image Builder customization object.

    $ImgCustomParams01 = @{
      PowerShellCustomizer = $true
      Name = 'settingUpMgmtAgtPath'
      RunElevated = $false
      Inline = @("mkdir c:\\buildActions", "mkdir c:\\buildArtifacts", "echo Azure-Image-Builder-Was-Here  > c:\\buildActions\\buildActionsOutput.txt")
    }
    $Customizer01 = New-AzImageBuilderTemplateCustomizerObject @ImgCustomParams01
    
  4. Create a second VM Image Builder customization object.

    $ImgCustomParams02 = @{
      FileCustomizer = $true
      Name = 'downloadBuildArtifacts'
      Destination = 'c:\\buildArtifacts\\index.html'
      SourceUri = 'https://raw.githubusercontent.com/azure/azvmimagebuilder/master/quickquickstarts/exampleArtifacts/buildArtifacts/index.html'
    }
    $Customizer02 = New-AzImageBuilderTemplateCustomizerObject @ImgCustomParams02
    
  5. Create a VM Image Builder template.

    $ImgTemplateParams = @{
      ImageTemplateName = $imageTemplateName
      ResourceGroupName = $imageResourceGroup
      Source = $srcPlatform
      Distribute = $disSharedImg
      Customize = $Customizer01, $Customizer02
      Location = $location
      UserAssignedIdentityId = $identityNameResourceId
    }
    New-AzImageBuilderTemplate @ImgTemplateParams
    

When the template has been created, a message is returned, and a VM Image Builder configuration template is created in $imageResourceGroup.

To determine whether the template creation process was successful, use the following example:

Get-AzImageBuilderTemplate -ImageTemplateName $imageTemplateName -ResourceGroupName $imageResourceGroup |
  Select-Object -Property Name, LastRunStatusRunState, LastRunStatusMessage, ProvisioningState

In the background, VM Image Builder also creates a staging resource group in your subscription. This resource group is used for the image build. It's in the format IT_<DestinationResourceGroup>_<TemplateName>.

Warning

Don't delete the staging resource group directly. To cause the staging resource group to be deleted, delete the image template artifact.

If the service reports a failure when the image configuration template is submitted, do the following:

  • See Troubleshoot Azure VM Image Builder failures.

  • Before you retry submitting the template, delete it by following this example:

    Remove-AzImageBuilderTemplate -ImageTemplateName $imageTemplateName -ResourceGroupName $imageResourceGroup
    

Start the image build

Submit the image configuration to the VM Image Builder service by running the following command:

Start-AzImageBuilderTemplate -ResourceGroupName $imageResourceGroup -Name $imageTemplateName

Wait for the image building process to finish, which could take up to an hour.

If you encounter errors, review Troubleshoot Azure VM Image Builder failures.

Create a VM

  1. Store the VM login credentials in a variable. The password must be complex.

    $Cred = Get-Credential
    
  2. Create the VM by using the image you created.

    $ArtifactId = (Get-AzImageBuilderTemplateRunOutput -ImageTemplateName $imageTemplateName -ResourceGroupName $imageResourceGroup).ArtifactId
    
    New-AzVM -ResourceGroupName $imageResourceGroup -Image $ArtifactId -Name myWinVM01 -Credential $Cred
    

Verify the customizations

  1. Create a Remote Desktop connection to the VM by using the username and password that you set when you created the VM.

  2. Inside the VM, open PowerShell and run Get-Content, as shown in the following example:

    Get-Content -Path C:\buildActions\buildActionsOutput.txt
    

    The output is based on the contents of the file that you created during the image customization process.

    Azure-Image-Builder-Was-Here
    
  3. From the same PowerShell session, verify that the second customization finished successfully by checking for the presence of c:\buildArtifacts\index.html, as shown in the following example:

    Get-ChildItem c:\buildArtifacts\
    

    The result should be a directory listing showing that the file was downloaded during the image customization process.

        Directory: C:\buildArtifacts
    
    Mode                 LastWriteTime         Length Name
    ----                 -------------         ------ ----
    -a---          29/01/2021    10:04            276 index.html
    

Clean up your resources

If you no longer need the resources that were created during this process, you can delete them by doing the following:

  1. Delete the VM Image Builder template.

    Remove-AzImageBuilderTemplate -ResourceGroupName $imageResourceGroup -Name $imageTemplateName
    
  2. Delete the image resource group.

    Caution

    The following example deletes the specified resource group and all the resources that it contains. If any resources outside the scope of this article exist in the resource group, they'll also be deleted.

    Remove-AzResourceGroup -Name $imageResourceGroup
    

Next steps

To learn more about the components of the JSON file that this article uses, see the VM Image Builder template reference.