Public/AzStackHci.ClusterPerformance.ps1

# ///////////////////////////////////////////////////////////////////
# Send-ClusterPerformanceHistory function
# Used to collect and send cluster performance history to Microsoft.
# Uses Get-SddcDiagnosticInfo and Send-DiagnosticInformation functions
# ///////////////////////////////////////////////////////////////////
function Send-ClusterPerformanceHistory {
<#
    .SYNOPSIS
    Collects the SDDC diagnostic information, including the cluster performance history, and sends the diagnostic information to Microsoft.
 
    .DESCRIPTION
    This function collects the SDDC diagnostic information, including the cluster performance history, and sends the diagnostic information to Microsoft.
    The function requires the name of the cluster to collect the SDDC and Cluster Performance History.
    The function also requires the time frame for the performance history, the root path to store the output files.
    There is an optional switch to ignore disk space check for log collection and an optional switch to exclude cluster performance history from the diagnostic information.
     
    .PARAMETER ClusterName
    The name of the cluster to collect SDDC and Cluster Performance History.
 
    .PARAMETER PerformanceHistoryTimeFrame
    The time frame for the performance history. The default is LastDay.
 
    .PARAMETER OutputRootPath
    The root path to store the output files. The default is C:\Temp, if not specified.
 
    .PARAMETER IgnoreDiskSpaceCheck
    Optional switch, used to ignore disk space check for log collection.
 
    .PARAMETER ExcludeClusterPerformanceHistory
    Optional switch, to exclude cluster performance history from the diagnostic information.
 
    .EXAMPLE
    Send-ClusterPerformanceHistory -ClusterName "Cluster-01" -OutputRootPath "C:\Temp"
 
    This example collects the SDDC diagnostic information, including the cluster performance history, for the cluster named "Cluster-01".
    The performance history time frame defaults to "LastDay", as the parameter was not specified.
    The output files are stored in the "C:\Temp" folder. The disk space check is checked, to ensure there is 10GB + 5% free space before starting the log collection.
#>


    [CmdletBinding()]
    [OutputType([void])]
    param (
        # The name of the cluster to collect SDDC and Cluster Performance History
        [Parameter(Mandatory = $true, Position=0, HelpMessage="Enter the Cluster Name")]
        [ValidateScript({Get-Cluster -Name $_})]
        [string]$ClusterName,

        # The time frame for the performance history
        [Parameter(Mandatory = $false,Position=1)]
        [ValidateSet("LastHour","LastDay","LastWeek","LastMonth","LastYear")]
        # Default is LastDay
        [string]$PerformanceHistoryTimeFrame = "LastDay",

        # The root path to store the output files
        [Parameter(Mandatory = $false,Position=2)]
        [ValidateScript({
            if ($_ -match '^\\\\') { throw "UNC paths are not supported. Use a local drive letter path (e.g., C:\Temp)." }
            if ($_ -notmatch '^[A-Za-z]:\\') { throw "Path must start with a drive letter (e.g., C:\Temp)." }
            Test-Path -Path ($_.Substring(0,3))
        })] # Must be a local drive letter path, not a UNC path
        # Default is C:\Temp, if not specified
        [string]$OutputRootPath = "C:\Temp",

        # Optional switch, used to ignore disk space check for log collection
        [Parameter(Mandatory=$false,Position=3)]
        [Switch]$IgnoreDiskSpaceCheck,

        [Parameter(Mandatory = $false,Position=4)]
        [switch]$ExcludeClusterPerformanceHistory, # optional switch, to exclude cluster performance history from the diagnostic information

        [Parameter(Mandatory=$false, HelpMessage="Optional switch to prevent console output from the function.")]
        [switch]$NoOutput
    )

    begin {
        # Requires administrator permissions to run this function
        if (-not (Test-Elevation)) { throw "This function must be run as an Administrator." }
    }

    process {
        # Handle -NoOutput: suppress all console output
        if ($NoOutput.IsPresent) {
            $script:SilentMode = $true
            $VerbosePreference = 'SilentlyContinue'
            $DebugPreference = 'SilentlyContinue'
        }

        # Check path and disk space
        [uint32]$LogCollectionFileSize = 10240 # 10GB, approx log collection size
        try {
            $Disk = Get-PSDrive -ErrorAction Stop | Where-Object { $PSItem.Root -eq $OutputRootPath.Substring(0,3) }
        } catch {
            Throw "Failed to query disk drives using Get-PSDrive: $($_.Exception.Message)"
        }
        Write-Output "Target disk free space = $([math]::round($Disk.Free / 1GB,2)) GiB"
        # Add the minimum / expected disk space required for log collection, plus 5% of the total disk space
        $MinimumRequiredDiskSpace = [math]::round($LogCollectionFileSize*1Mb/1Gb + ((($disk.Free + $disk.Used)/1Gb) * 0.05),2)
        if (($Disk.Free/1Gb -lt $MinimumRequiredDiskSpace) -and (-not($IgnoreDiskSpaceCheck.IsPresent)))
        {
            Write-Output "$($OutputRootPath.Substring(0,3)) disk free space is below minimum recommended size for log collection, plus 5% of total space as reserve"
            Throw "Insufficient disk space on the drive '$($OutputRootPath.Substring(0,3))' to store the output log files. Minimum recommended disk space is $MinimumRequiredDiskSpace GiB, increase space or consider use of '-IgnoreDiskSpaceCheck' switch."
        } else {
            if($IgnoreDiskSpaceCheck.IsPresent){
                Write-Output "*** Free disk space checks have been ignored, proceeding with log collection ***"
            } else { 
                Write-Output "Info: $($OutputRootPath.Substring(0,3)) has $([math]::round($Disk.Free/1Gb,2)) free disk space, this is above minimum recommended for log collection + reserve of 5% of total disk size ($MinimumRequiredDiskSpace GiB), performing log collection...."
            }
        }    
        # Get the current date in the format yyyyMMdd, used for the output folders
        $DateFormatted = Get-Date -f "yyyyMMdd"

        if(-not(Test-Path -Path $OutputRootPath)){
            Write-Output "Creating '$OutputRootPath\ output folder"
            try {
                New-Item -Path "$OutputRootPath" -ItemType Directory -Force | Out-Null
            } catch {  
                Throw "Failed to create '$OutputRootPath' output folder $($_.Exception.Message)"
            }        
        }

        # Create a folder to store the output files
        if(-not(Test-Path -Path "$OutputRootPath\Logs")){
            Write-Output "Creating '$OutputRootPath\SDDC-Logs\$($DateFormatted)-ClusterPerf' temporary output folder"
            try {
                New-Item -Path "$OutputRootPath\SDDC-Logs\$($DateFormatted)-ClusterPerf" -ItemType Directory -Force | Out-Null
            } catch {  
                Throw "Failed to create '$OutputRootPath\SDDC-Logs\$($DateFormatted)-ClusterPerf' output folder $($_.Exception.Message)"
            }
            
            Write-Output "Creating '$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf' target folder for the output zip file"
            try {
                New-Item -Path "$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf" -ItemType Directory -Force | Out-Null
            } catch {  
                Throw "Failed to create '$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf' output folder $($_.Exception.Message)"
            }
        }

        # Get the SDDC diagnostic information, including the cluster performance history
        Write-Output "Getting the SDDC diagnostic information for cluster '$ClusterName'..."

        if($ExcludeClusterPerformanceHistory.IsPresent){ # Exclude cluster performance history from the diagnostic information

            # Exclude cluster performance history from the diagnostic information
            Write-Output "Excluding cluster performance history from the diagnostic information"
            # Get the SDDC diagnostic information, including the cluster performance history
            try {
                Get-SddcDiagnosticInfo -WriteToPath "$OutputRootPath\SDDC-Logs\$($DateFormatted)-ClusterPerf" `
                -ClusterName "$ClusterName" `
                -IncludeGetNetView `
                -ZipPrefix "$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf" `
                -IncludePerformance $true `
                -DaysOfArchive 0 `
                -ErrorAction Stop
            } catch {
                Throw "Failed to collect SDDC diagnostic information (excluding cluster performance history): $($_.Exception.Message)"
            }

        } else { # Include cluster performance history from the diagnostic information

            # Include cluster performance history from the diagnostic information
            try {
                Get-SddcDiagnosticInfo -WriteToPath "$OutputRootPath\SDDC-Logs\$($DateFormatted)-ClusterPerf" `
                -ClusterName "$ClusterName" `
                -IncludeGetNetView `
                -ZipPrefix "$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf" `
                -IncludeClusterPerformanceHistory `
                -PerformanceHistoryTimeFrame $PerformanceHistoryTimeFrame `
                -IncludePerformance $true `
                -DaysOfArchive 0 `
                -ErrorAction Stop
            } catch {
                Throw "Failed to collect SDDC diagnostic information (including cluster performance history): $($_.Exception.Message)"
            }

        }
        
        # Confirm with user before deleting the output zip file:
        try {
            $ZipFile = Get-Item -Path "$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf*.zip"
        } catch {
            Throw "Failed to get the SDDC Zip File $($_.Exception.Message)"
        }
        # Ensure only a single zip file is found:
        if($ZipFile.count -eq 1){
            Write-Output "SDDC Zip file = '$($ZipFile.FullName)'"
        } else {
            Throw "Found more than 1 x zip file using this wildcard path: '$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf*.zip'"
        }

        # Extract the zip file to the target "SDDC" folder for sending the diagnostic information to Microsoft, as this folder is shown in the ingestion process
        Write-Output "Extracting the zip file '$($ZipFile.FullName)' to '$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf\SDDC' folder"
        New-Item -ItemType Directory -Path "$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf\SDDC" -Force | Out-Null
        try {
            Expand-Archive -Path $ZipFile.FullName -DestinationPath "$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf\SDDC" -Force
        } catch {
            Throw "Failed to extract the zip file $($_.Exception.Message)"
        }
        
        # Identify the GetNetView zip files in the target "SDDC" folder as these need to be extracted for the pipeline to ingest them
        $GetNetViewZipFiles = Get-ChildItem -Path "$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf\SDDC" -Recurse -File -Include "*.zip" | Where-Object {$_.FullName -ilike '*GetNetView*' -and $_.Name -ilike 'msdbg.*'}
        foreach ($GetNetViewZipFile in $GetNetViewZipFiles) {
            Write-Output "Extracting the GetNetView zip file '$($GetNetViewZipFile.FullName)' to '$($GetNetViewZipFile.DirectoryName)' folder"
            try {
                New-Item -ItemType Directory -Path "$($GetNetViewZipFile.DirectoryName)\msdbg" -Force | Out-Null
                Expand-Archive -Path $GetNetViewZipFile.FullName -DestinationPath "$($GetNetViewZipFile.DirectoryName)\msdbg" -Force
                if (Test-Path -Path $GetNetViewZipFile.FullName) {
                    Write-Output "Removing GetNetView zip file '$($GetNetViewZipFile.FullName)' in extracted files to reduce upload data"
                    Remove-Item -Path $GetNetViewZipFile.FullName -Force
                }            
            } catch {
                Throw "Failed to extract the GetNetView zip file $($_.Exception.Message)"
            }
        }

        # Send the diagnostic information to Microsoft, use "-CollectSddc $false" parameter, as SDDC event logs are collected as part of Get-SddcDiagnosticInfo
        try {
            Write-Output "Sending the diagnostic information to Microsoft..."
            Send-DiagnosticData -CollectSddc $false -SupplementaryLogs "$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf\SDDC" -Verbose -ErrorAction Continue
        } catch {
            Throw "Failed to send the diagnostic information to Microsoft using Send-DiagnosticData $($_.Exception.Message)"
        }

        # Remove the SDDC folder, after sending the diagnostic information
        try {
            Remove-Item -Path "$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf\SDDC" -Recurse
        } catch {
            Throw "Failed to remove the '$OutputRootPath\SDDC\$($DateFormatted)-ClusterPerf\SDDC' folder $($_.Exception.Message)"
        }

        # Confirm with user before deleting the output zip file:
        if ($script:SilentMode) {
            $DeleteConfirmation = "N"
        } else {
            $DeleteConfirmation = Read-Host "Do you want to DELETE the zip file '$($ZipFile.Name)', - ($([math]::round($zipfile.Length/1MB,2)) MiB) ? (Y/N)" -ErrorAction Stop
        }
        if($DeleteConfirmation -ne "Y"){
            Write-Output "Delete zip file cancelled by user."
            return
        } else {
            Write-Output "Deleting zip file '$($ZipFile.FullName)'"
            Remove-Item -Path $ZipFile.FullName -ErrorAction Stop -Verbose
        }

    } # End of process block

    end {
        if ($NoOutput.IsPresent) { $script:SilentMode = $false }
        Write-Debug "Completed Send-ClusterPerformanceHistory function"
    }

} # End of Send-ClusterPerformanceHistory