Test-MrVMBackupRequirement.ps1

#Requires -Version 3.0 -Modules Pester
function Test-MrVMBackupRequirement {

<#
.SYNOPSIS
    Tests the requirements for live backups of a Hyper-V Guest VM for use with Altaro VM Backup.
  
.DESCRIPTION
    Test the requirements for live backups of a Hyper-V Guest VM as defined in this Altaro support article:
    http://support.altaro.com/customer/portal/articles/808575-what-are-the-requirements-for-live-backups-of-a-hyper-v-guest-vm-.
  
.PARAMETER ComputerName
    Name of the Hyper-V host virtualization server that the specified VM's are running on.
 
.PARAMETER VMHost
    Name of the VM (Guest) server to test the requirements for.
 
.PARAMETER Credential
    Specifies a user account that has permission to perform this action. The default is the current user.
  
.EXAMPLE
     Test-MrVMBackupRequirement -ComputerName HyperVServer01 -VMName VM01, VCM02 -Credential (Get-Credential)
 
.INPUTS
    String
  
.OUTPUTS
    None
  
.NOTES
    Author: Mike F Robbins
    Website: http://mikefrobbins.com
    Twitter: @mikefrobbins
#>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [Alias('VMHost')]
        [string]$ComputerName,

        [Parameter(ValueFromPipeline)]
        [string[]]$VMName,

        [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty
    )

    BEGIN {
        try {
            $HostSession = New-PSSession -ComputerName $ComputerName -Credential $Credential -ErrorAction Stop
        }
        catch {
            Throw "Unable to connect to Hyper-V host '$ComputerName'. Aborting Pester tests."
        }

        $VMs = (Invoke-Command -Session $HostSession {
            Get-VM | Select-Object -Property Name
        }).Name
        
        if (-not($PSBoundParameters.VMName)) {
            $VMName = $VMs
        }

    }
    
    PROCESS {
        foreach ($VM in $VMName) {
            Describe "Validation of Altaro VM Backup Requirements for Live Backups of Hyper-V Guest VM: '$VM'" {

                if ($VM -notin $VMs) {
                    Write-Warning -Message "The VM: '$VM' does not exist on the Hyper-V host: '$ComputerName'"
                    Continue
                }

                try {
                    $GuestSession = New-PSSession -ComputerName $VM -Credential $Credential -ErrorAction Stop
                }
                catch {
                    Write-Warning -Message "Unable to connect. Aborting Pester tests for computer: '$VM'."
                    Continue
                }
        
                $SupportedGuestOS = '2008 R2', 'Server 2012', 'Server 2012 R2'

                It "Should be running one of the supported guest OS's ($($SupportedGuestOS -join ', '))" {
                    $OS = (Invoke-Command -Session $GuestSession {
                        Get-WmiObject -Class Win32_OperatingSystem -Property Caption
                    }).caption

                    ($SupportedGuestOS | ForEach-Object {$OS -like "*$_*"}) -contains $true |
                    Should Be $true
                }
                
                $VMInfo = Invoke-Command -Session $HostSession {
                    Get-VM -Name $Using:VM | Select-Object -Property IntegrationServicesState, State
                }

                It 'Should have the latest Integration Services version installed' {
                    $VMInfo.IntegrationServicesState -eq 'Up to date' |
                    Should Be $true
                }

                It 'Should have Backup (volume snapshot) enabled in the Hyper-V settings' {
                    (Invoke-Command -Session $HostSession {
                        Get-VM -Name $Using:VM | Get-VMIntegrationService -Name VSS | Select-Object -Property Enabled
                    }).enabled |
                    Should Be $true
                }

                It 'Should be running' {
                    $VMInfo.State.Value |
                    Should Be 'Running'
                }

                $GuestDiskInfo = Invoke-Command -Session $GuestSession {
                    Get-WMIObject -Class Win32_Volume -Filter 'DriveType = 3' -Property Capacity, FileSystem, FreeSpace, Label
                }

                It 'Should have at least 10% free disk space on all disks' {
                    $GuestDiskInfo | ForEach-Object {$_.FreeSpace / $_.Capacity * 100} |
                    Should BeGreaterThan 10
                }
        
                $GuestServiceInfo = Invoke-Command -Session $GuestSession {
                    Get-Service -DisplayName 'Hyper-V Volume Shadow Copy Requestor', 'Volume Shadow Copy', 'COM+ Event System',
                                             'Distributed Transaction Coordinator', 'Remote Procedure Call (RPC)', 'System Event Notification Service'
                }

                It 'Should be running the "Hyper-V Volume Shadow Copy Requestor" service on the guest' {
                    ($GuestServiceInfo |
                     Where-Object DisplayName -eq 'Hyper-V Volume Shadow Copy Requestor'
                    ).status |
                    Should Be 'Running'
                }
        
                It 'Should have snapshot file location for VM set to same location as VM VHD file' {
                    #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.
                    $HostOS = (Invoke-Command -Session $HostSession {
                        Get-WmiObject -Class Win32_OperatingSystem -Property Version
                    }).version
            
                    [Version]$HostOS -gt [Version]'6.1.7600' |
                    Should Be $true
                }

                It 'Should be running VSS in the guest OS' {
                    ($GuestServiceInfo |
                     Where-Object Name -eq VSS
                    ).status |
                    Should Be 'Running'
                }
        
                It 'Should have a SCSI controller attached in the VM settings' {
                    Invoke-Command -Session $HostSession {
                        if (Get-VM -Name $Using:VM | Get-VMScsiController) {
                            $true
                        }
                    } |
                    Should Be $true
                }
        
                It 'Should not have an explicit shadow storage assignment of a volume other than itself' {
                    Invoke-Command -Session $GuestSession {
                        $Results = vssadmin.exe list shadowstorage | Select-String -SimpleMatch 'For Volume', 'Shadow Copy Storage volume'
                        if ($Results) {
                            for ($i = 0; $i -lt $Results.Count; $i+=2){
                                ($Results[$i] -split 'volume:')[1].trim() -eq ($Results[$i+1] -split 'volume:')[1].trim() 
                            }                                                   
                        }
                        else {
                            $true
                        }                 
                    } |
                    Should Be $true                    
                }

                It 'Should not have any App-V drives installed on the VM' {
                    #App-V drives installed on the VM creates a non-NTFS volume.
                    $GuestDiskInfo.filesystem | ForEach-Object {
                        $_ | Should Be 'NTFS'
                    }
                    
                }

                It 'Should have at least 45MB of free space on system reserved partition if one exists in the guest OS' {
                    ($GuestDiskInfo |
                    Where-Object Label -eq 'System Reserved').freespace / 1MB |
                    Should BeGreaterThan 45 
                }
        
                It 'Should have all volumes formated with NTFS in the guest OS' {
                    $GuestDiskInfo.filesystem | ForEach-Object {
                        $_ | Should Be 'NTFS'
                    }
                }        

                It 'Should have volume containing VHD files formated with NTFS' {
                    $HostDiskLetter = (Invoke-Command -Session $HostSession {
                        Get-VM -Name $Using:VM | Get-VMHardDiskDrive
                    }).path -replace '\\.*$'

                    $HostDiskInfo = Invoke-Command -Session $HostSession {
                        Get-WMIObject -Class Win32_Volume -Filter 'DriveType = 3' -Property DriveLetter, FileSystem            
                    }

                    ($HostDiskLetter | ForEach-Object {$HostDiskInfo | Where-Object DriveLetter -eq $_}).filesystem |
                    Should Be 'NTFS'
                }

                It 'Should only contain basic and not dynamic disks in the guest OS' {
                    Invoke-Command -Session $GuestSession {
                        $DynamicDisk = 'Logical Disk Manager', 'GPT: Logical Disk Manager Data' 
                        Get-WmiObject -Class Win32_DiskPartition -Property Type |
                        ForEach-Object {$DynamicDisk -contains $_.Type}
                    } |
                    ForEach-Object {
                        $_ | Should Be $false
                    }
                }

                It 'Should be running specific services within the VM' {
                    $RunningServices = 'COM+ Event System', 'Distributed Transaction Coordinator', 'Remote Procedure Call (RPC)', 'System Event Notification Service'
                    ($GuestServiceInfo | Where-Object DisplayName -in $RunningServices).status | ForEach-Object {
                        $_ | Should Be 'Running'
                    }
                }

                It 'Should have specific services set to manual or automatic within the VM' {
                    $StartMode = (Invoke-Command -Session $GuestSession {
                            Get-WmiObject -Class Win32_Service -Filter "DisplayName = 'COM+ System Application' or DisplayName = 'Microsoft Software Shadow Copy Provider' or DisplayName = 'Volume Shadow Copy'"
                    }).StartMode
                    
                    $StartMode | ForEach-Object {$_ -eq 'Manual' -or $_ -eq 'Automatic'} |
                    ForEach-Object {
                        $_ | Should Be $true
                    }
                    
                }

                Remove-PSSession -Session $GuestSession
    
            }
        
        }
            
    }

    END {
        Remove-PSSession -Session $HostSession
    }

}