Use PowerShell and Pester for Operational Readiness Testing of Altaro VM Backup

I've recently been working with Altaro VM Backup and I must say that I've been very impressed with the ease and simplicity of the product. The back-end portion of the product can run on a virtual or physical server with or without the GUI (Server Core is supported). It can backup to just about any type of drive (local disk, UNC path, USB drive, etc). It doesn't require SQL Server. In my environment, adding a Hyper-V server (running Windows Server 2012 R2) installed a service on the Hypervisor, but did not require a reboot. There's no agent to install on the VM's themselves. Recovering an entire VM or specific files within the VM is simple enough. There's a mode where automated scheduled restores can be performed to validate the end-to-end backup and restore process. Even their licensing model is straightforward. This is how backups and restores are suppose to work .

One very important thing to keep in mind is that Altaro VM Backup is designed to backup VM's which means that all of the data that you intend on backing up needs to reside in a virtual file (VHD or VHDX for Hyper-V). If some of your VM's have pass-through drives or you've passed through iSCSI network cards and made direct connections to iSCSI targets from VM's, those drives won't been seen or backed up by Altaro VM Backup.

There are a number of reasons why a backup taken with Altaro VM Backup may be crash consistent instead of application consistent so I decided to write an operational readiness test using PowerShell and Pester as shown in the following code example to validate all the items listed in one of their support articles.

  1#Requires -Version 3.0 -Modules Pester
  2function Test-MrVMBackupRequirement {
  3
  4<#
  5.SYNOPSIS
  6    Tests the requirements for live backups of a Hyper-V Guest VM for use with Altaro VM Backup.
  7
  8.DESCRIPTION
  9    Test the requirements for live backups of a Hyper-V Guest VM as defined in this Altaro support article:
 10    http://support.altaro.com/customer/portal/articles/808575-what-are-the-requirements-for-live-backups-of-a-hyper-v-guest-vm-.
 11
 12.PARAMETER ComputerName
 13    Name of the Hyper-V host virtualization server that the specified VM's are running on.
 14
 15.PARAMETER VMHost
 16    Name of the VM (Guest) server to test the requirements for.
 17
 18.PARAMETER Credential
 19    Specifies a user account that has permission to perform this action. The default is the current user.
 20
 21.EXAMPLE
 22     Test-MrVMBackupRequirement -ComputerName HyperVServer01 -VMName VM01, VCM02 -Credential (Get-Credential)
 23
 24.INPUTS
 25    String
 26
 27.OUTPUTS
 28    None
 29
 30.NOTES
 31    Author:  Mike F Robbins
 32    Website: http://mikefrobbins.com
 33    Twitter: @mikefrobbins
 34#>
 35
 36    [CmdletBinding()]
 37    param (
 38        [Parameter(Mandatory)]
 39        [Alias('VMHost')]
 40        [string]$ComputerName,
 41
 42        [Parameter(ValueFromPipeline)]
 43        [string[]]$VMName,
 44
 45        [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty
 46    )
 47
 48    BEGIN {
 49        try {
 50            $HostSession = New-PSSession -ComputerName $ComputerName -Credential $Credential -ErrorAction Stop
 51        }
 52        catch {
 53            Throw "Unable to connect to Hyper-V host '$ComputerName'. Aborting Pester tests."
 54        }
 55
 56        $VMs = (Invoke-Command -Session $HostSession {
 57            Get-VM | Select-Object -Property Name
 58        }).Name
 59
 60        if (-not($PSBoundParameters.VMName)) {
 61            $VMName = $VMs
 62        }
 63
 64    }
 65
 66    PROCESS {
 67        foreach ($VM in $VMName) {
 68            Describe "Validation of Altaro VM Backup Requirements for Live Backups of Hyper-V Guest VM: '$VM'" {
 69
 70                if ($VM -notin $VMs) {
 71                    Write-Warning -Message "The VM: '$VM' does not exist on the Hyper-V host: '$ComputerName'"
 72                    Continue
 73                }
 74
 75                try {
 76                    $GuestSession = New-PSSession -ComputerName $VM -Credential $Credential -ErrorAction Stop
 77                }
 78                catch {
 79                    Write-Warning -Message "Unable to connect. Aborting Pester tests for computer: '$VM'."
 80                    Continue
 81                }
 82
 83                $SupportedGuestOS = '2008 R2', 'Server 2012', 'Server 2012 R2'
 84
 85                It "Should be running one of the supported guest OS's ($($SupportedGuestOS -join ', '))" {
 86                    $OS = (Invoke-Command -Session $GuestSession {
 87                        Get-WmiObject -Class Win32_OperatingSystem -Property Caption
 88                    }).caption
 89
 90                    ($SupportedGuestOS | ForEach-Object {$OS -like "*$_*"}) -contains $true |
 91                    Should Be $true
 92                }
 93
 94                $VMInfo = Invoke-Command -Session $HostSession {
 95                    Get-VM -Name $Using:VM | Select-Object -Property IntegrationServicesState, State
 96                }
 97
 98                It 'Should have the latest Integration Services version installed' {
 99                    $VMInfo.IntegrationServicesState -eq 'Up to date' |
100                    Should Be $true
101                }
102
103                It 'Should have Backup (volume snapshot) enabled in the Hyper-V settings' {
104                    (Invoke-Command -Session $HostSession {
105                        Get-VM -Name $Using:VM | Get-VMIntegrationService -Name VSS | Select-Object -Property Enabled
106                    }).enabled |
107                    Should Be $true
108                }
109
110                It 'Should be running' {
111                    $VMInfo.State.Value |
112                    Should Be 'Running'
113                }
114
115                $GuestDiskInfo = Invoke-Command -Session $GuestSession {
116                    Get-WMIObject -Class Win32_Volume -Filter 'DriveType = 3' -Property Capacity, FileSystem, FreeSpace, Label
117                }
118
119                It 'Should have at least 10% free disk space on all disks' {
120                    $GuestDiskInfo | ForEach-Object {$_.FreeSpace / $_.Capacity * 100} |
121                    Should BeGreaterThan 10
122                }
123
124                $GuestServiceInfo = Invoke-Command -Session $GuestSession {
125                    Get-Service -DisplayName 'Hyper-V Volume Shadow Copy Requestor', 'Volume Shadow Copy', 'COM+ Event System',
126                                             'Distributed Transaction Coordinator', 'Remote Procedure Call (RPC)', 'System Event Notification Service'
127                }
128
129                It 'Should be running the "Hyper-V Volume Shadow Copy Requestor" service on the guest' {
130                    ($GuestServiceInfo |
131                     Where-Object DisplayName -eq 'Hyper-V Volume Shadow Copy Requestor'
132                    ).status |
133                    Should Be 'Running'
134                }
135
136                It 'Should have snapshot file location for VM set to same location as VM VHD file' {
137                    #Hyper-V on Windows Server 2008 R2 and higher: The .AVHD file is always created in the same location as its parent virtual hard disk.
138                    $HostOS = (Invoke-Command -Session $HostSession {
139                        Get-WmiObject -Class Win32_OperatingSystem -Property Version
140                    }).version
141
142                    [Version]$HostOS -gt [Version]'6.1.7600' |
143                    Should Be $true
144                }
145
146                It 'Should be running VSS in the guest OS' {
147                    ($GuestServiceInfo |
148                     Where-Object Name -eq VSS
149                    ).status |
150                    Should Be 'Running'
151                }
152
153                It 'Should have a SCSI controller attached in the VM settings' {
154                    Invoke-Command -Session $HostSession {
155                        Get-VM -Name $Using:VM | Get-VMScsiController
156                    } |
157                    Should Be $true
158                }
159
160                It 'Should not have an explicit shadow storage assignment of a volume other than itself' {
161                    Invoke-Command -Session $GuestSession {
162                        $Results = vssadmin.exe list shadowstorage | Select-String -SimpleMatch 'For Volume', 'Shadow Copy Storage volume'
163                        if ($Results) {
164                            for ($i = 0; $i -lt $Results.Count; $i+=2){
165                                ($Results[$i] -split 'volume:')[1].trim() -eq ($Results[$i+1] -split 'volume:')[1].trim()
166                            }
167                        }
168                        else {
169                            $true
170                        }
171                    } |
172                    Should Be $true
173                }
174
175                It 'Should not have any App-V drives installed on the VM' {
176                    #App-V drives installed on the VM creates a non-NTFS volume.
177                    $GuestDiskInfo.filesystem |
178                    Should Be 'NTFS'
179                }
180
181                It 'Should have at least 45MB of free space on system reserved partition if one exists in the guest OS' {
182                    ($GuestDiskInfo |
183                    Where-Object Label -eq 'System Reserved').freespace / 1MB |
184                    Should BeGreaterThan 45
185                }
186
187                It 'Should have all volumes formated with NTFS in the guest OS' {
188                    $GuestDiskInfo.filesystem |
189                    Should Be 'NTFS'
190                }
191
192                It 'Should have volume containing VHD files formated with NTFS' {
193                    $HostDiskLetter = (Invoke-Command -Session $HostSession {
194                        Get-VM -Name $Using:VM | Get-VMHardDiskDrive
195                    }).path -replace '\\.*$'
196
197                    $HostDiskInfo = Invoke-Command -Session $HostSession {
198                        Get-WMIObject -Class Win32_Volume -Filter 'DriveType = 3' -Property DriveLetter, FileSystem
199                    }
200
201                    ($HostDiskLetter | ForEach-Object {$HostDiskInfo | Where-Object DriveLetter -eq $_}).filesystem |
202                    Should Be 'NTFS'
203                }
204
205                It 'Should only contain basic and not dynamic disks in the guest OS' {
206                    Invoke-Command -Session $GuestSession {
207                        $DynamicDisk = 'Logical Disk Manager', 'GPT: Logical Disk Manager Data'
208                        Get-WmiObject -Class Win32_DiskPartition -Property Type |
209                        ForEach-Object {$DynamicDisk -contains $_.Type}
210                    } |
211                    Should Be $false
212                }
213
214                It 'Should be running specific services within the VM' {
215                    $RunningServices = 'COM+ Event System', 'Distributed Transaction Coordinator', 'Remote Procedure Call (RPC)', 'System Event Notification Service'
216                    ($GuestServiceInfo | Where-Object DisplayName -in $RunningServices).status |
217                    Should Be 'Running'
218                }
219
220                It 'Should have specific services set to manual or automatic within the VM' {
221                    $StartMode = (Invoke-Command -Session $GuestSession {
222                            Get-WmiObject -Class Win32_Service -Filter "DisplayName = 'COM+ System Application' or DisplayName = 'Microsoft Software Shadow Copy Provider' or DisplayName = 'Volume Shadow Copy'"
223                    }).StartMode
224
225                    $StartMode | ForEach-Object {$_ -eq 'Manual' -or $_ -eq 'Automatic'} |
226                    Should Be $true
227
228                }
229
230                Remove-PSSession -Session $GuestSession
231
232            }
233
234        }
235
236    }
237
238    END {
239        Remove-PSSession -Session $HostSession
240    }
241
242}

To run the operational validation test, point the ComputerName parameter to a Hyper-V host virtualization server and it will automatically determine all of the VM's on the host and run the test on each of the VM's. You can specify specific VM's to test via the VMName parameter. Use the credential parameter to specify a userid and password with admin privileges on the Hyper-V host and VM's if you're running PowerShell as a user who doesn't have sufficient access.

You could use the PowerShell console or ISE (Integrated Scripting Environment) to run the test from, but the syntax highlighting and progress indicator of Visual Studio Code look amazing:

vmbackup-pester1a.png

See my previous articles on how to setup Visual Studio Code for use with PowerShell if that's something you're interested in.

The Test-MrVMBackupRequirement function shown in this blog article can be downloaded from my PowerShell repository on GitHub. Found a bug or a better way of accomplishing a task shown in the example code? Feel free to contribute by forking the repository and submitting a pull request.

µ