Src/Public/Invoke-AsBuiltReport.Nutanix.PrismElement.ps1

function Invoke-AsBuiltReport.Nutanix.PrismElement {
    <#
    .SYNOPSIS
        PowerShell script to document the configuration of Nutanix Prism infrastucture in Word/HTML/XML/Text formats
    .DESCRIPTION
        Documents the configuration of Nutanix Prism infrastucture in Word/HTML/XML/Text formats using PScribo.
    .NOTES
        Version: 0.1.0
        Author: Tim Carman
        Twitter: @tpcarman
        Github: tpcarman
        Credits: Iain Brighton (@iainbrighton) - PScribo module
                         
    .LINK
        https://github.com/AsBuiltReport/AsBuiltReport.Nutanix.PrismElement
    #>


    param (
        [String[]] $Target,
        [PSCredential] $Credential,
        [String]$StylePath
    )

    # Import JSON Configuration for Options and InfoLevel
    $InfoLevel = $ReportConfig.InfoLevel

    $TextInfo = (Get-Culture).TextInfo

    # If custom style not set, use default style
    if (!$StylePath) {
        & "$PSScriptRoot\..\..\AsBuiltReport.Nutanix.PrismElement.Style.ps1"
    }

    #region Workaround for SelfSigned Cert an force TLS 1.2
    if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type) {
        $certCallback = @"
        using System;
        using System.Net;
        using System.Net.Security;
        using System.Security.Cryptography.X509Certificates;
        public class ServerCertificateValidationCallback
        {
            public static void Ignore()
            {
                if(ServicePointManager.ServerCertificateValidationCallback ==null)
                {
                    ServicePointManager.ServerCertificateValidationCallback +=
                        delegate
                        (
                            Object obj,
                            X509Certificate certificate,
                            X509Chain chain,
                            SslPolicyErrors errors
                        )
                        {
                            return true;
                        };
                }
            }
        }
"@

        Add-Type $certCallback
    }
    [ServerCertificateValidationCallback]::Ignore()
    [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls"
    #endregion Workaround for SelfSigned Cert an force TLS 1.2

    foreach ($Ntnx in $Target) {
        #region System Connection
        $username = $Credential.UserName
        $password = $Credential.GetNetworkCredential().Password
        $api_v1 = "https://" + $ntnx + ":9440/PrismGateway/services/rest/v1"
        $api_v2 = "https://" + $ntnx + ":9440/PrismGateway/services/rest/v2.0"
        $auth = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($username + ":" + $password ))
        $Header = @{"Authorization" = "Basic $auth" }
        #endregion System Connection

        #region API Collections
        $NtnxAlertsConfig = Invoke-RestMethod -Method Get -Uri ($api_v2 + '/alerts/configuration/') -Headers $Header
        $NtnxAuthConfig = Invoke-RestMethod -Method Get -Uri ($api_v2 + '/authconfig/') -Headers $Header
        $NtnxContainers = (Invoke-RestMethod -Method Get -Uri ($api_v2 + '/storage_containers/') -Headers $Header).entities
        $NtnxDatastores = Invoke-RestMethod -Method Get -Uri ($api_v2 + '/storage_containers/datastores/') -Headers $Header
        $NtnxCluster = Invoke-RestMethod -Method Get -Uri ($api_v2 + '/cluster/') -Headers $Header
        $NtnxWitness = Invoke-RestMethod -Method Get -Uri ($api_v2 + '/cluster/metro_witness/') -Headers $Header
        $NtnxSmtpConfig = Invoke-RestMethod -Method Get -Uri ($api_v2 + '/cluster/smtp/') -Headers $Header
        $NtnxSnmpConfig = Invoke-RestMethod -Method Get -Uri ($api_v2 + '/snmp/') -Headers $Header
        $NtnxFtStatus = Invoke-RestMethod -Method Get -Uri ($api_v2 + '/cluster/domain_fault_tolerance_status/') -Headers $Header
        $NtnxDisks = (Invoke-RestMethod -Method Get -Uri ($api_v2 + '/disks/') -Headers $Header).entities
        $NtnxHosts = (Invoke-RestMethod -Method Get -Uri ($api_v2 + '/hosts/') -Headers $Header).entities
        $NtnxNetworks = (Invoke-RestMethod -Method Get -Uri ($api_v2 + '/networks/') -Headers $Header).entities
        $NtnxStoragePools = (Invoke-RestMethod -Method Get -Uri ($api_v1 + '/storage_pools/') -Headers $Header).entities
        $NtnxVMs = (Invoke-RestMethod -Method Get -Uri ($api_v1 + '/vms/') -Headers $Header).entities
        $NtnxCVMs = (Invoke-RestMethod -Method Get -Uri ($api_v1 + '/vms/') -Headers $Header).entities | Where-Object { $_.controllerVm }
        $NtnxNfsWhitelist = Invoke-RestMethod -Method Get -Uri ($api_v2 + '/cluster/nfs_whitelist/') -Headers $Header
        $NtnxHealthChecks = Invoke-RestMethod -Method Get -Uri ($api_v2 + '/health_checks/') -Headers $Header
        $NtnxLicense = Invoke-RestMethod -Method Get -Uri ($api_v1 + '/license/') -Headers $Header
        $NtnxProtectionDomains = (Invoke-RestMethod -Method Get -Uri ($api_v2 + '/protection_domains/') -Headers $Header).entities
        $NtnxPDReplications = (Invoke-RestMethod -Method Get -Uri ($api_v2 + '/protection_domains/replications/') -Headers $Header).entities
        $NtnxUnprotectedVMs = (Invoke-RestMethod -Method Get -Uri ($api_v2 + '/protection_domains/unprotected_vms/') -Headers $Header).entities
        $NtnxRemoteSites = Invoke-RestMethod -Method Get -Uri ($api_v1 + '/remote_sites/') -Headers $Header
        $NtnxDrSnapshots = (Invoke-RestMethod -Method Get -Uri ($api_v2 + '/remote_sites/dr_snapshots/') -Headers $Header).entities
        #endregion API Collections

        Section -Style Heading1 $NtnxCluster.name {
            #region Cluster Section
            if ($InfoLevel.Cluster -gt 0) {
                if ($NtnxCluster) { 
                    Section -Style Heading2 'Cluster' {
                        #region Hardware
                        Section -Style Heading3 'Hardware' {
                            $NtnxFtDomainStatus = $NtnxFtStatus | Where-Object { $_.domain_type -eq $NtnxCluster.fault_tolerance_domain_type }
                            $ClusterSummary = [PSCustomObject]@{
                                'Cluster Name' = $NtnxCluster.Name 
                                'Storage Type' = Switch ($NtnxCluster.storage_type) {
                                    'all_flash' { 'All Flash' }
                                    'all_hdd' { 'All HDD' }
                                    'mixed' { 'Hybrid' }
                                } 
                                'Hypervisor Types' = ($NtnxCluster.hypervisor_types).TrimStart('k').Replace('Kvm', 'AHV').Replace('VMware', 'ESXi') -join ', '
                                'Number of Nodes' = $NtnxCluster.num_nodes
                                'Number of Blocks' = ($NtnxCluster.block_serials | Select-Object -Unique).count
                                'Block Serial(s)' = ($NtnxCluster.block_serials | Sort-Object) -join ', ' 
                                'Fault Tolerance Domain Type' = $TextInfo.ToTitleCase(($NtnxCluster.fault_tolerance_domain_type.ToLower()))
                                'Data Resiliency Status' = if ($NtnxFtDomainStatus.component_fault_tolerance_status.static_configuration.number_of_failures_tolerable -gt 0) {
                                    "OK"
                                } else {
                                    "Critical"
                                }
                                "Desired Redundancy Factor" = "RF $($NtnxCluster.cluster_redundancy_state.desired_redundancy_factor)"
                                'Version' = $NtnxCluster.version 
                                'NCC Version' = ($NtnxCluster.ncc_version).TrimStart("ncc-") 
                                'Timezone' = $NtnxCluster.timezone
                            }
                            if ($Healthcheck.Cluster.Timezone) {
                                $ClusterSummary | Where-Object { $_.'Timezone' -ne $Healthcheck.Cluster.Timezone } | Set-Style -Style Critical -Property 'Timezone'
                            }
                            if ($Healthcheck.Cluster.DataResiliency) {
                                $ClusterSummary | Where-Object { $_.'Data Resiliency Status' -ne 'OK' } | Set-Style -Style Critical -Property 'Data Resiliency Status'
                            }
                            $ClusterSummary | Table -List -Name 'Cluster Summary' -ColumnWidths 50, 50
                        }
                        #endregion Hardware

                        #region Network
                        Section -Style Heading3 'Network' {
                            $Network = [PSCustomObject]@{
                                'Cluster Virtual IP Address' = $NtnxCluster.cluster_external_ipaddress 
                                'iSCSI Data Services IP Address' = $NtnxCluster.cluster_external_data_services_ipaddress 
                                'External Subnet' = $NtnxCluster.external_subnet
                                'Internal Subnet' = $NtnxCluster.internal_subnet 
                                'DNS Server(s)' = $NtnxCluster.name_servers -join ', ' 
                                'NTP Server(s)' = ($NtnxCluster.ntp_servers | Sort-Object) -join ', '
                            }
                            $Network | Table -List -Name 'Network' -ColumnWidths 50, 50
                        }
                        #endregion Network

                        #region Controller VMs
                        Section -Style Heading3 'Controller VMs' {
                            $ControllerVMs = foreach ($NtnxCVM in $NtnxCVMs) {
                                [PSCustomObject]@{
                                    'Name' = $NtnxCVM.vmName 
                                    'Power State' = $TextInfo.ToTitleCase($NtnxCVM.powerState)
                                    'Host' = $NtnxCVM.hostName 
                                    'IP Address' = $NtnxCVM.ipAddresses[0] 
                                    'CPUs' = $NtnxCVM.numVCPUs 
                                    'Memory' = "$([math]::Round(($NtnxCVM.memoryCapacityinBytes) / 1GB, 2)) GB"
                                }
                            }
                            if ($Healthcheck.CVM.PowerState) {
                                $ControllerVMs | Where-Object { $_.'Power State' -ne 'on' } | Set-Style -Style Critical -Property 'Power State'
                            }
                            $ControllerVMs | Sort-Object Host | Table -Name 'Controller VMs'
                        }
                        #endregion Controller VMs
                    }
                }
            }
            #endregion Cluster Section

            #region System Section
            if ($InfoLevel.System -gt 0) {
                Section -Style Heading2 'System' {
                    #region Global Filesystem Whitelists
                    if ($NtnxNfsWhitelist) {
                        Section -Style Heading3 'Global Filesystem Whitelists' {
                            $NtnxNfsWhitelists = [PSCustomObject]@{
                                'Global Filesystem Whitelists' = $NtnxNfsWhitelist -join [Environment]::NewLine
                            }
                            $NtnxNfsWhitelists | Table -List -Name 'Filesystem Whitelists' -ColumnWidths 50, 50
                        }
                    }
                    #endregion Global Filesystem Whitelists

                    #region Authentication
                    if ($NtnxAuthConfig) {
                        Section -Style Heading3 'Authentication' {
                            $AuthConfigDirectory = [PSCustomObject]@{
                                'Name' = $NtnxAuthConfig.directory_list.name 
                                'Domain' = $NtnxAuthConfig.directory_list.domain 
                                'URL' = $NtnxAuthConfig.directory_list.directory_url 
                                'Directory Type' = $TextInfo.ToTitleCase(($NtnxAuthConfig.directory_list.directory_type).ToLower())
                                'Connection Type' = $NtnxAuthConfig.directory_list.connection_type 
                                'Group Search Type' = $TextInfo.ToTitleCase(($NtnxAuthConfig.directory_list.group_search_type).ToLower())
                            }
                            $AuthConfigDirectory | Table -List -Name 'Authentication' -ColumnWidths 50, 50
                        }
                    }
                    #endregion Authentication

                    #region SMTP
                    if ($NtnxSmtpConfig) {
                        Section -Style Heading3 'SMTP Server' {
                            $SmtpConfig = [PSCustomObject]@{
                                'Address' = $NtnxSmtpConfig.address 
                                'Port' = $NtnxSmtpConfig.port 
                                'Username' = Switch ($NtnxSmtpConfig.username) {
                                    $null { "None" }
                                    default { $NtnxSmtpConfig.username }
                                }
                                'Password' = Switch ($NtnxSmtpConfig.password) {
                                    $null { "None" }
                                    default { $NtnxSmtpConfig.password }
                                }
                                'Secure Mode' = $TextInfo.ToTitleCase(($NtnxSmtpConfig.secure_mode).ToLower())
                                'From Email Address' = $NtnxSmtpConfig.from_email_address
                            }
                            $SmtpConfig | Table -List -Name 'SMTP Server' -ColumnWidths 50, 50
                        }
                    }
                    #endregion SMTP

                    #region Alerts Configuration
                    if ($NtnxAlertConfig) {
                        Section -Style Heading3 'Alert Email Configuration' {
                            $AlertConfig = [PSCustomObject]@{
                                'Email Every Alert' = Switch ($NtnxAlertsConfig.enable) {
                                    $true { 'yes' }
                                    $false { 'no' }
                                } 
                                'Email Daily Alert' = Switch ($NtnxAlertsConfig.enable_email_digest) {
                                    $true { 'yes' }
                                    $false { 'no' }
                                } 
                                'Nutanix Support Email' = $NtnxAlertsConfig.default_nutanix_email 
                                'Additional Email Recipients' = $NtnxAlertsConfig.email_contact_list -join ', '                         
                            }
                            $AlertConfig | Table -List -Name 'Alert Email Configuration' -ColumnWidths 50, 50
                        }
                    }
                    #endregion Alerts Configuration

                    #region SNMP Configuration
                    if ($NtnxSnmpConfig) {
                        Section -Style Heading3 'SNMP Configuration' {
                            $SnmpConfig = [PSCustomObject]@{
                                'Enabled' = $NtnxSnmpConfig.enabled               
                                'Transports' = $NtnxSnmpConfig.snmp_transports -join ','
                                'Users' = $NtnxSnmpConfig.snmp_users -join ','
                                'Traps' = $NtnxSnmpConfig.snmp_traps -join ','       
                            }
                            $SnmpConfig | Table -List -Name 'SNMP Configuration' -ColumnWidths 50, 50
                        }
                    }
                    #endregion SNMP Configuration

                    #region Syslog Configuration
                    if ($NtnxSyslogConfig) {
                        Section -Style Heading3 'Syslog Configuration' {
                            # ToDo: Syslog Configuration
                        }
                    }
                    #endregion Syslog Configuration
                    
                    #region Licensing
                    if ($NtnxLicense) {
                        Section -Style Heading3 'Licensing' {
                            $Licensing = [PSCustomObject]@{
                                'Cluster' = $NtnxCluster.name 
                                'License' = $NtnxLicense.category
                            }
                            if ($Healthcheck.System.License) {
                                $Licensing | Where-Object { $_.'License' -ne $Healthcheck.System.License } | Set-Style -Style Warning -Property 'License'
                            }
                            $Licensing | Table -Name 'Licensing' -ColumnWidths 50, 50

                            if ($InfoLevel.System -gt 2) {
                                BlankLine
                                #region Licensing Features
                                Section -Style Heading4 'Features' {
                                    $NtnxLicenseAllowanceMap = $NtnxLicense.allowanceMap
                                    foreach ($NtnxLicenseType in $NtnxLicenseAllowanceMap[0].PSObject.Properties) {
                                        Set-Variable -Name ('__{0}' -f $NtnxLicenseType.Name) -Value ($NtnxLicenseAllowanceMap | Select-Object -ExpandProperty $($NtnxLicenseType.Name))
                                    }
                                    
                                    $NtnxLicenseValues = Get-Variable -Name '__*'
                                    $LicensingFeatures = foreach ($NtnxLicenseValue in $NtnxLicenseValues.value) {
                                        [PSCustomObject]@{
                                            'Feature' = $NtnxLicenseValue.displayname
                                            'Permitted' = Switch ($NtnxLicenseValue.allowancesType) {
                                                'BOOLEAN' {
                                                    Switch ($NtnxLicenseValue.BoolValue.BoolValue) {
                                                        $true { 'Yes' }
                                                        $false { 'No' }
                                                    } 
                                                }
                                                'INTEGER_LIST' { ($NtnxLicenseValue.intValues).intValue }
                                            }
                                        }
                                    }
                                    $LicensingFeatures | Sort-Object 'Feature' | Table -Name 'Licensing Features' -ColumnWidths 50, 50
                                }
                                #endregion Licensing Features
                            }
                        }
                    }
                    #endregion Licensing
                }
            }
            #endregion System Section
            
            #region Hosts Section
            if (($InfoLevel.Hosts -gt 0) -and ($NtnxHosts)) {             
                Section -Style Heading2 'Hosts' {
                    #region Host Hardware Summary
                    if ($InfoLevel.Hosts -eq 1) {
                        Section -Style Heading3 'Hardware Summary' {
                            $NtnxHostSummary = [PSCustomObject]@{
                                'Hosts' = ($NtnxHosts | Where-Object { $_.Serial | Select-Object -Unique }).Count
                                'Blocks' = ($NtnxHosts | Where-Object { $_.Block_Serial | Select-Object -Unique }).Count
                                'Total CPU GHz' = [math]::Round(($NtnxHosts | Measure-Object -Property 'cpu_capacity_in_hz' -Sum).Sum / 1000000000, 1)
                                'Total Memory GiB' = [math]::Round(($NtnxHosts | Measure-Object -Property 'memory_capacity_in_bytes' -Sum).Sum / 1073741824, 2)
                                #ToDo: Total # Disks (SSD/HDD)
                                #ToDo: # of Network Switches
                            }
                            $NtnxHostSummary | Table -Name 'Hardware Summary' -ColumnWidths 25, 25, 25, 25
                        }
                    }
                    #endregion Host Hardware Summary
                    
                    #region Host Hardware Detailed
                    if ($InfoLevel.Hosts -ge 2) {
                        #region NtnxHost ForEach Loop
                        foreach ($NtnxHost in $NtnxHosts) {
                            #region Host Information
                            Section -Style Heading3 $NtnxHost.Name {
                                #region Host Hardware
                                Section -Style Heading4 'Hardware' {
                                    $NtnxHostConfig = [PSCustomObject]@{
                                        'Host Name' = $NtnxHost.name
                                        'Host Type' = $TextInfo.ToTitleCase(($NtnxHost.host_type).ToLower())
                                        'Node Serial' = $NtnxHost.serial 
                                        'Block Serial' = $NtnxHost.block_serial 
                                        'Block Model' = $NtnxHost.block_model_name 
                                        #'BMC Version' = $NtnxHost.bmc_version
                                        #'BIOS Version' = $NtnxHost.bios_version
                                        'Storage Capacity' = "$([math]::Round(($NtnxHost.usage_stats.'storage.capacity_bytes') / 1099511627776, 2)) TiB"
                                        'Memory' = "$([math]::Round(($NtnxHost.memory_capacity_in_bytes) / 1073741824, 2)) GiB"
                                        'CPU Capacity' = "$([math]::Round(($NtnxHost.cpu_capacity_in_hz) / 1000000000, 1)) GHz"
                                        'CPU Model' = $NtnxHost.cpu_model
                                        'No. of CPU Cores' = $NtnxHost.num_cpu_cores
                                        'No. of Sockets' = $NtnxHost.num_cpu_sockets
                                        #ToDo: 'No. of Disks'
                                        #ToDo: 'No. of NICs'
                                        'No. of VMs' = $NtnxHost.num_vms
                                        'Oplog Disk %' = "$($NtnxHost.oplog_disk_pct) %"
                                        'Oplog Disk Size' = "$([math]::Round(($NtnxHost.oplog_disk_size) / 1073741824, 1)) GiB"
                                        'Monitored' = $NtnxHost.monitored
                                        'Hypervisor' = $NtnxHost.hypervisor_full_name
                                        #ToDo: 'Datastores'
                                    }
                                    $NtnxHostConfig | Table -List -Name 'Host Hardware Specifications' -ColumnWidths 50, 50
                                }
                                #endregion Host Hardware

                                #region Host Network
                                Section -Style Heading4 'Network' {
                                    $NtnxHostNetworks = [PSCustomObject]@{
                                        'Hypervisor IP Address' = $NtnxHost.hypervisor_address 
                                        'CVM IP Address' = $NtnxHost.service_vmexternal_ip 
                                        'IPMI IP Address' = $NtnxHost.ipmi_address
                                    }
                                    $NtnxHostNetworks | Table -Name 'Host Network Specifications'
                                }
                                #endregion Host Network

                                #region Host Disks
                                if ($NtnxDisks) {
                                    Section -Style Heading5 'Disks' {
                                        $NtnxDisks = $NtnxDisks | Where-Object { $_.cvm_ip_address -eq $NtnxHost.service_vmexternal_ip } | Sort-Object 'Location'
                                        $NtnxHostDisks = foreach ($NtnxDisk in $NtnxDisks) {
                                            [PSCustomObject]@{
                                                'Location' = $NtnxDisk.location
                                                'Disk ID' = (($NtnxDisk.id) -split ('::'))[1]
                                                'Serial Number' = $NtnxDisk.disk_hardware_config.serial_number
                                                'Vendor' = $NtnxDisk.disk_hardware_config.vendor
                                                'Model' = $NtnxDisk.disk_hardware_config.model
                                                'Firmware' = $NtnxDisk.disk_hardware_config.current_firmware_version
                                                'Storage Tier' = $NtnxDisk.storage_tier_name
                                                'Host Name' = $NtnxHost.name
                                                'Hypervisor' = $NtnxDisk.host_name
                                                'Used (Physical)' = "$([math]::Round(($NtnxDisk.usage_stats.'storage.usage_bytes') / 1073741824, 2)) GiB"
                                                'Capacity (Logical)' = "$([math]::Round(($NtnxDisk.disk_size) / 1099511627776, 2)) TiB"
                                                'Self Encryption Drive' = Switch ($NtnxDisk.self_encrypting_drive) {
                                                    $true { 'Present' }
                                                    $false { 'Not Present' } 
                                                }
                                                'Status' = $TextInfo.ToTitleCase(($NtnxDisk.disk_status).ToLower())
                                                'Mode' = Switch ($NtnxDisk.online) {
                                                    $true { 'Online' }
                                                    $false { 'Offline' }  
                                                }
                                            }
                                        }
                                        if ($Healthcheck.Hardware.DiskStatus) {
                                            $NtnxHostDisks | Where-Object { $_.'Status' -ne 'normal' } | Set-Style -Style Critical -Property 'Status'
                                        }
                                        if ($Healthcheck.Hardware.DiskMode) {
                                            $NtnxHostDisks | Where-Object { $_.'Mode' -ne 'Online' } | Set-Style -Style Critical -Property 'Mode'
                                        }   
                                        if ($InfoLevel.Hosts -gt 2) {
                                            foreach ($NtnxHostDisk in $NtnxHostDisks) {
                                                Section -Style Heading5 "Disk $($NtnxHostDisk.Location)" {
                                                    $NtnxHostDisk | Table -List -Name "Host Disk $($NtnxHostDisk.Location) Specifications" -ColumnWidths 50, 50
                                                }
                                            }
                                        } else {
                                            $NtnxHostDisks | Table -Name 'Host Disk Specifications' -Columns 'Location', 'Disk ID', 'Serial Number', 'Firmware', 'Storage Tier', 'Capacity (Logical)', 'Status', 'Mode'
                                        }
                                    }
                                }
                                #endregion Host Disks

                                <#
                                #region Host Datastores
                                if (($NtnxDatastores) -and ($NtnxCluster.hypervisor_types -eq 'kVMware')) {
                                    Section -Style Heading4 'Datastores' {
                                        $NtnxDatastores = $NtnxDatastores | Where-Object { $_.host_uuid -eq $NtnxHost.uuid }
                                        $NtnxHostDatastores = foreach ($NtnxHostDatastore in $NtnxDatastores) {
                                            [PSCustomObject]@{
                                                'Datastore' = $NtnxHostDatastore.datastore_name
                                                'Container' = $NtnxHostDatastore.storage_container_name
                                                'Free Capacity TiB' = [math]::Round(($NtnxHostDatastore.free_space) / 1099511627776, 2)
                                                'Used Capacity TiB' = [math]::Round((($NtnxHostDatastore.capacity) - ($NtnxHostDatastore.free_space)) / 1099511627776, 2)
                                                'Maximum Capacity TiB' = [math]::Round(($NtnxHostDatastore.capacity) / 1099511627776, 2)
                                                'VMs' = ($NtnxHostDatastore.vm_names).Count
                                            }
                                        }
                                        $NtnxHostDatastores | Sort-Object 'Datastore' | Table -Name 'Host Datastores'
                                    }
                                }
                                #endregion Host Datastores
                                #>

                            }
                            #endregion Host Information
                        }
                        #endregion NtnxHost ForEach Loop
                    }
                    #endregion Host Hardware Detailed
                }               
            }
            #endregion Hosts Section

            #region Storage Section
            if ($InfoLevel.Storage -gt 0) {
                Section -Style Heading2 'Storage' {
                    #region Storage Pools
                    if ($NtnxStoragePools) {
                        Section -Style Heading3 'Storage Pools' {
                            $StoragePools = foreach ($NtnxStoragePool in $NtnxStoragePools) {
                                [PSCustomObject]@{
                                    'Storage Pool' = $NtnxStoragePool.name
                                    'Disks' = ($NtnxStoragePool.disks).count 
                                    'Free Capacity TiB' = [math]::Round((($NtnxStoragePool.capacity) - ($NtnxStoragePool.usageStats.'storage.disk_physical_usage_bytes')) / 1099511627776, 2)
                                    'Used Capacity TiB' = [math]::Round(($NtnxStoragePool.usageStats.'storage.disk_physical_usage_bytes') / 1099511627776, 2)
                                    'Maximum Capacity TiB' = [math]::Round(($NtnxStoragePool.capacity) / 1099511627776, 2)
                                } 
                            }
                            $StoragePools | Sort-Object 'Storage Pool' | Table -Name 'Storage Pools'
                        }
                    }
                    #endregion Storage Pools

                    #region Containers
                    if ($NtnxContainers) {
                        Section -Style Heading3 'Containers' {
                            $Containers = foreach ($NtnxContainer in ($NtnxContainers | Sort-Object Name)) {
                                [PSCustomObject]@{
                                    'Container' = $NtnxContainer.name 
                                    'Replication Factor' = "RF $($NtnxContainer.replication_factor)"
                                    #ToDo: 'Protection Domain'
                                    #ToDo: 'Datastore'
                                    'Compression' = Switch ($NtnxContainer.compression_enabled) {
                                        $true { 'On' }
                                        $false { 'Off' }
                                    }
                                    'Compression Delay' = Switch ($NtnxContainer.compression_delay_in_secs) {
                                        $null { '' }
                                        default { "$(($NtnxContainer.compression_delay_in_secs)*60) mins" }
                                    }
                                    'Cache Deduplication' = $TextInfo.ToTitleCase($NtnxContainer.finger_print_on_write)
                                    'Capacity Deduplication' = $TextInfo.ToTitleCase(($NtnxContainer.on_disk_dedup).ToLower())
                                    'Erasure Coding' = $TextInfo.ToTitleCase($NtnxContainer.erasure_code)
                                    'Free Capacity (Logical) TiB' = [math]::Round(($NtnxContainer.usage_stats.'storage.user_unreserved_free_bytes') / 1099511627776, 2)
                                    'Used Capacity TiB' = [math]::Round(((($NtnxContainer.usage_stats.'storage.user_capacity_bytes') - ($NtnxContainer.usage_stats.'storage.reserved_capacity_bytes')) - ($NtnxContainer.usage_stats.'storage.user_unreserved_free_bytes')) / 1099511627776, 2)
                                    'Maximum Capacity TiB' = [math]::Round((($NtnxContainer.usage_stats.'storage.user_capacity_bytes') - ($NtnxContainer.usage_stats.'storage.reserved_capacity_bytes')) / 1099511627776, 2)
                                    #ToDo: 'Reserved Capacity'
                                    'Advertised Capacity TiB' = [math]::Round(($NtnxContainer.advertised_capacity) / 1099511627776, 2)
                                    #ToDo: 'Data Reduction Ratio'
                                    #ToDo: 'Data Reduction Savings'
                                    #ToDo: 'Effective Free'
                                    #ToDo: 'Overall Efficiency'
                                    'Filesystem Whitelists Inherited' = $NtnxContainer.nfs_whitelist_inherited
                                    'Filesystem Whitelists' = $NtnxContainer.nfs_whitelist -join ', '
                                }
                            }
                            if ($Healthcheck.Storage.Compression) {
                                $Containers | Where-Object { $_.'Compression' -ne 'on' } | Set-Style -Style Warning -Property 'Compression'
                            }
                            if ($Healthcheck.Storage.CacheDedupe) {
                                $Containers | Where-Object { $_.'Cache Deduplication' -ne 'on' } | Set-Style -Style Warning -Property 'Cache Deduplication'
                            }
                            if ($Healthcheck.Storage.CapacityDedupe) {
                                $Containers | Where-Object { $_.'Capacity Deduplication' -ne 'on' } | Set-Style -Style Warning -Property 'Capacity Deduplication'
                            }
                            if ($Healthcheck.Storage.ErasureCoding) {
                                $Containers | Where-Object { $_.'Erasure Coding' -ne 'on' } | Set-Style -Style Warning -Property 'Erasure Coding'
                            }
                            if ($InfoLevel.Storage -gt 2) {
                                foreach ($container in $Containers) {
                                    Section -Style Heading4 "$($Container.Container)" {
                                        $Container | Table -List -Name 'Containers' -ColumnWidths 50, 50
                                    }
                                }
                            } else {
                                $Containers | Table -Name 'Containers' -Columns 'Container' , 'Replication Factor', 'Compression', 'Cache Deduplication', 'Capacity Deduplication', 'Erasure Coding', 'Free Capacity (Logical) TiB', 'Used Capacity TiB', 'Maximum Capacity TiB' 
                            }
                        }
                    }
                    #endregion Containers
                    <#
                    #region Datastores
                    if (($NtnxDatastores) -and ($NtnxCluster.hypervisor_types -eq 'kVMware')) {
                        Section -Style Heading3 'Datastores' {
                            $Datastores = foreach ($NtnxDatastore in $NtnxDatastores) {
                                [PSCustomObject]@{
                                    'Datastore' = $NtnxDatastore.datastore_name
                                    'Container' = $NtnxDatastore.storage_container_name
                                    'Free Capacity TiB' = [math]::Round(($NtnxDatastore.free_space) / 1099511627776, 2)
                                    'Used Capacity TiB' = [math]::Round((($NtnxDatastore.capacity) - ($NtnxDatastore.free_space)) / 1099511627776, 2)
                                    'Maximum Capacity TiB' = [math]::Round(($NtnxDatastore.capacity) / 1099511627776, 2)
                                    'VMs' = ($NtnxDatastore.vm_names).Count
                                }
                            }
                            $Datastores | Sort-Object 'Datastore' | Table -Name 'NFS Datastores'
                        }
                    }
                    #endregion Datastores
                    #>

                }
            }
            #endregion Storage Section

            #region Virtual Machines Section
            if (($InfoLevel.VM -gt 0) -and ($NtnxVMs)) {
                # Excludes CVMs and VMs not running on a container
                $NtnxVirtualMachines = $NtnxVMs | Where-Object { ($_.controllervm -eq $false) -and ($_.runningOnNdfs -eq $true) } | Sort-Object vmName
                Section -Style Heading2 'Virtual Machines' {
                    $NtnxVMConfigs = foreach ($NtnxVM in $NtnxVirtualMachines) {
                        [PSCustomObject]@{
                            'VM' = $NtnxVM.vmName 
                            'Power State' = $TextInfo.ToTitleCase($NtnxVM.powerState) 
                            'Operating System' = $NtnxVM.guestOperatingSystem 
                            'IP Addresses' = $NtnxVM.ipAddresses -join ', '
                            'vCPUs' = $NtnxVM.numVCpus
                            'Memory' = "$([math]::Round(($NtnxVM.memoryCapacityInBytes) / 1GB, 0)) GB" 
                            'NICs' = $NtnxVM.numNetworkAdapters 
                            'Disk Capacity' = "$([math]::Round(($NtnxVM.diskCapacityinBytes) / 1GB, 2)) GB"
                            'Host' = $NtnxVM.hostName
                        }
                    }
                    if ($Healthcheck.VM.PowerState) {
                        $NtnxVMConfigs | Where-Object { $_.'Power State' -eq 'off' } | Set-Style -Style Warning -Property 'Power State'
                    }
                    if ($InfoLevel.VM -gt 2) {
                        foreach ($NtnxVMConfig in $NtnxVMConfigs) {
                            Section -Style Heading3 "$($NtnxVMConfig.VM)" {
                                $NtnxVMConfig | Table -List -Name "$($NtnxVMConfig.VM)" -ColumnWidths 50, 50
                            }
                        }
                    } else {
                        $NtnxVMConfigs | Table -Name 'Virtual Machines' -Columns 'VM', 'Power State', 'vCPUs', 'Memory', 'Disk Capacity'
                    }
                }
            }
            #endregion Virtual Machines Section

            #region Data Protection Section
            if (($InfoLevel.DataProtection -gt 0) -and ($NtnxProtectionDomains -or $NtnxRemoteSites)) {
                Section -Style Heading2 'Data Protection' {
                    #region Protection Domains
                    if ($NtnxProtectionDomains) {
                        Section -Style Heading3 'Protection Domains' {
                            $ProtectionDomains = foreach ($NtnxProtectionDomain in $NtnxProtectionDomains) {
                                [PSCustomObject]@{
                                    'Name' = $NtnxProtectionDomain.name 
                                    'Active' = $NtnxProtectionDomain.active 
                                    'Remote Site(s)' = $NtnxProtectionDomain.replication_links.remote_site_name 
                                    'Pending Replications' = $NtnxProtectionDomain.pending_replication_count 
                                    'Ongoing Replications' = $NtnxProtectionDomain.ongoing_replication_count 
                                    'Written Bytes' = $NtnxProtectionDomain.total_user_written_bytes     
                                }
                            }
                            $ProtectionDomains | Sort-Object 'Name' | Table -Name 'Protection Domains' 
                        }
                    }
                    #endregion Protection Domains
                    
                    #region Protection Domain Replication
                    if (($InfoLevel.DataProtection -eq 3) -and ($NtnxPDReplications)) {
                        Section -Style Heading3 'Protection Domain Replication' {
                            $ProtectionDomainReplications = foreach ($NtnxPDReplication in $NtnxPDReplications) {
                                [PSCustomObject]@{
                                    'Name' = $NtnxPDReplication.protection_domain_name 
                                    'Remote Sites' = $NtnxPDReplication.remote_site_name -join ', '
                                    'Snapshot ID' = $NtnxPDReplication.snapshot_id 
                                    'Data Completed' = "$([math]::Round(($NtnxPDReplication.completed_bytes) / 1099511627776, 2)) TiB" 
                                    '% Complete' = $NtnxPDReplication.completed_percentage
                                    'Minutes to Complete' = [math]::Round(($NtnxPDReplication.replication_time_to_complete_secs) / 60, 2)
                                }
                            }
                            $ProtectionDomainReplications | Sort-Object 'Name' | Table -Name 'Protection Domain Replication' 
                        }
                    }
                    #endregion Protection Domain Replication
                    
                    #region Protection Domain Snapshots
                    if (($InfoLevel.DataProtection -eq 3) -and ($NtnxDrSnapshots)) {
                        Section -Style Heading3 'Protection Domain Snapshots' {
                            $ProtectionDomainSnapshots = foreach ($NtnxDrSnapshot in $NtnxDrSnapshots) {
                                [PSCustomObject]@{
                                    'Protection Domain' = $NtnxDrSnapshot.protection_domain_name 
                                    'State' = ($NtnxDrSnapshot.state).ToLower() 
                                    'Snapshot ID' = $NtnxDrSnapshot.snapshot_id 
                                    'Consistency Groups' = $NtnxDrSnapshot.consistency_groups -join ', '
                                    'Remote Site(s)' = $NtnxDrSnapshot.remote_site_names -join ', '
                                    'Size in Bytes' = $NtnxDrSnapshot.size_in_bytes
                                }
                            }
                            $ProtectionDomainSnapshots | Sort-Object 'Protection Domain' | Table -Name 'Protection Domain Snapshots' 
                        }
                    }
                    #endregion Protection Domain Snapshots

                    #region Unprotected VMs
                    $NtnxUnprotectedVMs = $NtnxVMs | Where-Object { ($_.controllervm -eq $false) -and ($_.runningOnNdfs -eq $false) }
                    if ($NtnxUnprotectedVMs) {
                        Section -Style Heading3 'Unprotected VMs' {
                            $UnprotectedVMs = foreach ($NtnxUnprotectedVM in $NtnxUnprotectedVMs) {
                                [PSCustomObject]@{
                                    'VM Name' = $NtnxUnprotectedVM.vmName 
                                    'Power State' = $TextInfo.ToTitleCase($NtnxUnprotectedVM.powerState)
                                    'Operating System' = $NtnxUnprotectedVM.guestOperatingSystem 
                                    'CPUs' = $NtnxUnprotectedVM.numVCPUs 
                                    'NICs' = $NtnxUnprotectedVM.numNetworkAdapters 
                                    'Disk Capacity' = "$([math]::Round(($NtnxUnprotectedVM.diskCapacityinBytes) / 1GB, 2)) GB" 
                                    'Host' = $NtnxUnprotectedVM.hostName
                                }
                            }
                            $UnprotectedVMs | Sort-Object 'VM Name' | Table -Name 'Unprotected VMs' 
                        }
                    }
                    #endregion Unprotected VMs

                    #region Remote Sites
                    if ($NtnxRemoteSites) {
                        Section -Style Heading3 'Remote Sites' {
                            $RemoteSites = foreach ($NtnxRemoteSite in $NtnxRemoteSites) {
                                [PSCustomObject]@{
                                    'Name' = $NtnxRemoteSite.name 
                                    'Capabilities' = ($TextInfo.ToTitleCase(($NtnxRemoteSite.capabilities).ToLower()) | Sort-Object) -join ', ' 
                                    'Remote Addresses' = "$(($NtnxRemoteSite.remoteIpPorts | Get-Member -MemberType NoteProperty).Name):2020"
                                    'Metro Ready' = Switch ($NtnxRemoteSite.metroReady) {
                                        $true { 'Yes' }
                                        $false { 'No' }
                                    }
                                    'Use SSH Tunnel' = Switch ($NtnxRemoteSite.sshEnabled) {
                                        $true { 'Yes' }
                                        $false { 'No' }
                                    }
                                    'Compress On Wire' = Switch ($NtnxRemoteSite.compressionEnabled) {
                                        $true { 'On' }
                                        $false { 'Off' }
                                    }
                                    'Enable Proxy' = Switch ($NtnxRemoteSite.proxyEnabled) {
                                        $true { 'On' }
                                        $false { 'Off' }
                                    }
                                    'Bandwidth Throttling' = Switch ($NtnxRemoteSite.bandwidthPolicyEnabled) {
                                        $true { 'On' }
                                        $false { 'Off' }
                                    }                    
                                }
                            }
                            $RemoteSites | Sort-Object 'Name' | Table -Name 'Remote Sites' -List -ColumnWidths 50, 50
                        }
                    }
                    #endregion Remote Sites
                }
            }
            #endregion Data Protection Section
        }
    }
}