mardi 28 juin 2016

How to create a report on the VM Guest Disks in VMware vCenter via Powershell




A while ago, I was asked to build up a report about the Free and Occupied space by the VM in our vCenter Infrastructure.

The task itself wasn't that complicated, but the tricky part was mostly about the presentation. So I came up with a simple HTML design which lists the following properties:

  • The VM name
  • The Drive name
  • The Total capacity on the drive
  • The Free space on the drive
  • The Occupation percentage shown along with a progress bar

Once executed, the results are parsed in a easy-to-read way as seen below!



The script may be called normally or by using a piped format:

"vc001.katalykt.lan", "vc002.katalykt.lan" |

    Get-VMGuestDiskCapacity -Verbose |

    Sort-Object -Property Name |

    Export-VMGuestDiskCapacity -Path d:\reports -Verbose



Note that the script uses two profile variables for the mailing ($Profile_Mailing and $Profile_SMTP) which may be defined as follows in your powershell profile (you may also adapt the script to pass it as a variable!):

# Local System Mailing Definition

$_Profile_Mailing = New-Object -TypeName PSObject -Property @{

    From = "Powershell Reporter<posh@noreply.com>"

    ToSysAdm = "SYSTEM REPORT <SYSTEM-REPORT@katalykt.lan>"

}


# Profile - Global RO - System Mailing

New-Variable -Name Profile_Mailing -Value $_Profile_Mailing -Scope Global -Option ReadOnly


# Profile - Global RO - SMTP

New-Variable -Name SMTP_Host -Value "smtp.katalykt.lan" -Scope Global -Option ReadOnly



In my environment, the script isn't being executed with an account that allows a direct connection to the vCenter. In this case I am using some Read-Only credentials stored within my Powershell Profile. You may want to use the -Credentials switch with the Get-VMGuestDiskCapacity function, thus giving us a command like:

"vc001.katalykt.lan", "vc002.katalykt.lan" |

    Get-VMGuestDiskCapacity -Credentials $SYSTEM_Accounts.vCenterReader -Verbose |

    Sort-Object -Property Name |

    Export-VMGuestDiskCapacity -Path d:\reports -Verbose


The credentials may be defined as follows:

# Local System Operators Definition

$_SYSTEM_Accounts = New-Object -TypeName PSObject -Property @{

    vCenterReader = New-Object System.Management.Automation.PSCredential ("KATALYKT\srv-vcreader", ("654d1116743f0423413b16050a5345MgB8AGAFAARgBFAFEAZABwAGEALcAdABNkAMQBowBmAEEATQB4AGADgANwB1AHcAPQA9AHwAMQBjADUAMQBmADMANQAzAGYAMgAwAGEAOAAzADcAZQBlADkAMgA4ADMAZQOAA1AGMANQA4AGIAYwAyAGUANAA5ADMAZABlADMAZQAyADYABjAGYANAA4ADkAYwBkADkAMQDgAMwAzADcAA3ADMAZQBmADIANwA4AMAA1ADIAMgA=" | ConvertTo-SecureString -Key (1..16)))

}


# Profile - Global RO - System Operators

New-Variable -Name SYSTEM_Accounts -Value $_SYSTEM_Accounts -Scope Global -Option ReadOnly



So here's the script!

#Requires -Version 3.0


