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 {
    
    # 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) {               
                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 ($Shutdown) {  
                    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 { $result }
}