Optimize-VMDisk.psm1

function Optimize-VMDisk
{
    <#
        .SYNOPSIS
            This is a cmdlet that will mount, optimize and dismount a VM's disks.
 
        .DESCRIPTION
            This cmdlet verifies that a VM is turned off, disks are unmounted and that it has no checkpoints.
            If the VM meets all of these requirements it mounts the VM's disks read only and then performs Optimize-VHD on the VM's disks and then dismounts the disk.
 
        .PARAMETER Name
            This is the name of the VM that you want optimized. Accepts pipeline input from Get-VM.
 
        .PARAMETER Shutdown
            If used, this will perform Stop-VM -Force on all of the VMs selected to be optimized. The VM's will be started again once the script completes.
     
        .INPUTS
            Accepts pipeline input from Get-VM.
 
        .OUTPUTS
            Outputs a PSObject with the results of the optimization process.
         
        .EXAMPLE
            Optimize-VMDisk -Name ET-DC-02 -Shutdown
    #>
    
    [CmdletBinding()]
    Param(
            [parameter(ValueFromPipelineByPropertyName,Mandatory=$True)]
            [ValidateNotNullorEmpty()]
            [string[]]
            $Name,
            
            [parameter(Mandatory=$False)]
            [switch]
            $Shutdown
    )
    
    Begin
    {
    
    # Checks to see if it is being run in an administrative prompt. Breaks the script if not.
    if ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match 'S-1-5-32-544') -eq $False )
    {  
        Write-Error 'This script must be run with administrator privledges. Relaunch script in an administrative prompt.'
        break
    }

    # Variable Setup
    $result = [System.Collections.ArrayList]@()
    }   
    
    Process
    {

        foreach ($vmname in $Name)
        {

            # Collect the VMs we are working with.
            try
            {
                Write-Verbose "Gathering information for $($vmname)."
                $VM = Get-VM -Name $vmname
                Write-Verbose "Found information for $($VM.Name)."
            }
            catch
            {
                Write-Verbose "Get-VM failed for $($VM.Name)."
                Write-Host $_.Exception.Message -ForegroundColor Red             
            }
            
            # Shutsdown the VM before attempting the optimization process.
            if ($Shutdown)
            {               
                $VMStateBeforeShutdown = $VM.State
                if ($VMStateBeforeShutdown -eq 'Running')
                {
                    try
                    {
                        Write-Verbose "Attempting to shutdown $($VM.Name)."
                        Stop-VM -Name $VM.Name -Force -Verbose
                    }
                    catch
                    {
                        Write-Verbose "Shutdown unsuccessful $($VM.Name)."
                        Write-Host $_.Exception.Message -ForegroundColor Red 
                    }                
                }               
            }
                        
            # Build variable of the disks connected to this VM.
            Write-Verbose "Gathering VM disk info from $($VM.Name)."
            $VMDisks = Get-VMHardDiskDrive -VMName $VM.Name
            
            # Validate that VM is not running, has no checkpoints and isn't mounted. If all pass then mount,optimize and dismount the disk.
            Write-Verbose "Verifying that $($VM.Name) is turned off."
            if ($VM.State -ne "Off" )
            {
                Write-Warning "$($VM.Name) is not turned off. Turn $($VM.Name) off and try again."
                continue
            }
            else
            {
                Write-Verbose "$($VM.Name) is turned off."            
                $result += foreach ($disk in $VMDisks)
                {
                    $Path = $disk.Path
                    $VHD = Get-VHD -Path $Path
                    $AttachCheck = $VHD.Attached
                    Write-Verbose "Verifying virtual disk is ready."
                    Write-Verbose "Verifying that $($Path) isn't mounted."
                    if ($AttachCheck -eq $False)
                    {
                        Write-Verbose "$($Path) is not mounted."
                        if ($Path.Split(".")[1] -eq 'avhdx')
                        {
                            Write-Warning "$($_.VMName) has a checkpoint. Merge the checkpoint and try again."
                            continue
                        }
                        else
                        {                         
                            Write-Verbose "Virtual disk state verified."
                            
                            # Mount the VHD
                            try
                            { 
                                Write-Verbose "Mounting $Path as read only."
                                Mount-VHD -Path $Path -ReadOnly
                            }
                            catch
                            {
                                Write-Host "Couldn't mount $($Path)" -ForegroundColor Red
                                Write-Host $_.Exception.Message -ForegroundColor Red
                            }
                            
                            # Optimize the VHD
                            try
                            {
                                $VHDSizePre = [math]::Round($VHD.FileSize /1GB)
                                Write-Verbose "Optimizing $Path." 
                                Optimize-VHD -Path $Path
                                $VHDSizePost = [math]::Round( (Get-VHD -Path $Path).FileSize /1GB )
                                $VHDSavings = $VHDSizePre - $VHDSizePost
                                Write-Verbose "Saved $VHDSavings GB."   
                            }
                            catch
                            {
                                Write-Host "Couldn't optimize $($Path)" -ForegroundColor Red
                                Write-Host $_.Exception.Message -ForegroundColor Red                        
                            }
                            
                            # Dismount the VHD
                            try
                            {
                                Write-Verbose "Dismounting $Path."
                                Dismount-VHD -Path $Path
                                Write-Verbose "$Path has been optimized."
                            }
                            catch
                            {
                                Write-Host "Couldn't dismount. $($Path)" -ForegroundColor Red
                                Write-Host $_.Exception.Message -ForegroundColor Red                        
                            }
                            [PSCustomObject]@{
                                VMName = $VM.Name
                                Disk = $Path
                                'Original(GB)' = $VHDSizePre
                                'Current(GB)' = $VHDSizePost
                                'Savings(GB)' = $VHDSavings                                                                                
                            }
                        }                                                     
                    }
                    else
                    {
                        Write-Error "$($Path) is currently mounted. Dismount disk and try again."
                        continue
                    }
                }
                
                # If the VM was turned off then turn it back on
                if ($VMStateBeforeShutdown -eq 'Running')
                {  
                    try
                    {
                        Write-Verbose "Attempting to Start $($VM.Name)."
                        Start-VM -Name $VM.Name -Verbose
                    }
                    catch
                    {
                        Write-Host "Couldn't start $($VM.Name)" -ForegroundColor Red
                        Write-Host $_.Exception.Message -ForegroundColor Red    
                    }
                }  
            }
        }
    }
    
    End
    {
        # Return report of how much space was reclaimed.
        $result
    }
}