Deploy a custom Windows VHD in Azure with PowerShell and validate with Pester


Microsoft do not offers this from scratch on Azure VM. So we first need to build our own VHD file. Nothing difficult here, but let’s use the PowerShell way !

Build the image

First of all we need a Virtual Machine started in Hyper-V. I won’t explain how doing this on this post but you should do (at least)  the following things on it:

  • Enable RDP and open ports in Firewall
  • Enable WinRM and open ports in Firewall

With that you’ll be able to administrate your deployed server. Once modifications are done, you can no sysprep the system, using sysprep.exe.

[su_frame align=”center”]sysprep[/su_frame]

Click on the sysprep Window and wait for the VM to sop by itself at the end of the process.

[su_lightbox_content id=”Sysprep_1″ width=”75%” margin=”10″ background=”#82f7f6″]You can find the sysprep.exe in $env:windir\system32\sysprep[/su_lightbox_content]

You now have a vhd or a vhdx file. If you have a vhdx file (like me) the first thing to do, is to convert it to vhd.

Convert-VHD -Path E:\test01.vhdx -DestinationPath E:\Win2K12R2WinRM.vhd

After a little time waiting for the command’s end, you now have a beautiful .vhd file.


Upload the VHD

In order to upload a VHD file to Azure, we need :

  1. Azure PowerShell SDK
  2. A publishsetting file
  3. An affinity group
  4. A Storage account
  5. A container

[su_service title=”Information” icon=”icon: info-circle” icon_color=”#76f9f7″]

We already have our publishsetting file, we saw how to get it on this post 🙂

For the he affinity group creation and the storage account, you can check this post!


So let’s start the creation of the storage.

Import-AzurePublishSettingsFile -PublishSettingsFile azure_ps.publishsettings
New-AzureAffinityGroup -Name PWRSHELL -Description "Test Affinity group" `
                       -Label "Amsterdam Datacenter" -Location "West Europe"
New-AzureStorageAccount -AffinityGroup PWRSHELL -Description "Test storage account" `
                        -Label "Amsterdam Datacenter" `
                        -StorageAccountName amsterdam02
Set-AzureSubscription -SubscriptionName "<abo_name>" `

[su_service title=”Information” icon=”icon: info-circle” icon_color=”#76f9f7″]In this post we saw how to create our container.[/su_service]

$Key = Get-AzureStoragekey -StorageAccountName amsterdam01
$Context = New-AzureStorageContext -StorageAccountName amsterdam01 `
                                   -StorageAccountKey $Key.Primary
New-AzureStorageContainer -Name windowsiso -Context $Context

Now, let’s upload the .vhd into this container.

Add-AzureVhd -Destination "" -LocalFilePath E:\Win2K12R2WinRM.vhd

The cmdlet will first calculate a MD5 Hash to guarantee the vhd file integrity and upload it to the container. It can takes few time 😉

MD5 hash is being calculated for the file  E:\Win2K12R2WinRM.vhd.
MD5 hash calculation is completed.
Elapsed time for the operation: 00:07:07
Creating new page blob of size 136365212160...
Elapsed time for upload: 05:19:09

Once the vhd is uploaded, we now have to add the vhd to Windows Images listing. Once again PowerShell to the rescue (you can do it by GUI, but you’re not on the right blog for that :p)

Add-AzureVMImage -ImageName Win2K12R2WinRM -MediaLocation -OS Windows
ImageName            : Win2K12R2WinRM
OS                   : Windows
MediaLink            :
LogicalSizeInGB      : 127
AffinityGroup        :
Category             : User
Location             :
Label                : Win2K12R2WinRM
Description          :
Eula                 :
ImageFamily          :
PublishedDate        :
IsPremium            : False
IconUri              :
SmallIconUri         :
PrivacyUri           :
RecommendedVMSize    :
PublisherName        :
IOType               : Standard
ShowInGui            :
OperationDescription : Add-AzureVMImage
OperationId          : 2b47b934-3385-7db2-9802-246a9965698e
OperationStatus      : Succeeded

[su_service title=”Pester” icon=”” icon_color=”#76f9f7″]Bonus: Here is a little Pester script to validate that everything is OK for your vhd deployment :)[/su_service]

Describe "Validate your Azure Storage and the VHD uploaded" {
    $Certfile = 'E:\Powershell\azure.publishsettings'
    $StorageName = 'amsterdam02'
    $ContainerName = 'chefisos'
    $Subscription = '<subscription_name>'

    Import-AzurePublishSettingsFile -PublishSettingsFile $Certfile
    Select-AzureSubscription -SubscriptionName $Subscription | Set-AzureSubscription
    Set-AzureSubscription -SubscriptionName $subscription -CurrentStorageAccountName $StorageName
    $Subscription = (Get-AzureSubscription | ? SubscriptionName -eq $Subscription )
    $StorageAccount = Get-AzureStorageAccount -StorageAccountName $StorageName
    $AffinityGroup = Get-AzureAffinityGroup -Name $StorageAccount.AffinityGroup
    $key = Get-AzureStorageKey -StorageAccountName $StorageName
    $Context =New-AzureStorageContext -StorageAccountKey $key.Primary -StorageAccountName $StorageName
    $Blob = Get-AzureStorageBlob -Context $Context -Container $ContainerName
    $ISO = Get-AzureVMImage | ? { $_.ImageName -eq  "Win2K12R2WinRM" }

    It "Publish Settings file exists" {
        $Certfile | Should Exist

    It "Azure Subscription exists" {
        $subscription | Should Not be $null
    It "Current Storage account Affinty Group is PWRSHELL" {
        $StorageAccount.AffinityGroup | Should be "PWRSHELL"

    It "Current Storage account is located on West Europe" {
        $AffinityGroup.Location | Should be "West Europe"

    It "Current Storage account configured to amsterdam02" {
        $subscription.CurrentStorageAccountName | Should be "amsterdam02"

    It "Container exists on blob" {
        $blob.Name  | Should be "Win2K12R2WinRM.vhd"

    It "Blob lenght match local vhd" {
        $blob.Length  | Should be "136365212160"

    It "VHD exists" {
        $ISO.ImageName  | Should be "Win2K12R2WinRM"

    It "VHD is Windows" {
        $ISO.OS  | Should be "Windows"

[su_note note_color=”#ffffff” radius=”6″]If you still don’t know Why and How use Pester Framework, please follow this link![/su_note]

[su_frame align=”center”][/su_frame]

Now you can deploy customized VMs with PowerShell or Chef.