Function Export-VMGuestDiskCapacity {

<#

    .SYNOPSIS

            This function will export the output from Get-VMGuestDiskCapacity

    .DESCRIPTION

            This function will export the output from Get-VMGuestDiskCapacity via Mail or/and in an HTML report

    .PARAMETER  VM

            The VM status in an object-format, pipeline is supported

    .PARAMETER  Path

            (Optional) The Folder Path used to store the HTML report in

    .PARAMETER  AsMail

            (Optional) Send the report by Mail if present

    .EXAMPLE

            $VM = Get-VMGuestDiskCapacity -vCenter vc001.katalykt.lan

            Export-VMGuestDiskCapacity -VM $VM

    .EXAMPLE

            Get-VMGuestDiskCapacity -vCenter vc001.katalykt.lan | Export-VMGuestDiskCapacity -Path d:\reports

    .EXAMPLE

            Get-VMGuestDiskCapacity -vCenter vc001.katalykt.lan | Export-VMGuestDiskCapacity -AsMail

    .EXAMPLE

            "vc001.katalykt.lan", "vc002.katalykt.lan" | Get-VMGuestDiskCapacity | Export-VMGuestDiskCapacity -Path d:\reports -AsMail

    .PROFILES

            [System.Object]$Profile_Mailing

            [System.String]$Profile_SMTP

    .NOTES

            NAME:     Export-VMGuestDiskCapacity

            AUTHOR:   ROULEAU Benjamin

            LASTEDIT: 2015-08-21

#>

    [CmdletBinding()]

    PARAM(

        [Parameter(

            Mandatory,

            ValueFromPipeline,

            ValueFromPipelineByPropertyName)]

        $VM,


        [ValidateScript({Test-Path -Path $_ -PathType Container})]

        $Path,


        [Switch]$AsMail

    )


    BEGIN {

        Write-Verbose -Message "[BEGIN - Export-VMGuestDiskCapacity] Attempting to export the desired output"


        Function Get-ProgressColoration {

        <#

            .SYNOPSIS

                    This function will return a color according to an Hue/Saturation/Lightness Value

            .DESCRIPTION

                    This function will return a color according to an Hue/Saturation/Lightness Value

            .PARAMETER  HSLValue

                    The HSL Value on a scale from 0 to 1

            .PARAMETER  ConvertToRGB

                    (Optional) Define whether the output should be returned in a RGB format or not

            .PARAMETER  Saturation

                    (Optional) The Saturation Value on a scale from 0 to 1

            .PARAMETER  Lightness

                    (Optional) The Lightness Value on a scale from 0 to 1

            .EXAMPLE

                    Get-ProgressColoration -HSLValue 0.5

                    

                    hsl(60.0,100%,50%)

            .EXAMPLE

                    Get-ProgressColoration -HSLValue 0.2 -Saturation 0.7 -Lightness 0.4

                    

                    hsl(96.0,70%,40%)

            .EXAMPLE

                    Get-ProgressColoration -HSLValue 0.5 -ConvertToRGB

                    

                    rgb(255,255,0)

            .NOTES

                    NAME:     Get-ProgressColoration

                    AUTHOR:   ROULEAU Benjamin

                    LASTEDIT: 2015-08-21

        #>

            [CmdletBinding()]

            PARAM(

                [Parameter(Mandatory)]

                [ValidateRange(0,1.00)]

                [Decimal]$HSLValue,

                

                [Switch]$ConvertToRGB,


                $Saturation = 1,


                $Lightness = 0.5

            )


            BEGIN {

                Write-Verbose -Message "[BEGIN - Get-ProgressColoration] Getting a desired color for value $Value"

            }


            PROCESS {

                # Return either the HSL or the RGB format

                If ($ConvertToRGB) {

                    $Hue = ((1-$HSLValue)*120).ToString() -replace ',', '.'

                    "rgb({0})" -f ((Convert-HSLToRGB -Hue ($Hue/360) -Saturation $Saturation -Lightness $Lightness) -join ',')

                } Else {

                    "hsl({0},{1}%,{2}%)" -f ((((1-$HSLValue)*120).ToString() -replace ',', '.'), [System.Math]::Round($Saturation * 100), [System.Math]::Round($Lightness * 100))

                }

            }


            END {

                

            }

        }


        Function Convert-HSLToRGB {

        <#

            .SYNOPSIS

                    This function will convert a Hue/Saturation/Lightness Value to a Red/Green/Blue format

            .DESCRIPTION

                    This function will convert a Hue/Saturation/Lightness Value to a Red/Green/Blue format

            .PARAMETER  Hue

                    The Hue Value on a scale from 0 to 1

            .PARAMETER  Saturation

                    The Saturation Value on a scale from 0 to 1

            .PARAMETER  Lightness

                    The Lightness Value on a scale from 0 to 1

            .EXAMPLE

                    Convert-HSLToRGB -Hue 0.166666666666667 -Saturation 1 -Lightness 0.5

                    

                    255, 255, 0

            .NOTES

                    NAME:     Convert-HSLToRGB

                    AUTHOR:   ROULEAU Benjamin

                    LASTEDIT: 2015-08-21

            .LINKS

                    https://en.wikipedia.org/wiki/HSL_color_space

                    http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c

        #>

            [CmdletBinding()]

            PARAM(

                [Parameter(Mandatory)]

                $Hue,


                [Parameter(Mandatory)]

                $Saturation,


                [Parameter(Mandatory)]

                $Lightness

            )


            BEGIN {

                Write-Verbose -Message "[BEGIN - Convert-HSLToRGB] Converting HSL: $Hue, $Saturation, $Lightness to RGB"

                

                Function Convert-HueToRGB {

                <#

                    .SYNOPSIS

                            This function will convert a Hue value to either a Red, Green or Blue value

                    .DESCRIPTION

                            This function will convert a Hue value to either a Red, Green or Blue value

                    .PARAMETER  p

                            The temporary 2 value

                    .PARAMETER  q

                            The temporary 1 value

                    .PARAMETER  t

                            The temporary color (R, G or B)

                    .EXAMPLE

                            Convert-HSLToRGB -Hue 0.166666666666667 -Saturation 1 -Lightness 0.5

                    

                            255, 255, 0

                    .NOTES

                            NAME:     Convert-HueToRGB

                            AUTHOR:   ROULEAU Benjamin

                            LASTEDIT: 2015-08-21

                    .LINKS

                            https://en.wikipedia.org/wiki/HSL_color_space

                            http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c

                            http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/

                #>

                    [CmdletBinding()]

                    PARAM(

                        [Parameter(Mandatory)]

                        $p,


                        [Parameter(Mandatory)]

                        $q,


                        [Parameter(Mandatory)]

                        $t

                    )


                    BEGIN {


                    }


                    PROCESS {

                        If ($t -lt 0) { $t += 1 }

                        If ($t -gt 1) { $t -= 1 }

                        If ($t -lt 1/6) { return (($p + ($q - $p)) * 6 * $t) }

                        If ($t -lt 1/2) { return $q }

                        If ($t -lt 2/3) { return (($p + ($q - $p)) * (2/3 - $t) * 6) }

                        return $p

                    }


                    END {


                    }

                }

            }


            PROCESS {

                # If there's no saturation then it's a shade of grey.

                If ($Saturation -eq 0) {

                    $Red = $Green = $Blue = $Lightness

                } Else {

                    If ($Lightness -lt 0.5) {

                        $q = $Lightness * (1 + $Saturation)

                    } Else {

                        $q = $Lightness + $Saturation - ($Lightness *$Saturation)

                    }


                    $p = 2 * $Lightness -$q


                    $Red = Convert-HueToRGB -p $p -q $q -t ($Hue + 1/3)

                    $Green = Convert-HueToRGB -p $p -q $q -t ($Hue)

                    $Blue = Convert-HueToRGB -p $p -q $q -t ($Hue - 1/3)

                }


                # Round it up

                $Red = [System.Math]::Round($Red * 255)

                $Green = [System.Math]::Round($Green * 255)

                $Blue = [System.Math]::Round($Blue * 255)


                Write-Verbose -Message "[BEGIN - Convert-HSLToRGB] Returning RGB: $Red, $Green, $Blue"


                @($Red, $Green, $Blue)

            }


            END {

                

            }

        }


        $HTML_BorderThinColor = "#e5e5e5"

        $HTML_BorderThickColor = "#6690bc"

        $InitDate = Get-Date -Format "dd/MM/yyyy HH:mm:ss"

    }


    PROCESS {

        # Build the output

        $HTML_BorderColor = $HTML_BorderThickColor

        If ($VM.Disks) {

            $HTML_Disks = ""


            $VM.Disks | Sort-Object DiskPath | ForEach-Object {

                # Parse properly

                If ($HTML_Disks -ne "") {

                    $TR_OpenTag = "<tr>"

                } Else {

                    $TR_OpenTag = ""

                }


                # Coloration scale

                $ScaleRatio = 1

                If ($_.PercentFull -lt 95) { $ScaleRatio = 1.3 }



                $HTML_Disks += '

                  {5}

                    <td style="border-right: 1px solid {7}; border-top: 1px solid {6}; padding-left: 4px;"><span id="pbcontainer">{0}</span></td>

                    <td width="80px" id="rowlight" style="border-top: 1px solid {6};">{1} GB</td>

                    <td width="80px" id="rowlight" style="border-top: 1px solid {6};">{2} GB</td>

                    <td width="50px" id="rowlight" style="border-top: 1px solid {6};">{3} %</td>

                    <td style="border-top: 1px solid {6};">

                        <div class="percentbar" style="width:100px; ">

                          <div style="background-color: {4};width:{3}px;"></div>

                        </div>

                    </td>

                  </tr>

                ' -f $_.DiskPath, $_.Capacity, $_.Freespace, $_.PercentFull, (Get-ProgressColoration -HSLValue (($_.PercentFull / 100) / $ScaleRatio) -ConvertToRGB), $TR_OpenTag, $HTML_BorderColor, $HTML_BorderThinColor

                

                # The next borders are lighter than the first one

                $HTML_BorderColor = $HTML_BorderThinColor

            }


            $HTML_Content += '

            <tr>

              <td width="300px" rowspan="{0}" id="rowhead">{1}</td>

              {2}

            

            ' -f $VM.Disks.Count, $VM.Name, $HTML_Disks, $HTML_BorderThickColor

        } Else {

            $HTML_Content += '

            <tr>

              <td width="300px" id="rowhead">{0}</td>

              <td colspan="5" id="rowoff">{1}</td>

            </tr>

            ' -f $VM.Name, $VM.Status, $HTML_BorderThickColor

        }

    }


    END {

        $HTML_Export = @"

        <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

        <html xmlns="http://www.w3.org/1999/xhtml">

          <head>

            <title>VMware Disk Report</title>

            <style type="text/css">

              body {

                width: 900px;

                margin: 0 auto;

                padding-top: 15px;

              }

          

              #reporting {

                border: 1px solid #6690BC;

                border-collapse:collapse;

                font: 12px Arial, Helvetica, sans-serif;

                margin: 0 auto;

              }

          

              #reporting th { background-color: #6690BC; color: #ffffff; }


              #reporting tr { vertical-align: top; }    


              #subheaders {

                font-weight: bold;

                padding-left: 4px;

                background-color: #A8C8E9;

              }


              #rowlight {

                border-right: 1px solid #e5e5e5;

                padding-left: 4px;

              }


              #rowhead {

                background-color: #D3E6FA;

                border-right: 1px solid #6690BC;

                border-top: 1px solid #6690bc;

                padding-left: 4px;

              }


              #rowoff {

                padding-left: 4px;

                border-top: 1px solid #6690bc;

                background-color: #CCCCCC;

              }


              #pbcontainer {

                display:block;

                float:left;

                overflow: hidden;

                text-overflow:ellipsis;

                max-width:80px;

                width:80px;

              }

          

              .percentbar { background:#CCCCCC; border:1px solid #666666; height:10px; }

              .percentbar div { background: #28B8C0; height: 10px; }

            </style>

          </head>

      

          <body>

            <table id="reporting">

              <tr>

                <th>Virtual Machine</th>

                <th colspan="5">Guest Disk Properties</th>

              </tr>


              <tr>

                <td width="300px" id="subheaders"></td>

                <td width="60px" id="subheaders">Path</td>

                <td width="80px" id="subheaders">Capacity</td>

                <td width="80px" id="subheaders">Free Space</td>

                <td width="150px" colspan="2" id="subheaders">Occupation</td>

              </tr>


              $HTML_Content

            </table>


            <br /><br />

          </body>

        </html>

"@

        

        # Output the HTML in a file if specified

        If ($Path) {

            $HTML_Export | Out-File (Join-Path -Path $Path -ChildPath ("vCenter-GuestDisks-Report-{0}.html" -f (Get-Date -Format "yyyyMMdd_HHmmss")))

        }


        # Send the report via Mail if specified

        If ($AsMail) {

            Send-MailMessage `

                -To $Profile_Mailing.ToSysAdm `

                -From $Profile_Mailing.From `

                -Subject "vCenter Report: $InitDate" `

                -Body $HTML_Export `

                -BodyAsHtml `

                -SmtpServer $Profile_SMTP

        }

    }

}


