• Improve this Doc

    Show / Hide Table of Contents

    How to create a custom image from a VM on Azure Stack Hub using PowerShell

    Overview

    You can create an image resource from a generalised virtual machine (VM) that is stored as either a managed disk or an unmanaged disk in a storage account. You can then use the image to create multiple VMs.

    This article explains how to create a custom image from a VM on Azure Stack Hub, which you can then use to deploy other VMs.

    Prerequisites

    To complete the steps in this article, you must have appropriate access to a subscription in the Azure Stack Hub portal.

    Before you begin, ensure your PowerShell environment is set up as detailed in Configure the Azure Stack Hub user's PowerShell environment.

    High-level Process Overview

    1. Generalise the virtual machine.

    2. Create a new resource group.

      Important

      The image must be created in the same resource group that you are planning to create the new virtual machine in.

      In other words, we are creating a new resource group to contain the image and the new virtual machine.

    3. Create a custom image from the virtual machine, ensuring that it is created in the new resource group.

    4. Delete the virtual machine.

    5. Deploy a second virtual machine from the image, which can only be created in the new resource group.

    6. Delete the resource group which contained the original virtual machine.

      Warning

      The last step is only applicable if there are no other resources, such as other virtual machines, in the original resource group.

    Generalise your VM

    Warning

    Once you've generalised a VM, you cannot log back into it.

    • Windows
    • Linux
    1. Log in to your Windows VM using Remote Desktop Protocol (RDP).

    2. Open a PowerShell console or command prompt as administrator and run the following command: C:\Windows\System32\Sysprep\sysprep.exe

    3. In the System Preparation Tool, from the System Cleanup Action list, select Enter System Out-of-Box Experience (OOBE).

    4. Ensure the Generalise check box is selected.

    5. From the Shutdown Options list, select Shutdown.

      See the image below for an example:

      Windows sysprep example

    6. Click OK and wait for the VM to shutdown. Your RDP session will be closed.

      Tip

      The generalisation process is complete once your VM is in a stopped state.

    1. Log in to your Linux VM using Secure Shell (SSH).

    2. Run the following command: sudo su and enter your user password if prompted.

    3. Run the following command: waagent -deprovision+user -force

    4. Wait a minute for the generalisation process to complete before continuing.

    Creating the image

    Warning

    Capturing a VM image will make the VM unusable and cannot be undone.

    Declare variables

    Enter details below to provide values for the variables in the scripts in this article:

    Variable name Variable description Input
    $ArmEndpoint Azure Resource Manager endpoint for Azure Stack Hub
    $RGName Name of the resource group
    $NewRGName Name of the resource group for the virtual machine to be created within
    $VMName Name of the virtual machine to be created
    $ImageName Name of the new custom image to be created
    $NewVMName Name of the new virtual machine to be created
    $Size Size of the new virtual machine to be created
    $VNetName Name of the virtual network to be created
    $SubnetName Name of the subnet
    $NSGName Name of the network security group to be created
    $PublicIPName Name of the public IP to be created
    $Username Username of the VM to be created
    $Password Password of the VM to be created
    • Managed Disk
    • Unmanaged Disk

    Run the following PowerShell code:

    # Initialise environment and variables
    
    # Declare endpoint
    $ArmEndpoint = "https://management.frn00006.azure.ukcloud.com"
    
    # Add environment
    Add-AzEnvironment -Name "AzureStackUser" -ArmEndpoint $ArmEndpoint
    
    # Login
    Connect-AzAccount -EnvironmentName "AzureStackUser"
    
    # Get location of Azure Stack Hub
    $Location = (Get-AzLocation).Location
    
    # Declare variables
    $VMName = "MyVM"
    $RGName = "MyResourceGroup"
    $ImageName = "MyCustomImage"
    
    # Declare variables to create a new VM from the image
    $NewVMName = "MyNewVMFromImage"
    $NewRGName = "MyNewResourceGroup"
    $Size = "Standard_DS2_v2"
    $VNetName = "MyVNetwork"
    $SubnetName = "MySubnet"
    $NSGName = "MyNSG"
    $PublicIPName = "MyPublicIP"
    $Username = "MyUser"
    $Password = "Password123!" | ConvertTo-SecureString -Force -AsPlainText
    $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username, $Password
    
    # Get VM details
    $VM = Get-AzVM -Name $VMName -ResourceGroupName $RGName
    
    if ($VM) {
        # Stop the VM
        Write-Output -InputObject "Stopping VM and marking as generalised..."
        Stop-AzVM -Name $VMName -ResourceGroupName $RGName -Force
    
        # Mark VM as Generalised
        Set-AzVM -Name $VMName -ResourceGroupName $RGName -Generalized
    }
    else {
        Write-Error -Message "VM with name: $VMName does not exist in resource group: $RGName."
        break
    }
    
    # Create new resource group
    New-AzResourceGroup -Name $NewRGName -Location $Location
    
    # Create VM image
    Write-Output -InputObject "Creating image of VM: $VMName."
    $ImageConfig = New-AzImageConfig -Location $Location -SourceVirtualMachineId $VM.Id
    $Image = New-AzImage -ResourceGroupName $NewRGName -ImageName $ImageName -Image $ImageConfig
    
    # Delete the VM
    Remove-AzVM -ResourceGroupName $RGName -Name $VMName -Force
    
    # Get image to check OS type
    $Image = Get-AzImage | Where-Object -FilterScript { $_.Name -like $ImageName }
    
    # Depending on the OS type, open either an RDP or SSH port and provision correct size
    # WARNING - These ports will be exposed to the internet. Edit the rules after creation to limit inbound traffic to known IP addresses
    if ($Image.StorageProfile.OsDisk.OsType -like "Windows") {
        $OpenPorts = 3389
        if (-not $Size) {
            $Size = "Standard_DS2_v2"
        }
    }
    else {
        $OpenPorts = 22
        if (-not $Size) {
            $Size = "Standard_DS1_v2"
        }
    }
    
    # Create new VM from custom image
    Write-Output -InputObject "Creating VM from image: $ImageName... (This may take a while)"
    New-AzVM -ResourceGroupName $NewRGName -Location $Location -Name $NewVMName -ImageName $ImageName -Credential $Credential -VirtualNetworkName $VNetName -SubnetName $SubnetName -PublicIpAddressName $PublicIPName -SecurityGroupName $NsgName -OpenPorts $OpenPorts -Size $Size
    
    # Remove source resource group
    Remove-AzResourceGroup -ResourceGroupName $RGName -Location $Location -Confirm
    

    Run the following PowerShell code:

    # Initialise environment and variables
    
    # Declare endpoint
    $ArmEndpoint = "https://management.frn00006.azure.ukcloud.com"
    
    # Add environment
    Add-AzEnvironment -Name "AzureStackUser" -ArmEndpoint $ArmEndpoint
    
    # Login
    Connect-AzAccount -EnvironmentName "AzureStackUser"
    
    # Get location of Azure Stack Hub
    $Location = (Get-AzLocation).Location
    
    # Declare variables
    $VMName = "MyVM"
    $RGName = "MyResourceGroup"
    $ImageName = "MyCustomImage"
    
    # Declare variables to create a new VM from the image
    $NewVMName = "MyNewVMFromImage"
    $NewRGName = "MyNewResourceGroup"
    $Size = "Standard_DS2_v2"
    $VNetName = "MyVNetwork"
    $SubnetName = "MySubnet"
    $NSGName = "MyNSG"
    $PublicIPName = "MyPublicIP"
    $Username = "MyUser"
    $Password = "Password123!" | ConvertTo-SecureString -Force -AsPlainText
    $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username, $Password
    
    # Get VM details
    $VM = Get-AzVM -Name $VMName -ResourceGroupName $RGName
    
    if ($VM) {
        # Stop the VM
        Write-Output -InputObject "Stopping VM and marking as generalised..."
        Stop-AzVM -Name $VMName -ResourceGroupName $RGName -Force
    
        # Mark VM as Generalised
        Set-AzVM -Name $VMName -ResourceGroupName $RGName -Generalized
    }
    else {
        Write-Error -Message "VM with name: $VMName does not exist in resource group: $RGName."
        break
    }
    
    # Get the VM VHD URI
    $VHDUri = $VM.StorageProfile.OsDisk.Vhd.Uri
    if (-not $VHDUri) {
        Write-Error -Message "Failed to retrieve VHD URI."
        break
    }
    
    # Create new resource group
    New-AzResourceGroup -Name $NewRGName -Location $Location
    
    # Create VM image
    Write-Output -InputObject "Creating image of VM: $VMName."
    $ImageConfig = New-AzImageConfig -Location $Location
    $ImageConfig = Set-AzImageOsDisk -Image $ImageConfig -OsType $VM.StorageProfile.OsDisk.OsType -OsState "Generalized" -BlobUri $VHDUri
    $Image = New-AzImage -ResourceGroupName $NewRGName -Image $ImageConfig -ImageName $ImageName
    
    # Delete the VM
    Remove-AzVM -ResourceGroupName $RGName -Name $VMName -Force
    
    # Get image to check OS type
    $Image = Get-AzImage | Where-Object -FilterScript { $_.Name -like $ImageName }
    
    # Depending on the OS type, open either an RDP or SSH port and provision correct size
    # WARNING - These ports will be exposed to the internet. Edit the rules after creation to limit inbound traffic to known IP addresses
    if ($Image.StorageProfile.OsDisk.OsType -like "Windows") {
        $OpenPorts = 3389
        if (-not $Size) {
            $Size = "Standard_DS2_v2"
        }
    }
    else {
        $OpenPorts = 22
        if (-not $Size) {
            $Size = "Standard_DS1_v2"
        }
    }
    
    # Create new VM from custom image
    Write-Output -InputObject "Creating VM from image: $ImageName... (This may take a while)"
    New-AzVM -ResourceGroupName $NewRGName -Location $Location -Name $NewVMName -ImageName $ImageName -Credential $Credential -VirtualNetworkName $VNetName -SubnetName $SubnetName -PublicIpAddressName $PublicIPName -SecurityGroupName $NsgName -OpenPorts $OpenPorts -Size $Size
    
    # Remove source resource group
    Remove-AzResourceGroup -ResourceGroupName $RGName -Location $Location -Confirm
    

    Feedback

    If you find a problem with this article, click Improve this Doc to make the change yourself or raise an issue in GitHub. If you have an idea for how we could improve any of our services, send an email to feedback@ukcloud.com.

    ☀
    ☾
    Generated by DocFX
    Back to top
    © UKCloud Ltd, 2022. All Rights Reserved.
    Privacy Policy. Terms of Use. Contribute.

    The UKCloud Knowledge Centre uses cookies to ensure that we give you the best experience on our website. If you continue we assume that you consent to receive all cookies on this website.