Public/Get-NutanixVMStorageReport.ps1

function Get-NutanixVMStorageReport {

    [CmdletBinding()]
    param (
        # Updated to accept an array of Prism Central host names or IPs
        [Parameter(Mandatory = $true)]
        [String[]] $PrismCentral,
        
        [Parameter(Mandatory = $true, HelpMessage="Enter your nxUserID(Ex:adm_svemulap@na.imtn.com")]
        [String] $nxUser,
        
        [Parameter(Mandatory = $true, HelpMessage="Enter your nxPassword as SecureString")]
        [ValidateNotNullOrEmpty()]
        [SecureString]$nxPassword,

        # Added parameter to accept a targeted list of server names
        [Parameter(Mandatory = $false)]
        [String[]]$ComputerName,

        [Parameter(Mandatory = $false)]
        [String]
        $ExportPath = "C:\Temp\Logs"

    )

    # 1. FORCE TLS 1.2 FOR POWERSHELL 5.1 COMPATIBILITY
    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

    # 2. BYPASS SSL CERTIFICATE CHECKS (Replaces -SkipCertificateCheck)
    if ([System.Net.ServicePointManager]::ServerCertificateValidationCallback -eq $null) {
        [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
    }

    Write-Host "Script execution in Progress... Please wait" -ForegroundColor Yellow
    Write-Verbose "PowerShell version $($PSVersionTable.PSVersion.Major) is running with TLS 1.2 enforced."

    # Decrypt SecureString password safely
    $MyKey = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($nxPassword))
    $APIKey = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("${nxUser}:${MyKey}"))

    $Headers = @{
        "Authorization" = "Basic $APIKey"
        "Content-Type"  = "application/json"
        "Accept"        = "application/json"
    }

    # Consolidated global array to hold combined output from all provided hosts
    $MasterResponse = New-Object System.Collections.ArrayList

    # Iterate through each provided Prism Central cluster host
    foreach ($PC in $PrismCentral) {
        Write-Host "Connecting to Prism Central: $PC..." -ForegroundColor Cyan
        $baseUrl = "https://$($PC):9440/api/nutanix/v3"

        # ----------------------------------------------------
        # 1. STORAGE CONTAINERS
        # ----------------------------------------------------
        Write-Verbose "[$PC] Mapping storage containers..."
        $containerMap = @{}
        $offsetby = 0
        $pageSize = 500
        $totalContainersFound = $null

        do {
            $containerBody = @{
                entity_type             = "storage_container"
                group_member_count      = $pageSize
                group_member_offset     = $offsetby
                group_member_attributes = @(
                    @{ attribute = "container_name" }
                )
            } | ConvertTo-Json -Depth 5

            $containerResponse = Invoke-RestMethod -Uri "$baseUrl/groups" -Headers $Headers -Method Post -Body $containerBody
            
            if ($containerResponse.group_results.entity_results) {
                foreach ($sc in $containerResponse.group_results.entity_results) {
                    $uuid = $sc.entity_id
                    
                    $containerData = $sc.data | Where-Object { $_.name -eq "container_name" }
                    $name = if ($containerData.values.values) { $containerData.values.values | Select-Object -First 1 } else { $containerData.values }

                    if ($uuid -and $name) {
                        $containerMap[$uuid] = [string]$name
                    }
                }
            }

            if ($null -eq $totalContainersFound) {
                $totalContainersFound = $containerResponse.group_results.total_entity_count
            }
            
            $offsetby += $pageSize
        } while ($offsetby -lt $totalContainersFound)


        # ----------------------------------------------------
        # 2. GROUP METRICS (VM Storage Usage)
        # ----------------------------------------------------
        Write-Verbose "[$PC] Gathering storage consumption metrics via v3 groups API..."
        $groupUsageMap = @{}
        $offsetby = 0
        $totalGroupsFound = $null

        do {
            $groupsBody = @{
                entity_type             = "mh_vm"
                group_member_count      = $pageSize
                group_member_offset     = $offsetby
                group_member_attributes = @(
                    @{ attribute = "vm_name" },
                    @{ attribute = "controller_user_bytes" }
                )
            } | ConvertTo-Json -Depth 5

            $groupResponse = Invoke-RestMethod -Uri "$baseUrl/groups" -Headers $Headers -Method Post -Body $groupsBody
            
            if ($groupResponse.group_results.entity_results) {
                foreach ($groupItem in $groupResponse.group_results.entity_results) {
                    
                    $nameData = $groupItem.data | Where-Object { $_.name -eq "vm_name" }
                    $bytesData = $groupItem.data | Where-Object { $_.name -eq "controller_user_bytes" }

                    $vmNameAttr = if ($nameData.values.values) { $nameData.values.values | Select-Object -First 1 } else { $nameData.values }
                    $usedBytesAttr = if ($bytesData.values.values) { $bytesData.values.values | Select-Object -First 1 } else { $bytesData.values }

                    if ($vmNameAttr -and $usedBytesAttr) {
                        $groupUsageMap[[string]$vmNameAttr] = [int64]$usedBytesAttr
                    }
                }
            }

            if ($null -eq $totalGroupsFound) {
                $totalGroupsFound = $groupResponse.group_results.total_entity_count
            }
            
            $offsetby += $pageSize
        } while ($offsetby -lt $totalGroupsFound)


        # ----------------------------------------------------
        # 3. VMs CONFIGURATION ---> FINAL REPORT GENERATION
        # ----------------------------------------------------
        Write-Verbose "[$PC] Fetching virtual machines configuration details..."
        $offsetby = 0
        $totalVMsFound = $null

        # Pre-build filter verification matching using uppercase conversion
        $filterActive = $null -ne $ComputerName -and $ComputerName.Count -gt 0
        if ($filterActive) {
            $upperComputerList = $ComputerName | ForEach-Object { $_.ToUpper() }
        }

        do {
            $vmBody = @{
                kind   = "vm"
                offset = $offsetby
                length = $pageSize
            } | ConvertTo-Json

            $vmResponse = Invoke-RestMethod -Uri "$baseUrl/vms/list" -Headers $Headers -Method Post -Body $vmBody
            
            foreach ($vm in $vmResponse.entities) {
                $vmName = $vm.spec.name

                # Dynamic Filtering Logic
                if ($filterActive -and $upperComputerList -notcontains $vmName.ToUpper()) {
                    continue # Skip processing this VM if it isn't listed in $ComputerName
                }

                $totalSizeBytes = 0
                $containers = New-Object System.Collections.Generic.HashSet[string]
                $diskList = $vm.status.resources.disk_list
                
                if ($diskList) {
                    foreach ($disk in $diskList) {
                        if ($disk.disk_size_bytes) {
                            $totalSizeBytes += $disk.disk_size_bytes
                        }
                        
                        $scUuid = $disk.storage_config.storage_container_reference.uuid
                        $scRefName = $disk.storage_config.storage_container_reference.name
                        
                        if ($scRefName) {
                            [void]$containers.Add($scRefName)
                        } elseif ($scUuid -and $containerMap.ContainsKey($scUuid)) {
                            [void]$containers.Add($containerMap[$scUuid])
                        } elseif ($scUuid) {
                            [void]$containers.Add($scUuid)
                        }
                    }
                }

                $usedBytes = if ($groupUsageMap.ContainsKey($vmName)) { $groupUsageMap[$vmName] } else { 0 }
                
                $totalGB = [math]::Round($totalSizeBytes / 1GB, 2)
                $usedGB = [math]::Round($usedBytes / 1GB, 2)
                $freeGB = [math]::Round(($totalSizeBytes - $usedBytes) / 1GB, 2)
                $usedPct = if ($totalSizeBytes -gt 0) { [math]::Round(($usedBytes / $totalSizeBytes) * 100, 2) } else { 0 }

                $allocatedMemoryMiB = $vm.status.resources.memory_size_mib
                if ($null -eq $allocatedMemoryMiB) { $allocatedMemoryMiB = 0 }
                
                # Safe fallbacks to prevent errors if a VM is powered off and unassigned to a physical host
                $hostName = $vm.status.resources.host_reference.name
                if ($null -eq $hostName) { 
                $hostName = "N/A" 
                }
                elseif ($hostName -match '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') {
                    # If the host name is an IP address, resolve it to a short name via DNS
                    try {
                    $fqdn = [System.Net.Dns]::GetHostEntry($hostName).HostName
                    $hostName = $fqdn.Split('.')[0]
                    }
                    catch {
                        Write-Verbose "falling back to original data as name lookup failed"
                    }
                 }

                # Construct identical PSCustomObject matching your original exact property list
                [void]$MasterResponse.Add([PSCustomObject]@{
                    Nutanix_PC         = $PC
                    VMName             = $vmName
                    PowerState         = $vm.status.resources.power_state
                    HostName           = $hostName
                    MemoryGiB          = [math]::Round($allocatedMemoryMiB / 1024, 2)
                    AllocatedGB        = $totalGB
                    UsedGB             = $usedGB
                    FreeGB             = $freeGB
                    UsedPercentage     = $usedPct
                    ClusterName        = $vm.status.cluster_reference.name
                    StorageContainers  = ($containers -join ", ")
                })
            }

            if ($null -eq $totalVMsFound) {
                $totalVMsFound = $vmResponse.metadata.total_matches
            }
            
            $offsetby += $pageSize
            } while ($offsetby -lt $totalVMsFound)
        }

        # Define the output file structure with a unique timestamp
        $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
        $OutputFile = Join-Path -Path $ExportPath -ChildPath "NutanixVM_Storage_Report_$($timestamp).csv"
        
        $Report = $MasterResponse
        
        # Export the combined data to a single consolidated CSV file
        if ($Report) {
        if (-not (Test-Path $ExportPath)) {
            New-Item -ItemType Directory -Force -Path $ExportPath | Out-Null
        }
        
        $Report | Export-Csv -Path $OutputFile -NoTypeInformation
        Write-Host "Execution completed successfully! Report saved to: $OutputFile" -ForegroundColor Green
        
        # Output the report object to the pipeline in case you want to filter it dynamically
        # return $Report
        
        } else {
            Write-Warning "No virtual machine data matched your criteria."
        }
        
   }



   
<#
# Loading standalone function to Terminal if it's not part of Module
 . ./Get-NutanixVMStorageReport.ps1


# if output needs to be filtered down to contain only select list of LOB systems
$ServerList = (Get-Content "allservers.txt")

# $Secret = Read-Host -Prompt "Please enter your Secret" -AsSecureString
# $bytes = ConvertFrom-SecureString $Secret
# $bytes | out-file .\secureKey.key

# Using locally stored secret.
$secureKey = (get-content .\secureKey.key) | ConvertTo-SecureString
 


$Params = @{
    nxUser = "nxUser@domain.local"
    nxPassword = $secureKey
    ComputerName = $ServerList
   }

# Dynamically add the target list later
$Params["PrismCentral"] = @("pc01.domain.local", "pc02.domain.local")

Get-NutanixVMStorageReport @Params


#>