Function Get-VMGuestDiskCapacity {

<#

    .SYNOPSIS

            This function will retrieve the Guest Disk capacity of all VMs present in a vCenter

    .DESCRIPTION

            This function will retrieve the Guest Disk capacity of all VMs present in a vCenter

            as long as the Guest Tools are present in the VM

    .PARAMETER  vCenter

            The vCenter server, pipeline is supported

    .PARAMETER  Credentials

            (Optional) The Credentials used to connect to the vCenter(s), the current user is used

            if nothing is specified

    .EXAMPLE

            Get-VMGuestDiskCapacity -vCenter vc001.katalykt.lan


            Status         VCServer              Name             Disks                                      

            ------         --------              ----             -----                                      

            Powered On     vc001.katalykt.lan    srv-iis001       {@{Capacity=10; PercentFull=92; DiskPath=...

            Powered On     vc001.katalykt.lan    srv-iis002       {@{Capacity=19,9; PercentFull=71; DiskPat...

    .EXAMPLE

            "vc001.katalykt.lan", "vc002.katalykt.lan" | Get-VMGuestDiskCapacity


            Status         VCServer              Name             Disks                                      

            ------         --------              ----             -----                                      

            Powered On     vc001.katalykt.lan    srv-iis001       {@{Capacity=10; PercentFull=92; DiskPath=...

            Powered On     vc001.katalykt.lan    srv-iis002       {@{Capacity=19,9; PercentFull=71; DiskPat...

            Powered On     vc002.katalykt.lan    srv-sql001       {@{Capacity=120; PercentFull=65; DiskPat...

    .SNAPIN

           VMware.VimAutomation.Core

    .NOTES

            NAME:     Export-VMGuestDiskCapacity

            AUTHOR:   ROULEAU Benjamin

            LASTEDIT: 2015-08-21

#>

    [CmdletBinding()]

    PARAM(

        [Parameter(

            Mandatory,

            ValueFromPipeline,

            ValueFromPipelineByPropertyName)]

        [String]$vCenter,


        $Credentials

    )


    BEGIN {

        Try {

            # Attempt to load the VIM Snapin

            If ( (Get-PSSnapin -Name "VMware.VimAutomation.Core" -errorAction SilentlyContinue) -eq $null ) {

                Write-Verbose -Message "[BEGIN - Get-VMGuestDiskCapacity] Attempting to load the VMware.VimAutomation.Core Snapin"

                Add-PsSnapin "VMware.VimAutomation.Core" -ErrorAction Stop -ErrorVariable ErrSnapin

            }

        } Catch {

            If ($ErrSnapin) { Write-Warning -Message ("[BEGIN - Get-VMGuestDiskCapacity] An error has occured during the PSSnapin import: {0}" -f $Error[0].Exception.Message) }

            exit

        }

    }


    PROCESS {

        Try {

            $vCenter | ForEach-Object {

                $vCenterHost = $_


                If ($Credentials) {

                    Write-Verbose -Message "[PROCESS - Get-VMGuestDiskCapacity] Attempting to connect to the vCenter host: $vCenterHost with the given credentials"

                    $VCServer = Connect-VIServer -Server $vCenterHost -Credential $Credentials -ErrorAction SilentlyContinue -ErrorVariable ErrVCConnect

                } Else {

                    Write-Verbose -Message "[PROCESS - Get-VMGuestDiskCapacity] Attempting to connect to the vCenter host: $vCenterHost with the current user"

                    $VCServer = Connect-VIServer -Server $vCenterHost -ErrorAction SilentlyContinue -ErrorVariable ErrVCConnect

                }


                # If we're connected to the vCenter, we retrieve the list of it's VMs

                If ($VCServer) {

                    Get-View -Server $VCServer -ViewType VirtualMachine | Where-Object {-not $_.Config.Template} | Sort-Object Name | ForEach-Object {

                        $VM = New-Object -TypeName PSObject -Property @{

                            Name = $_.Name

                            VCServer = $vCenterHost

                            Status = ""

                        }

            

                        Write-Verbose -Message ("[PROCESS - Get-VMGuestDiskCapacity] Processing Virtual Machine '{0}'" -f $VM.Name)


                        If ($_.Summary.Runtime.PowerState -eq "poweredOn") {

                            If ($_.Summary.Guest.ToolsRunningStatus -eq "guestToolsRunning") {

                                # The VM is online and it has VM Tools running. Retrieve the disks capacity

                                $VM.Status = "Powered On"


                                $VM | Add-Member -Name Disks -MemberType NoteProperty -Value ($_.Guest.Disk | ForEach-Object {

                                    New-Object -TypeName PSObject -Property @{

                                        DiskPath = $_.DiskPath

                                        Capacity = [math]::Round($_.Capacity/ 1GB, 2)

                                        FreeSpace = [math]::Round($_.FreeSpace / 1GB, 2)

                                        PercentFull = (100 - [math]::Round(([math]::Round($_.FreeSpace / 1GB, 2) * 100) / [math]::Round($_.Capacity/ 1GB, 2)))

                                    }

                                })

                    

                            } Else {

                                Write-Warning -Message ("[PROCESS - Get-VMGuestDiskCapacity] VM Tools on Virtual Machine '{0}' does not appear to be running" -f $VM.Name)

                                $VM.Status = "VM Tools missing"

                            }

                        } Else {

                            Write-Warning -Message ("[PROCESS - Get-VMGuestDiskCapacity] Virtual Machine '{0}' is not powered on" -f $VM.Name)

                            

                            $VM.Status = "Not Powered On"


                            # Just in case, it could managed by SRM?

                            If ($_.Config.ManagedBy.ExtensionKey -eq "com.vmware.vcDr") { $VM.Status = "Managed by Site Recovery Manager" }

                        }


                        $VM

                    }


                    Write-Verbose -Message "[PROCESS - Get-VMGuestDiskCapacity] Removing the vCenter connection from host $_"

                    Disconnect-VIServer $VCServer -Force -Confirm:$false | Out-Null

                } Else {

                    Write-Warning -Message "[PROCESS - Get-VMGuestDiskCapacity] vCenter connection is not established"

                }

            }

        } Catch {

            If ($ErrVCConnect) { Write-Warning -Message ("[PROCESS - Get-VMGuestDiskCapacity] Cannot connect to the given vCenter: {0}" -f $Error[0].Exception.Message) }

            Write-Warning $Error[0].Exception.Message

        }

    }


    END {

        

    }

}