Examples/New-PfaVolumeReport.ps1

function New-PfaVolumeReport {
    param ( 
        [Parameter(Mandatory = $true, ParameterSetName = 'NewConnection', Position = 0)]
        [String[]]$Array,
        [Parameter(Mandatory = $true, ParameterSetName = 'NewConnection', Position = 1)]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        [Parameter(Mandatory = $true, ParameterSetName = 'ExistingConnection', Position = 0)]
        [ValidateNotNullOrEmpty()]
        [PureStorageRestApi[]]$Connection,

        [Switch]$ShowTotals
    )
    begin {
        $MyCSS = '
        body {
            color: #333333;
            font-family: Calibri,Tahoma,Arial,Verdana;
            font-size: 11pt;
            margin: 0px;
            padding: 0px;
        }
        h3 {
            margin: 5px 0px 5px 0px;
        }
        h4 {
            margin: 0px;
        }
        table {
            border-collapse: collapse;
             
        }
        th {
            text-align: center;
            font-weight: bold;
            border-top: 1px solid black;
            border-bottom: 1px solid black;
            white-space: nowrap;
            padding: 0px 10px 0px 10px;
        }
        td {
            padding: 2px 10px 2px 10px;
            text-align: center;
            white-space: nowrap;
        }
        .odd {
            background-color: #ffffff;
        }
        .even {
            background-color: #dddddd;
        }'

        # Define parameters array for the bytes columns
        $paramsFormat = @{ 
            # Test criteria: None. Used only for Formatting.
            CommandFormat = ${function:Format-Byte}
        }
        # Define parameters array for the Reduction column
        $paramsReduction = @{ 
            # Test criteria: None. Used only for Formatting.
            StringFormat = "{0:N2} to 1"
        }
        # Define parameters array for the "Growth" columns
        $paramsPercentChanged = @{ 
            # Test criteria: Is value a positive percent?
            ScriptBlock = {([string]$args[0] -ne "n/a" -and [string]$args[0] -ne "0.00 %") -and -not ([string]$args[0]).StartsWith("-")}
            # CSS attribute to add if ScriptBlock is true
            CSSAttribute = "style"
        }
        # Define parameters array for the "Growth" columns
        $paramsPercentChangedNegative = @{ 
            # Test criteria: Is value a positive percent?
            ScriptBlock = {([string]$args[0]).StartsWith('-')}  
            # CSS attribute to add if ScriptBlock is true
            CSSAttribute = "style"
        }
        # Define parameters array for the "Utilization" column
        $paramsUtilization = @{ 
            # Column name
            Column = "Utilization"
            # Test criteria: Is value greater than or equal to Argument?
            ScriptBlock = {[double]$args[0] -ge [double]$args[1]}  
            # CSS attribute to add if ScriptBlock is true
            CSSAttribute = "style"
            # Format column with 2 decimal places and add a percent symbol
            StringFormat = "{0:N2} %"
        }
        
        function Get-PercentChanged {
            [CmdletBinding()]
            param (
                [Parameter(Mandatory = $true, Position = 0)]
                [double]$Reference,
                [Parameter(Mandatory = $true, Position = 1)]
                [double]$Difference
            )
            if (($null -ne $Reference -and $Reference -ne 0) -and ($null -ne $Difference -and $Difference -ne 0)) {
                $Result = [math]::Round((($Difference - $Reference) / $Reference * 100), 2, [MidPointRounding]::AwayFromZero)
                if ($Result -ne -0) {
                    "{0:N2} %" -f $Result
                } else {
                    "0.00 %"
                }
            } else {
                "n/a"
            }
        }

        $AllVolumes = @()
        $AllErrors = @{}
        
        if ($PSCmdlet.ParameterSetName -eq 'NewConnection') {
            foreach ($ArrayName in $Array) {
                try {
                    $Connection += Connect-PfaApi -ArrayName $ArrayName -Credential $Credential -SkipCertificateCheck
                } catch {
                    Write-Error $_.Exception.Message
                }
            }
        }
        foreach ($FlashArray in $Connection) {
            try {
                $Volumes = Invoke-PfaApiRequest -Array $FlashArray -Request RestMethod -Method GET -Path "/volumes?destroyed=false" -SkipCertificateCheck -ErrorAction Stop -PipelineVariable Volume | Where-Object {$_.Name -ne 'pure-protocol-endpoint'} | ForEach-Object {
                    $VolumeMetrics = Invoke-PfaApiRequest -Array $FlashArray -Request RestMethod -Method GET -Path "/volumes/space?names=$($_.Name)" -SkipCertificateCheck -ErrorAction Stop
                    $HostConnections = Invoke-PfaApiRequest -Array $FlashArray -Request RestMethod -Method GET -Path "/connections?volume_names=$($_.Name)" -SkipCertificateCheck -ErrorAction Stop
                    [PSCustomObject]@{
                        Name                =   $_.Name
                        Size                =   $_.Space.Total_Provisioned
                        Volumes             =   $VolumeMetrics.Space.Unique
                        Utilization         =   (($VolumeMetrics.Space.Total_Physical / $_.Space.Total_Provisioned) * 100)
                        Snapshots           =   $VolumeMetrics.Space.Snapshots
                        Reduction           =   $VolumeMetrics.Space.Data_Reduction
                        Shared              =   if ($null -ne $VolumeMetrics.Space.Shared) {
                                                    $VolumeMetrics.Space.Shared
                                                } else {
                                                    "-"
                                                }
                        System              =   if ($null -ne $VolumeMetrics.System) {
                                                    $VolumeMetrics.Space.System
                                                } else {
                                                    "-"
                                                }
                        ThinProvisioning    =   $VolumeMetrics.Space.Thin_Provisioning
                        Written             =   ((1 - $VolumeMetrics.Space.Thin_Provisioning) * $VolumeMetrics.Space.Total_Physical)
                        Total               =   $VolumeMetrics.Space.Total_Physical
                        Protected           =   if (-not (Invoke-PfaApiRequest -Array $FlashArray -Request RestMethod -Method GET -Path "/volume-snapshots?names=$($_.Name)" -SkipCertificateCheck -ErrorAction Stop)) {
                                                    "No"
                                                } else {
                                                    "Yes"
                                                }
                        Connections         =   if (-not $HostConnections.Host -and -not ($HostConnections.Host_Group)) {
                                                    "Not Connected"
                                                } else {
                                                    if (($HostConnections.Host_Group.Name | Sort-Object -Unique).Count -gt 0) {
                                                        ($HostConnections.Host_Group | Sort-Object -Unique Name | Select-Object -ExpandProperty Name) -join ", "
                                                    } else {
                                                        if (($HostConnections.Host.Name | Sort-Object -Unique).Count -gt 1) {
                                                            ($HostConnections.Host | Sort-Object -Unique Name | Select-Object -ExpandProperty Name) -join ", "
                                                        } else {
                                                            $HostConnections.Host.Name
                                                        }
                                                    }
                                                }
                        '7DayGrowth'        =   Invoke-Command -Command {
                                                    try {
                                                        Get-PercentChanged (Invoke-PfaApiRequest -Array $FlashArray -Request RestMethod -Method GET -Path "/volumes/space?resolution=1800000&start_time=$(([DateTimeOffset]$(Get-Date).AddDays(-7)).ToUnixTimeMilliseconds())&end_time=$(([DateTimeOffset]$(Get-Date).AddDays(-7).AddMilliseconds(1800000)).ToUnixTimeMilliseconds())&names=$($_.Name)" -SkipCertificateCheck -ErrorAction Stop).Space.Total_Physical $VolumeMetrics.Space.Total_Physical
                                                    } catch {
                                                        throw $_
                                                    }
                                                }
                        '30DayGrowth'       =   Invoke-Command -Command {
                                                    try {
                                                        Get-PercentChanged $(Invoke-PfaApiRequest -Array $FlashArray -Request RestMethod -Method GET -Path "/volumes/space?resolution=7200000&start_time=$(([DateTimeOffset]$(Get-Date).AddDays(-30)).ToUnixTimeMilliseconds())&end_time=$(([DateTimeOffset]$(Get-Date).AddDays(-30).AddMilliseconds(7200000)).ToUnixTimeMilliseconds())&names=$($_.Name)" -SkipCertificateCheck -ErrorAction Stop).Space.Total_Physical $VolumeMetrics.Space.Total_Physical
                                                    } catch {
                                                        "n/a"
                                                    }
                                                }
                        '1YearGrowth'       =   Invoke-Command -Command {
                                                    try {
                                                        Get-PercentChanged $(Invoke-PfaApiRequest -Array $FlashArray -Request RestMethod -Method GET -Path "/volumes/space?resolution=86400000&start_time=$(([DateTimeOffset]$(Get-Date).AddYears(-1)).ToUnixTimeMilliseconds())&end_time=$(([DateTimeOffset]$(Get-Date).AddYears(-1).AddMilliseconds(86400000)).ToUnixTimeMilliseconds())&names=$($_.Name)" -SkipCertificateCheck -ErrorAction Stop).Space.Total_Physical $VolumeMetrics.Space.Total_Physical
                                                    } catch {
                                                        "n/a"
                                                    }
                                                }
                        Array               =   $FlashArray.ArrayName
                    }
                }
                $AllVolumes += $Volumes
                if ($ErrorMessages) {
                    $AllErrors += [PSCustomObject]@{
                        Array           =   $FlashArray
                        ErrorMessage    =   $ErrorMessage
                    }
                }
            } catch {
                throw $_
            }
        }
    }
    process {
        if ($AllVolumes.Count -gt 0 -or $AllErrors.Count -gt 0) {
            if (Get-Module -Name "PS2HTMLTable") {
                $HTML = New-HTMLHead -Style $MyCSS -Title "Flash Array - Volume Report"
                $HTML += '<table id="container" cellpadding="0" cellspacing="0" border="0">'

                $AllVolumes | Sort-Object Array, Name | Group-Object Array | ForEach-Object -Begin {$HTMLTable = $null} -Process {
                    $HTML += "<tr><td style=""text-align: left;"" colspan=""6""><h3>$($_.Name) - Volumes ($($_.Group.Count))</h3></td></tr>"
                    $HTMLTable = $_.Group | Select-Object Name, Size, Volumes, Utilization, Snapshots, Reduction, Shared, System, @{Name = "Thin Provisioning";Expression = {"{0:N3}" -f $_.ThinProvisioning}}, Written, Total, Protected, @{Name = "Connection(s)";Expression = {$_.Connections}}, @{Name = "7 Day Growth";Expression = {$_.'7DayGrowth'}}, @{Name = "30 Day Growth";Expression = {$_.'30DayGrowth'}}, @{Name = "1 Year Growth";Expression = {$_.'1YearGrowth'}} | New-HTMLTable -HTMLDecode -SetAlternating -NestedTable -RemoveColumnGroup

                    # Format Column Data
                    $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -Column "Size" @paramsFormat -ApplyFormat
                    $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -Column "Volumes" @paramsFormat -ApplyFormat
                    $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -Column "Snapshots" @paramsFormat -ApplyFormat
                    $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -Column "Written" @paramsFormat -ApplyFormat
                    $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -Column "Total" @paramsFormat -ApplyFormat
                    $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -Column "Reduction" @paramsReduction -ApplyFormat

                    # Color "Utilization" cell yellow if value is greater than or equal to 60%
                    $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -Argument 60 -CSSAttributeValue "background-color:#fac13a;" @paramsUtilization
                    # Color "Utilization" cell orange if value is greater than or equal to 75%
                    $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -Argument 75 -CSSAttributeValue "background-color:#fa8a1c;" @paramsUtilization
                    # Color "Utilization" cell red if value is greater than or equal to 90%
                    $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -Argument 90 -CSSAttributeValue "background-color:#e44f12;" @paramsUtilization -ApplyFormat

                    if ($null -ne $_.Group[0]."7DayGrowth") {
                        $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -CSSAttributeValue "color:#fe5000;" -Column "7 Day Growth" @paramsPercentChanged
                        $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -CSSAttributeValue "color:#0d98e3;" -Column "7 Day Growth" @paramsPercentChangedNegative
                    }
                    if ($null -ne $_.Group[0]."30DayGrowth") {
                        $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -CSSAttributeValue "color:#fe5000;" -Column "30 Day Growth" @paramsPercentChanged
                        $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -CSSAttributeValue "color:#0d98e3;" -Column "30 Day Growth" @paramsPercentChangedNegative
                    }
                    if ($null -ne $_.Group[0]."1YearGrowth") {
                        $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -CSSAttributeValue "color:#fe5000;" -Column "1 Year Growth" @paramsPercentChanged
                        $HTMLTable = Add-HTMLTableColor -HTML $HTMLTable -CSSAttributeValue "color:#0d98e3;" -Column "1 Year Growth" @paramsPercentChangedNegative
                    }
                    if ($ShowTotals) {
                        $HTMLTable += "<tr><td style=""border-top: 1px solid black;""></td><td style=""border-top: 1px solid black;font-weight: bold;"">$(Format-Byte ($_.Group.Size | Measure-Object -Sum).Sum)</td><td style=""border-top: 1px solid black;font-weight: bold;"">$(Format-Byte ($_.Group.Volumes | Measure-Object -Sum).Sum)</td><td style=""border-top: 1px solid black;font-weight: bold;"">$("{0:N2} %" -f (($_.Group.Utilization | Measure-Object -Maximum).Maximum))</td><td style=""border-top: 1px solid black;font-weight: bold;"">$(Format-Byte ($_.Group.Snapshots | Measure-Object -Sum).Sum)</td><td style=""border-top: 1px solid black;font-weight: bold;"">$("{0:N2} to 1" -f ($_.Group.Reduction | Measure-Object -Average).Average)</td><td style=""border-top: 1px solid black;""></td><td style=""border-top: 1px solid black;""></td><td style=""border-top: 1px solid black;""></td><td style=""border-top: 1px solid black;font-weight: bold;"">$(Format-Byte ($_.Group.Total | Measure-Object -Sum).Sum)</td><td style=""border-top: 1px solid black;font-weight: bold;"">$(Format-Byte ($_.Group.Written | Measure-Object -Sum).Sum)</td><td style=""border-top: 1px solid black;""></td><td style=""border-top: 1px solid black;""></td><td style=""border-top: 1px solid black;font-weight: bold;"">$(if ($null -ne ($_.Group.'7DayGrowth' | Where-Object {$_ -ne 'n/a'})) {"{0:N2} %" -f (($_.Group.'7DayGrowth' | Where-Object {$_ -ne 'n/a'}).Replace(' %', '') | Measure-Object -Maximum).Maximum})</td><td style=""border-top: 1px solid black;font-weight: bold;"">$(if ($null -ne ($_.Group.'30DayGrowth' | Where-Object {$_ -ne 'n/a'})) {"{0:N2} %" -f (($_.Group.'30DayGrowth' | Where-Object {$_ -ne 'n/a'}).Replace(' %', '') | Measure-Object -Maximum).Maximum})</td><td style=""border-top: 1px solid black;font-weight: bold;"">$(if ($null -ne ($_.Group.'1YearGrowth' | Where-Object {$_ -ne 'n/a'})) {"{0:N2} %" -f (($_.Group.'1YearGrowth' | Where-Object {$_ -ne 'n/a'}).Replace(' %', '') | Measure-Object -Maximum).Maximum})</td></tr>"
                    } else {
                        $HTMLTable += '<tr><td colspan="16" style="border-top: 1px solid black;padding: 0px 10px 0px 10px;"></td></tr>'
                    }
                    $HTML += $HTMLTable
                }
                $HTML += "</table>"
                $HTML = $HTML | Close-HTML -Validate
            } else {
                $HTML = @"
                <!DOCTYPE html>
                    <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
                        <head>
                            <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
                            <title>$Array - Status Report</title>
                            <style>
                            $MyCSS
                            </Style>
                        </head>
                        <body>
"@

                $HTML += '<table id="container" cellpadding="0" cellspacing="0" border="0">'
                $AllVolumes | Sort-Object Array, Name | Group-Object Array | ForEach-Object -Begin {$HTMLTable = $null} -Process {
                    $HTML += "<tr><td style=""text-align: left;"" colspan=""6""><h3>$($_.Name) - Volumes ($($_.Group.Count))</h3></td></tr>"
                    $Xml = [System.Xml.Linq.XDocument]::Parse("$($_.Group | Select-Object Name, @{Name = "Size";Expression = {Format-Byte $_.Size}}, @{Name = "Volumes";Expression = {Format-Byte $_.Volumes}}, @{Name = "Utilization";Expression = {"{0:N2} %" -f $_.Utilization}}, @{Name = "Snapshots";Expression = {Format-Byte $_.Snapshots}}, @{Name = "Reduction";Expression = {"{0:N3} to 1" -f $_.Reduction}}, Shared, System, @{Name = "Thin Provisioning";Expression = {"{0:N3}" -f $_.ThinProvisioning}}, @{Name = "Written";Expression = {Format-Byte $_.Written}}, @{Name = "Total";Expression = {Format-Byte $_.Total}}, Protected, @{Name = "Connection(s)";Expression = {$_.Connections}}, @{Name = "7 Day Growth";Expression = {$_.'7DayGrowth'}}, @{Name = "30 Day Growth";Expression = {$_.'30DayGrowth'}}, @{Name = "1 Year Growth";Expression = {$_.'1YearGrowth'}} | ConvertTo-Html -Fragment)")
                    $Xml.Element("table").Element("colgroup").Remove()
                    foreach ($XmlTr in $($Xml.Descendants("tr"))) {
                        if ($XmlTr.Where({$_.Element('td')})) {
                            if (($XmlTr.NodesBeforeSelf() | Measure-Object).Count % 2 -eq 0) {
                                $XmlTr.SetAttributeValue("class", "even $($XMlTr.Attribute("class").Value)".Trim())
                            } else {
                                $XmlTr.SetAttributeValue("class", "odd $($XMlTr.Attribute("class").Value)".Trim())
                            }
                        }
                    }
                    $HTMLTable = [System.Xml.Linq.XDocument]::Parse($Xml).Document.ToString().Replace("<table>", "").Replace("</table>", "")

                    if ($ShowTotals) {
                        $HTMLTable += "<tr><td style=""border-top: 1px solid black;""></td><td style=""border-top: 1px solid black;font-weight: bold;"">$(Format-Byte ($_.Group.Size | Measure-Object -Sum).Sum)</td><td style=""border-top: 1px solid black;font-weight: bold;"">$(Format-Byte ($_.Group.Volumes | Measure-Object -Sum).Sum)</td><td style=""border-top: 1px solid black;font-weight: bold;"">$("{0:N2} %" -f (($_.Group.Utilization | Measure-Object -Maximum).Maximum))</td><td style=""border-top: 1px solid black;font-weight: bold;"">$(Format-Byte ($_.Group.Snapshots | Measure-Object -Sum).Sum)</td><td style=""border-top: 1px solid black;font-weight: bold;"">$("{0:N2} to 1" -f ($_.Group.Reduction | Measure-Object -Average).Average)</td><td style=""border-top: 1px solid black;""></td><td style=""border-top: 1px solid black;""></td><td style=""border-top: 1px solid black;""></td><td style=""border-top: 1px solid black;font-weight: bold;"">$(Format-Byte ($_.Group.Total | Measure-Object -Sum).Sum)</td><td style=""border-top: 1px solid black;font-weight: bold;"">$(Format-Byte ($_.Group.Written | Measure-Object -Sum).Sum)</td><td style=""border-top: 1px solid black;""></td><td style=""border-top: 1px solid black;""></td><td style=""border-top: 1px solid black;font-weight: bold;"">$(if ($null -ne ($_.Group.'7DayGrowth' | Where-Object {$_ -ne 'n/a'})) {"{0:N2} %" -f (($_.Group.'7DayGrowth' | Where-Object {$_ -ne 'n/a'}).Replace(' %', '') | Measure-Object -Maximum).Maximum})</td><td style=""border-top: 1px solid black;font-weight: bold;"">$(if ($null -ne ($_.Group.'30DayGrowth' | Where-Object {$_ -ne 'n/a'})) {"{0:N2} %" -f (($_.Group.'30DayGrowth' | Where-Object {$_ -ne 'n/a'}).Replace(' %', '') | Measure-Object -Maximum).Maximum})</td><td style=""border-top: 1px solid black;font-weight: bold;"">$(if ($null -ne ($_.Group.'1YearGrowth' | Where-Object {$_ -ne 'n/a'})) {"{0:N2} %" -f (($_.Group.'1YearGrowth' | Where-Object {$_ -ne 'n/a'}).Replace(' %', '') | Measure-Object -Maximum).Maximum})</td></tr>"
                    } else {
                        $HTMLTable += '<tr><td colspan="16" style="border-top: 1px solid black;padding: 0px 10px 0px 10px;"></td></tr>'
                    }
                    $HTML += $HTMLTable
                }
                $HTML += "</table>"
                $HTML += "</body></html>"
            }
            $HTML | Out-File "FlashArray - Volumes.html"
        }
    }
}