public/Invoke-VPASMetricsPSM.ps1

<#
.Synopsis
   RUN VARIOUS PSM METRICS FROM CYBERARK
   CREATED BY: Vadim Melamed, EMAIL: vpasmodule@gmail.com
.DESCRIPTION
   USE THIS FUNCTION TO GENERATE VARIOUS PSM RELATED METRICS FROM CYBERARK
.LINK
   https://vpasmodule.com/commands/Invoke-VPASMetricsPSM
.PARAMETER token
   HashTable of data containing various pieces of login information (PVWA, LoginToken, HeaderType, etc).
   If -token is not passed, function will use last known hashtable generated by New-VPASToken
.PARAMETER TargetMetric
   Specify which report will be run
   Possible values: PSMSessionsInXDays, PSMUtilizationForXDays, PSMConnectionComponentsInXDays, UsersConnectingWithPSMInXDays
.PARAMETER MetricFormat
   Specify the report output format
   NONE will return the generated hashtable of data that can be assigned to a variable
   Possible values: JSON, HTML, ALL, NONE
.PARAMETER OutputDirectory
   Specify where the location for report output to be saved
.PARAMETER DayRange
   Specify the date range for the selected metric report
.PARAMETER AmtOfSets
   Specify the length of historic data to be included in the metric report
.PARAMETER AmtOfUsers
   Specify the amount of users to be included in the metric
.PARAMETER HTMLChart
   Specify the HTML report type
   Possible values: BarGraph, LineGraph, PieChart, ALL
.PARAMETER HideRawData
   Removes the RawData visual from the exported output
   Helpful when exporting to a PDF or document to remove extra not needed information
.PARAMETER IgnorePlatforms
   Wildcard value that will cause a record to be ignored from the metrics if the target record PlatformID matches
.PARAMETER IgnoreUsernames
   Wildcard value that will cause a record to be ignored from the metrics if the target record Username matches
.PARAMETER PlatformSearchQuery
   Wildcard value that will limit the metrics to only target records that match the searchquery via platformID
.PARAMETER UsernameSearchQuery
   Wildcard value that will limit the metrics to only target records that match the searchquery via account username
.PARAMETER InputParameters
   HashTable of values containing the parameters required to make the API call
.EXAMPLE
   $GenerateReport = Invoke-VPASMetricsPSM -TargetMetric PSMConnectionComponentsInXDays -MetricFormat ALL -OutputDirectory C:\Temp\Metrics\PSMMetrics -HTMLChart ALL -DayRange 30
.EXAMPLE
   $GenerateReport = Invoke-VPASMetricsPSM -TargetMetric PSMSessionsInXDays -MetricFormat ALL -OutputDirectory C:\Temp\Metrics\PSMMetrics -HTMLChart ALL -DayRange 7 -AmtOfSets 4
.EXAMPLE
   $GenerateReport = Invoke-VPASMetricsPSM -TargetMetric PSMUtilizationForXDays -MetricFormat ALL -OutputDirectory C:\Temp\Metrics\PSMMetrics -HTMLChart ALL -DayRange 30
.EXAMPLE
   $GenerateReport = Invoke-VPASMetricsPSM -TargetMetric UsersConnectingWithPSMInXDays -MetricFormat ALL -OutputDirectory C:\Temp\Metrics\PSMMetrics -HTMLChart ALL -DayRange 30 -AmtOfUsers 10
.EXAMPLE
   $InputParameters = @{
        TargetMetric = "PSMSessionsInXDays"|"PSMUtilizationForXDays"|"PSMConnectionComponentsInXDays"|"UsersConnectingWithPSMInXDays"
        MetricFormat = "JSON"|"HTML"|"ALL"|"NONE"
        OutputDirectory = "C:\temp\ReportOutputs"
        HTMLChart = "BarGraph"|"LineGraph"|"PieChart"|"ALL"
        DayRange = "7"
        AmtOfSets = "4"
        HideRawData = $true|$false
        AmtOfUsers = "5"
        IgnorePlatforms = @("WinDomain","WinLocal")
        IgnoreUsernames = @("IgnoreUsername1")
        PlatformSearchQuery = @("norotate","store")
        UsernameSearchQuery = @("TargetUser1","TargetUser3")
   }
   $GenerateReport = Invoke-VPASMetricsPSM -InputParameters $InputParameters
.OUTPUTS
   HashTable object if successful
   ---
   $false if failed
#>

function Invoke-VPASMetricsPSM{
    [OutputType([bool])]
    [CmdletBinding(DefaultParameterSetName='Set1')]
    Param(

        [Parameter(Mandatory=$true,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true,HelpMessage="Enter TargetMetric to be generated (PSMSessionsInXDays,PSMUtilizationForXDays,PSMConnectionComponentsInXDays,UsersConnectingWithPSMInXDays)")]
        [ValidateSet('PSMSessionsInXDays','PSMUtilizationForXDays','PSMConnectionComponentsInXDays','UsersConnectingWithPSMInXDays')]
        [String]$TargetMetric,

        [Parameter(Mandatory=$true,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true,HelpMessage="Enter ReportOutput type (JSON, HTML, ALL, NONE)")]
        [ValidateSet('JSON','HTML','ALL','NONE')]
        [String]$MetricFormat,

        [Parameter(Mandatory=$false,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true)]
        [String]$OutputDirectory,

        [Parameter(Mandatory=$true,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true,HelpMessage="Specify the date range for the selected metric report")]
        [String]$DayRange,

        [Parameter(Mandatory=$false,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true)]
        [String]$AmtOfSets,

        [Parameter(Mandatory=$false,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true)]
        [ValidateSet('BarGraph','LineGraph','PieChart','ALL')]
        [String]$HTMLChart,

        [Parameter(Mandatory=$false,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true)]
        [String]$AmtOfUsers,

        [Parameter(Mandatory=$false,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true)]
        [switch]$HideRawData,

        [Parameter(Mandatory=$false,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true)]
        [String[]]$IgnorePlatforms,

        [Parameter(Mandatory=$false,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true)]
        [String[]]$IgnoreUsernames,

        [Parameter(Mandatory=$false,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true)]
        [String[]]$PlatformSearchQuery,

        [Parameter(Mandatory=$false,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true)]
        [String[]]$UsernameSearchQuery,

        [Parameter(Mandatory=$true,ParameterSetName='InputParameters',ValueFromPipelineByPropertyName=$true,HelpMessage="Hashtable of parameters required to make API call, refer to get-help -examples for valid inputs")]
        [hashtable]$InputParameters,

        [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]
        [hashtable]$token
    )

    Begin{
        $tokenval,$sessionval,$PVWA,$Header,$ISPSS,$IdentityURL,$EnableTextRecorder,$AuditTimeStamp,$NoSSL,$VaultVersion,$HideWarnings,$AuthenticatedAs,$SubDomain,$EnableTroubleshooting = Get-VPASSession -token $token
        $CommandName = $MyInvocation.MyCommand.Name
        $log = Write-VPASTextRecorder -inputval $CommandName -token $token -LogType COMMAND
    }
    Process{

        try{
            if($PSCmdlet.ParameterSetName -eq "InputParameters"){
                $KeyHash = @{
                    set1 = @{
                        AcceptableKeys = @("TargetMetric","MetricFormat","OutputDirectory","HTMLChart","DayRange","AmtOfSets","AmtOfUsers","HideRawData","IgnorePlatforms","IgnoreUsernames","PlatformSearchQuery","UsernameSearchQuery")
                        MandatoryKeys = @("TargetMetric","MetricFormat")
                    }
                }
                $CheckSet = Test-VPASHashtableKeysHelper -InputHash $InputParameters -KeyHash $KeyHash

                if(!$CheckSet){
                    $log = Write-VPASTextRecorder -inputval "FAILED TO FIND TARGET PARAMETER SET" -token $token -LogType MISC
                    Write-Verbose "FAILED TO FIND TARGET PARAMETER SET"
                    Write-VPASOutput -str "FAILED TO FIND TARGET PARAMETER SET...VIEW EXAMPLES BELOW:" -type E
                    $examples = Write-VPASExampleHelper -CommandName $CommandName
                    return $false
                }
                else{
                    foreach($key in $InputParameters.Keys){
                        Set-Variable -Name $key -Value $InputParameters.$key
                    }
                }
            }
        }catch{
            $log = Write-VPASTextRecorder -inputval $_ -token $token -LogType ERROR
            $log = Write-VPASTextRecorder -inputval "REST API COMMAND RETURNED: FALSE" -token $token -LogType MISC
            Write-Verbose "FAILED TO INVOKE METRIC"
            Write-VPASOutput -str $_ -type E
            return $false
        }

        try{
            $SkipPlatformsSTR = ""
            foreach($rec in $IgnorePlatforms){
                $SkipPlatformsSTR += "*" + $rec + "*; "
            }
            $SkipUsernameSTR = ""
            foreach($rec in $IgnoreUsernames){
                $SkipUsernameSTR += "*" + $rec + "*; "
            }
            $SkipSafeSTR = ""
            foreach($rec in $IgnoreSafes){
                $SkipSafeSTR += "*" + $rec + "*; "
            }
            $TargetSafeSTR = ""
            foreach($rec in $SafeSearchQuery){
                $TargetSafeSTR += "*" + $rec + "*; "
            }
            $TargetPlatformSTR = ""
            foreach($rec in $PlatformSearchQuery){
                $TargetPlatformSTR += "*" + $rec + "*; "
            }
            $TargetUsernameSTR = ""
            foreach($rec in $UsernameSearchQuery){
                $TargetUsernameSTR += "*" + $rec + "*; "
            }

            if($MetricFormat -ne "NONE"){
                if([String]::IsNullOrEmpty($OutputDirectory)){
                    $curUser = $env:UserName
                    $OutputDirectory = "C:\Users\$curUser\AppData\Local\VPASModuleOutputs\Metrics"
                    Write-Verbose "NO OUTPUT DIRECTORY SUPPLIED, USING DEFAULT LOCATION: $OutputDirectory"

                    if(Test-Path -Path $OutputDirectory){
                        #DO NOTHING
                    }
                    else{
                        write-verbose "$OutputDirectory DOES NOT EXIST, CREATING DIRECTORY"
                        $MakeDirectory = New-Item -Path $OutputDirectory -Type Directory
                    }
                }
                else{
                    if(Test-Path -Path $OutputDirectory){
                        #DO NOTHING
                    }
                    else{
                        $curUser = $env:UserName
                        $OutputDirectory = "C:\Users\$curUser\AppData\Local\VPASModuleOutputs\Metrics"
                        write-verbose "$OutputDirectory DOES NOT EXIST, USING DEFAULT LOCATION: $OutputDirectory"
                        if(Test-Path -Path $OutputDirectory){
                            #DO NOTHING
                        }
                        else{
                            $MakeDirectory = New-Item -Path $OutputDirectory -Type Directory
                        }
                    }
                }
            }

            if([String]::IsNullOrEmpty($HTMLChart)){
                $HTMLChart = "BarGraph"
            }

            if($TargetMetric -eq "PSMSessionsInXDays"){
                $tagout = "sessions"
                if([String]::IsNullOrEmpty($DayRange)){
                    Write-VPASOutput -str "NO DayRange SUPPLIED, ENTER DayRange (1 - 365): " -type Y
                    $DayRange = Read-Host
                }
                if([String]::IsNullOrEmpty($AmtOfSets)){
                    Write-VPASOutput -str "NO AmtOfSets SUPPLIED, ENTER AmtOfSets (1 - 365): " -type Y
                    $AmtOfSets = Read-Host
                }

                try{
                    $AmtDaysInt = [Int]$DayRange
                    if($AmtDaysInt -lt 1 -or $AmtDaysInt -gt 365){
                        Write-Verbose "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365"
                        Write-VPASOutput -str "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365" -type E
                        return $false
                    }
                }catch{
                    Write-Verbose "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365"
                    Write-VPASOutput -str "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365" -type E
                    Write-VPASOutput -str $_ -type E
                    return $false
                }

                try{
                    $AmtSetsInt = [Int]$AmtOfSets
                    if($AmtSetsInt -lt 1 -or $AmtSetsInt -gt 365){
                        Write-Verbose "INVALID INPUT FOR SET AMOUNT: $AmtOfSets...MUST AN INTEGER BETWEEN 1 - 365"
                        Write-VPASOutput -str "INVALID INPUT FOR SET AMOUNT: $AmtOfSets...MUST AN INTEGER BETWEEN 1 - 365" -type E
                        return $false
                    }
                }catch{
                    Write-Verbose "INVALID INPUT FOR SET AMOUNT: $AmtOfSets ...MUST AN INTEGER BETWEEN 1 - 365"
                    Write-VPASOutput -str "INVALID INPUT FOR SET AMOUNT: $AmtOfSets ...MUST AN INTEGER BETWEEN 1 - 365" -type E
                    Write-VPASOutput -str $_ -type E
                    return $false
                }

                #INITIALIZE DATE HASH
                $RecordingsHash = @{}
                $CurTime = ([int][double]::Parse((Get-Date (get-date (get-date).Date).ToLocalTime() -UFormat %s))) + 86400
                $NumSeconds = ($AmtDaysInt * 86400)

                $counter = 0
                while($counter -lt $AmtSetsInt){
                    $counter += 1
                    $MaxNum = $CurTime - 1
                    $CurTime = $CurTime - $NumSeconds
                    $MinNum = $CurTime

                    $UniqueKey = "Set" + $counter
                    $RecordingsHash += @{
                        $UniqueKey = @{
                            Max = $MaxNum
                            Min = $MinNum
                            Count = 0
                            RawData = @()
                        }
                    }
                }

                $LastKey = $AmtSetsInt
                $LastKeyStr = "Set$LastKey"
                $FromTime = $RecordingsHash.$LastKeyStr.Min
                $ToTime = $RecordingsHash.Set1.Max

                #GET RECORDINGS
                $AllRecordings = Get-VPASPSMSessions -SearchQuery " " -FromTime $FromTime -ToTime $ToTime
                if(!$AllRecordings){
                    Write-Verbose "FAILED TO RETRIEVE PSM SESSIONS"
                    Write-VPASOutput -str "FAILED TO RETRIEVE PSM SESSIONS" -type E
                    Write-VPASOutput -str $_ -type E
                    return $false
                }

                foreach($rec in $AllRecordings.Recordings){
                    $validtargetplatform = $false
                    $validtargetusername = $false
                    $EpochStart = $rec.Start
                    $targetPlat = $rec.AccountPlatformID
                    $targetUsername = $rec.AccountUsername
                    $targetAddress = $rec.AccountAddress
                    $skipval = $false

                    if($PlatformSearchQuery.Count -eq 0){
                        $validtargetplatform = $true
                    }
                    else{
                        foreach($query in $PlatformSearchQuery){
                            if($targetPlat -match $query){
                                $validtargetplatform = $true
                            }
                        }
                    }

                    if($UsernameSearchQuery.Count -eq 0){
                        $validtargetusername = $true
                    }
                    else{
                        foreach($query in $UsernameSearchQuery){
                            if($targetUsername -match $query){
                                $validtargetusername = $true
                            }
                        }
                    }

                    if(!$validtargetplatform -or !$validtargetusername){
                        $skipval = $true
                    }

                    if(!$skipval){
                        foreach($ignoreval in $IgnorePlatforms){
                            if($targetPlat -match $ignoreval){
                                Write-Verbose "SKIPPING $targetUsername@$targetAddress : SKIP VALUE $ignoreval FOUND IN PLATFORM $targetPlat"
                                $skipval = $true
                            }
                        }
                    }

                    if(!$skipval){
                        foreach($ignoreval in $IgnoreUsernames){
                            if($targetUsername -match $ignoreval){
                                Write-Verbose "SKIPPING $targetUsername@$targetAddress : SKIP VALUE $ignoreval FOUND IN USERNAME $targetUsername"
                                $skipval = $true
                            }
                        }
                    }

                    if(!$skipval){
                        $parser = $AmtSetsInt
                        while($parser -gt 0){
                            $uniqueKey = "Set$parser"
                            $setmin = $RecordingsHash.$uniqueKey.Min
                            $setmax = $RecordingsHash.$uniqueKey.Max
                            $setcount = $RecordingsHash.$uniqueKey.Count

                            if($EpochStart -ge $setmin -and $EpochStart -le $setmax){
                                $setcount += 1
                                $RecordingsHash.$uniqueKey.Count = $setcount
                                $parser = 0
                                $RecordingsHash.$UniqueKey.RawData += $rec
                            }
                            else{
                                $parser -= 1
                            }
                        }
                    }
                }

                #CONVERT THINGS TO HUMAN READABLE TIME
                $origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
                $parser = $AmtSetsInt
                while($parser -gt 0){
                    $uniqueKey = "Set$parser"
                    $setmin = $RecordingsHash.$uniqueKey.Min
                    $setmax = $RecordingsHash.$uniqueKey.Max

                    $newmin = $origin.AddSeconds($setmin).ToShortDateString()
                    $newmax = $origin.AddSeconds($setmax).ToShortDateString()

                    $RecordingsHash.$uniqueKey.Min = $newmin
                    $RecordingsHash.$uniqueKey.Max = $newmax

                    $parser -= 1
                }

                if($MetricFormat -eq "JSON" -or $MetricFormat -eq "ALL"){
                    $outputfile = "$OutputDirectory/PSMSessionsIn" + $AmtDaysInt + "Days.json"
                    $OutputDataJSON = $RecordingsHash | ConvertTo-Json -Depth 100
                    Write-Output $OutputDataJSON | Set-Content $outputfile
                }
                if($MetricFormat -eq "NONE"){
                    $htmlData += @{
                        datahash = $RecordingsHash
                    }
                }
                if($MetricFormat -eq "HTML" -or $MetricFormat -eq "ALL"){
                    $outputfile = "$OutputDirectory/PSMSessionsIn" + $AmtDaysInt + "Days.html"
                    $titlesplit = "PSM Sessions Made Per $AmtDaysInt Days"
                    $metricTag = "PSM Metrics"
                    $recommendation1 = "PSM provides a secure gateway for accessing critical systems and applications by privileged users. It ensures that sensitive credentials are never exposed to end-users, reducing the risk of credential theft and unauthorized access."
                    $recommendation2 = "PSM facilitates sessions that are isolated within the PSM environment, reducing the risk of lateral movement by attackers and preventing unauthorized access to sensitive resources."
                    $recommendation3 = "PSM offers real-time monitoring and alerting capabilities, allowing organizations to detect and respond to suspicious activities promptly. It provides visibility into privileged sessions, user activities, and system events, enabling proactive threat detection and incident response."
                    $OutputDataJSON = $RecordingsHash | ConvertTo-Json -Depth 100
                    $curTime = get-date -Format "MM/dd/yyyy HH:mm:ss"
                    if($HTMLChart -eq "ALL"){
                        $tablestr = "Bar Graph, Line Graph, Pie Chart"
                    }
                    else{
                        $tablestr = $HTMLChart
                    }
                    $metricexplanation = "This metric tracks the quantity of PSM sessions made within the past $AmtDaysInt days in batches, with historical data included."

                    $tempstr = ""
                    $tempstr2 = ""
                    $tempstr3 = ""
                    $tempstr4 = ""
                    $AllKeys = $RecordingsHash.Keys
                    $AmtKeys = $AllKeys.Count
                    $counter = $AmtKeys
                    $GetMinMax = @()

                    while($counter -gt 0){
                        $key = "Set$counter"
                        $minVal = $RecordingsHash.$key.Min
                        $maxVal = $RecordingsHash.$key.Max
                        $curCount = $RecordingsHash.$key.count
                        $GetMinMax += $curCount
                        $textColor =  '#{0:X6}' -f (Get-Random -Maximum 0x1000000)

                        $outputstr = "$maxVal - $minVal"
                        $tempstr += "`"$outputstr`","
                        $tempstr2 += "$curCount,"
                        $tempstr3 += "`"green`","
                        $tempstr4 += "`"$textColor`","

                        $counter -= 1
                    }

                    if($GetMinMax.count -ne 0){
                        $GetMinMax = $GetMinMax | Sort-Object
                        $mintick = 0
                        $maxtick = $GetMinMax[$GetMinMax.Count - 1]
                    }
                    else{
                        $mintick = 0
                        $maxtick = 0
                    }

                    if(![String]::IsNullOrEmpty($tempstr)){
                        $tempstr = $tempstr.Substring(0,$tempstr.Length-1)
                    }
                    if(![String]::IsNullOrEmpty($tempstr2)){
                        $tempstr2 = $tempstr2.Substring(0,$tempstr2.Length-1)
                    }
                    if(![String]::IsNullOrEmpty($tempstr3)){
                        $tempstr3 = $tempstr3.Substring(0,$tempstr3.Length-1)
                    }
                    if(![String]::IsNullOrEmpty($tempstr4)){
                        $tempstr4 = $tempstr4.Substring(0,$tempstr4.Length-1)
                    }

                    $htmlData += @{
                        Recommendation1 = $recommendation1
                        Recommendation2 = $recommendation2
                        Recommendation3 = $recommendation3
                        titlesplit = $titlesplit
                        outputfile = $outputfile
                        metricTag = $metricTag
                        OutputDataJSON = $OutputDataJSON
                        curTime = $curTime
                        tablestr = $tablestr
                        metricexplanation = $metricexplanation
                        HTMLChart = $HTMLChart
                        tempstr = $tempstr
                        tempstr2 = $tempstr2
                        tempstr3 = $tempstr3
                        tempstr4 = $tempstr4
                        datahash = $RecordingsHash
                        maxtick = $maxtick
                        mintick = $mintick
                    }

                }
            }
            if($TargetMetric -eq "PSMUtilizationForXDays"){
                $tagout = "connections"
                if([String]::IsNullOrEmpty($DayRange)){
                    Write-VPASOutput -str "NO DayRange SUPPLIED, ENTER DayRange (1 - 365): " -type Y
                    $DayRange = Read-Host
                }

                try{
                    $AmtDaysInt = [Int]$DayRange
                    if($AmtDaysInt -lt 1 -or $AmtDaysInt -gt 365){
                        Write-Verbose "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365"
                        Write-VPASOutput -str "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365" -type E
                        return $false
                    }
                }catch{
                    Write-Verbose "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365"
                    Write-VPASOutput -str "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365" -type E
                    Write-VPASOutput -str $_ -type E
                    return $false
                }

                #INITIALIZE HASH
                $RecordingsHash = @{}
                $MaxTime = ([int][double]::Parse((Get-Date (get-date (get-date).Date).ToLocalTime() -UFormat %s))) + 86400
                $TimeDiff = ($AmtDaysInt * 86400)
                $MinTime = $MaxTime - $TimeDiff
                $AllPSMs = Get-VPASSystemHealth -Component PSM
                if(!$AllPSMs){
                    Write-Verbose "FAILED TO RETRIEVE PSM IDs"
                    Write-VPASOutput -str "FAILED TO RETRIEVE PSM IDs" -type E
                    Write-VPASOutput -str $_ -type E
                    return $false
                }

                $FromTime = $MinTime
                $ToTime = $MaxTime

                foreach($rec in $AllPSMs.ComponentsDetails){
                    $ComponentUsername = $rec.ComponentUserName

                    if($ComponentUsername -match "PSMApp_"){
                        $UniqueKey = $ComponentUsername
                        $RecordingsHash += @{
                            $UniqueKey = @{
                                Count = 0
                                RawData = @()
                            }
                        }
                    }
                }

                #GET RECORDINGS
                $AllRecordings = Get-VPASPSMSessions -SearchQuery " " -FromTime $FromTime -ToTime $ToTime
                if(!$AllRecordings){
                    Write-Verbose "FAILED TO RETRIEVE PSM SESSIONS"
                    Write-VPASOutput -str "FAILED TO RETRIEVE PSM SESSIONS" -type E
                    Write-VPASOutput -str $_ -type E
                    return $false
                }

                foreach($rec in $AllRecordings.Recordings){
                    $validtargetplatform = $false
                    $validtargetusername = $false
                    $EpochStart = $rec.Start
                    $TargetPSM = $rec.RawProperties.ProviderID
                    $targetPlat = $rec.AccountPlatformID
                    $targetUsername = $rec.AccountUsername
                    $targetAddress = $rec.AccountAddress
                    $skipval = $false

                    if($PlatformSearchQuery.Count -eq 0){
                        $validtargetplatform = $true
                    }
                    else{
                        foreach($query in $PlatformSearchQuery){
                            if($targetPlat -match $query){
                                $validtargetplatform = $true
                            }
                        }
                    }

                    if($UsernameSearchQuery.Count -eq 0){
                        $validtargetusername = $true
                    }
                    else{
                        foreach($query in $UsernameSearchQuery){
                            if($targetUsername -match $query){
                                $validtargetusername = $true
                            }
                        }
                    }

                    if(!$validtargetplatform -or !$validtargetusername){
                        $skipval = $true
                    }

                    if(!$skipval){
                        foreach($ignoreval in $IgnorePlatforms){
                            if($targetPlat -match $ignoreval){
                                Write-Verbose "SKIPPING $targetUsername@$targetAddress : SKIP VALUE $ignoreval FOUND IN PLATFORM $targetPlat"
                                $skipval = $true
                            }
                        }
                    }

                    if(!$skipval){
                        foreach($ignoreval in $IgnoreUsernames){
                            if($targetUsername -match $ignoreval){
                                Write-Verbose "SKIPPING $targetUsername@$targetAddress : SKIP VALUE $ignoreval FOUND IN USERNAME $targetUsername"
                                $skipval = $true
                            }
                        }
                    }

                    if(!$skipval){
                        if($EpochStart -le $MaxTime -and $EpochStart -ge $MinTime){
                            if($RecordingsHash.$TargetPSM){
                                $curCount = $RecordingsHash.$TargetPSM.Count
                                $curCount += 1
                                $RecordingsHash.$TargetPSM.Count = $curCount
                                $RecordingsHash.$TargetPSM.RawData += $rec
                            }
                        }
                    }
                }

                if($MetricFormat -eq "JSON" -or $MetricFormat -eq "ALL"){
                    $outputfile = "$OutputDirectory/PSMUtilizationFor" + $AmtDaysInt + "Days.json"
                    $OutputDataJSON = $RecordingsHash | ConvertTo-Json -Depth 100
                    Write-Output $OutputDataJSON | Set-Content $outputfile
                }
                if($MetricFormat -eq "NONE"){
                    $htmlData += @{
                        datahash = $RecordingsHash
                    }
                }
                if($MetricFormat -eq "HTML" -or $MetricFormat -eq "ALL"){
                    $outputfile = "$OutputDirectory/PSMUtilizationFor" + $AmtDaysInt + "Days.html"
                    $titlesplit = "PSM Utilization For $AmtDaysInt Days"
                    $metricTag = "PSM Metrics"
                    $recommendation1 = "Load balancing ensures high availability of PSM services by distributing incoming traffic across multiple PSM instances. In the event of a server failure or maintenance, the load balancer automatically redirects traffic to healthy instances, minimizing downtime and ensuring uninterrupted access to privileged resources."
                    $recommendation2 = "Load balancing optimizes resource utilization and performance by evenly distributing incoming requests across multiple PSM instances. This prevents any single instance from becoming overloaded, ensuring consistent response times and minimizing latency for users accessing privileged sessions."
                    $recommendation3 = "Load balancing enables organizations to deploy PSM instances across multiple geographic locations for enhanced redundancy and disaster recovery. By using global load balancing techniques, organizations can route traffic to the nearest or least congested PSM instance, improving performance and reliability for remote users."
                    $OutputDataJSON = $RecordingsHash | ConvertTo-Json -Depth 100
                    $curTime = get-date -Format "MM/dd/yyyy HH:mm:ss"
                    if($HTMLChart -eq "ALL"){
                        $tablestr = "Bar Graph, Line Graph, Pie Chart"
                    }
                    else{
                        $tablestr = $HTMLChart
                    }
                    $metricexplanation = "This metric tracks which PSM is being used in an evironment with multiple/loadbalanced PSMs"
                    #PSMUtilizationForXDays
                    $tempstr = ""
                    $tempstr2 = ""
                    $tempstr3 = ""
                    $tempstr4 = ""
                    $AllKeys = $RecordingsHash.Keys
                    $GetMinMax = @()

                    foreach($key in $AllKeys){
                        $curCount = $RecordingsHash.$key.count
                        $GetMinMax += $curCount
                        $textColor =  '#{0:X6}' -f (Get-Random -Maximum 0x1000000)

                        $tempstr += "`"$key`","
                        $tempstr2 += "$curCount,"
                        $tempstr3 += "`"green`","
                        $tempstr4 += "`"$textColor`","
                    }

                    if($GetMinMax.count -ne 0){
                        $GetMinMax = $GetMinMax | Sort-Object
                        $mintick = 0
                        $maxtick = $GetMinMax[$GetMinMax.Count - 1]
                    }
                    else{
                        $mintick = 0
                        $maxtick = 0
                    }

                    if(![String]::IsNullOrEmpty($tempstr)){
                        $tempstr = $tempstr.Substring(0,$tempstr.Length-1)
                    }
                    if(![String]::IsNullOrEmpty($tempstr2)){
                        $tempstr2 = $tempstr2.Substring(0,$tempstr2.Length-1)
                    }
                    if(![String]::IsNullOrEmpty($tempstr3)){
                        $tempstr3 = $tempstr3.Substring(0,$tempstr3.Length-1)
                    }
                    if(![String]::IsNullOrEmpty($tempstr4)){
                        $tempstr4 = $tempstr4.Substring(0,$tempstr4.Length-1)
                    }

                    $htmlData += @{
                        Recommendation1 = $recommendation1
                        Recommendation2 = $recommendation2
                        Recommendation3 = $recommendation3
                        titlesplit = $titlesplit
                        outputfile = $outputfile
                        metricTag = $metricTag
                        OutputDataJSON = $OutputDataJSON
                        curTime = $curTime
                        tablestr = $tablestr
                        metricexplanation = $metricexplanation
                        HTMLChart = $HTMLChart
                        tempstr = $tempstr
                        tempstr2 = $tempstr2
                        tempstr3 = $tempstr3
                        tempstr4 = $tempstr4
                        datahash = $RecordingsHash
                        maxtick = $maxtick
                        mintick = $mintick
                    }
                }
            }
            if($TargetMetric -eq "PSMConnectionComponentsInXDays"){
                $tagout = "connections"
                if([String]::IsNullOrEmpty($DayRange)){
                    Write-VPASOutput -str "NO DayRange SUPPLIED, ENTER DayRange (1 - 365): " -type Y
                    $DayRange = Read-Host
                }

                try{
                    $AmtDaysInt = [Int]$DayRange
                    if($AmtDaysInt -lt 1 -or $AmtDaysInt -gt 365){
                        Write-Verbose "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365"
                        Write-VPASOutput -str "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365" -type E
                        return $false
                    }
                }catch{
                    Write-Verbose "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365"
                    Write-VPASOutput -str "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365" -type E
                    Write-VPASOutput -str $_ -type E
                    return $false
                }

                #INITIALIZE HASH
                $RecordingsHash = @{}
                $MaxTime = ([int][double]::Parse((Get-Date (get-date (get-date).Date).ToLocalTime() -UFormat %s))) + 86400
                $TimeDiff = ($AmtDaysInt * 86400)
                $MinTime = $MaxTime - $TimeDiff
                $FromTime = $MinTime
                $ToTime = $MaxTime

                #GET RECORDINGS
                $AllRecordings = Get-VPASPSMSessions -SearchQuery " " -FromTime $FromTime -ToTime $ToTime
                if(!$AllRecordings){
                    Write-Verbose "FAILED TO RETRIEVE PSM SESSIONS"
                    Write-VPASOutput -str "FAILED TO RETRIEVE PSM SESSIONS" -type E
                    Write-VPASOutput -str $_ -type E
                    return $false
                }

                foreach($rec in $AllRecordings.Recordings){
                    $validtargetplatform = $false
                    $validtargetusername = $false
                    $EpochStart = $rec.Start
                    $TargetCC = $rec.ConnectionComponentID
                    $targetPlat = $rec.AccountPlatformID
                    $targetUsername = $rec.AccountUsername
                    $targetAddress = $rec.AccountAddress
                    $skipval = $false

                    if($PlatformSearchQuery.Count -eq 0){
                        $validtargetplatform = $true
                    }
                    else{
                        foreach($query in $PlatformSearchQuery){
                            if($targetPlat -match $query){
                                $validtargetplatform = $true
                            }
                        }
                    }

                    if($UsernameSearchQuery.Count -eq 0){
                        $validtargetusername = $true
                    }
                    else{
                        foreach($query in $UsernameSearchQuery){
                            if($targetUsername -match $query){
                                $validtargetusername = $true
                            }
                        }
                    }

                    if(!$validtargetplatform -or !$validtargetusername){
                        $skipval = $true
                    }

                    if(!$skipval){
                        foreach($ignoreval in $IgnorePlatforms){
                            if($targetPlat -match $ignoreval){
                                Write-Verbose "SKIPPING $targetUsername@$targetAddress : SKIP VALUE $ignoreval FOUND IN PLATFORM $targetPlat"
                                $skipval = $true
                            }
                        }
                    }

                    if(!$skipval){
                        foreach($ignoreval in $IgnoreUsernames){
                            if($targetUsername -match $ignoreval){
                                Write-Verbose "SKIPPING $targetUsername@$targetAddress : SKIP VALUE $ignoreval FOUND IN USERNAME $targetUsername"
                                $skipval = $true
                            }
                        }
                    }

                    if(!$skipval){
                        if($EpochStart -le $MaxTime -and $EpochStart -ge $MinTime){
                            if($RecordingsHash.$TargetCC){
                                $curCount = $RecordingsHash.$TargetCC.Count
                                $curCount += 1
                                $RecordingsHash.$TargetCC.Count = $curCount
                                $RecordingsHash.$TargetCC.RawData += $rec
                            }
                            else{
                                $RecordingsHash += @{
                                    $TargetCC = @{
                                        Count = 1
                                        RawData = @($rec)
                                    }
                                }
                            }
                        }
                    }
                }

                if($MetricFormat -eq "JSON" -or $MetricFormat -eq "ALL"){
                    $outputfile = "$OutputDirectory/PSMConnectionComponentsFor" + $AmtDaysInt + "Days.json"
                    $OutputDataJSON = $RecordingsHash | ConvertTo-Json -Depth 100
                    Write-Output $OutputDataJSON | Set-Content $outputfile
                }
                if($MetricFormat -eq "NONE"){
                    $htmlData += @{
                        datahash = $RecordingsHash
                    }
                }
                if($MetricFormat -eq "HTML" -or $MetricFormat -eq "ALL"){
                    $outputfile = "$OutputDirectory/PSMConnectionComponentsFor" + $AmtDaysInt + "Days.html"
                    $titlesplit = "PSM Connection Components Used In The Last $AmtDaysInt Days"
                    $metricTag = "PSM Metrics"
                    $recommendation1 = "Creating connection components for PSM allows sessions to be recorded, including keystrokes, commands, and screen activities, providing a detailed audit trail of user actions. This audit trail is invaluable for compliance purposes, forensic analysis, and investigating security incidents."
                    $recommendation2 = "When utilizing a connection component to initiate a privileged session through PSM, the platform dynamically injects the credentials into the session without revealing them to the end-user. This process ensures that sensitive passwords are never exposed to users or applications accessing the target system."
                    $recommendation3 = "Connection components reduce the attack surface by limiting direct access to target systems and enforcing strict access controls. This minimizes the risk of unauthorized access, privilege escalation, and insider threats, improving overall security posture."
                    $OutputDataJSON = $RecordingsHash | ConvertTo-Json -Depth 100
                    $curTime = get-date -Format "MM/dd/yyyy HH:mm:ss"
                    if($HTMLChart -eq "ALL"){
                        $tablestr = "Bar Graph, Line Graph, Pie Chart"
                    }
                    else{
                        $tablestr = $HTMLChart
                    }
                    $metricexplanation = "This metric tracks which connection component was used to make a PSM session.<br>(Note - this will only track successful PSM sessions)"

                    $tempstr = ""
                    $tempstr2 = ""
                    $tempstr3 = ""
                    $tempstr4 = ""
                    $AllKeys = $RecordingsHash.Keys
                    $GetMinMax = @()

                    foreach($key in $AllKeys){
                        $curCount = $RecordingsHash.$key.count
                        $GetMinMax += $curCount
                        $textColor =  '#{0:X6}' -f (Get-Random -Maximum 0x1000000)

                        $tempstr += "`"$key`","
                        $tempstr2 += "$curCount,"
                        $tempstr3 += "`"green`","
                        $tempstr4 += "`"$textColor`","
                    }

                    if($GetMinMax.count -ne 0){
                        $GetMinMax = $GetMinMax | Sort-Object
                        $mintick = 0
                        $maxtick = $GetMinMax[$GetMinMax.Count - 1]
                    }
                    else{
                        $mintick = 0
                        $maxtick = 0
                    }

                    if(![String]::IsNullOrEmpty($tempstr)){
                        $tempstr = $tempstr.Substring(0,$tempstr.Length-1)
                    }
                    if(![String]::IsNullOrEmpty($tempstr2)){
                        $tempstr2 = $tempstr2.Substring(0,$tempstr2.Length-1)
                    }
                    if(![String]::IsNullOrEmpty($tempstr3)){
                        $tempstr3 = $tempstr3.Substring(0,$tempstr3.Length-1)
                    }
                    if(![String]::IsNullOrEmpty($tempstr4)){
                        $tempstr4 = $tempstr4.Substring(0,$tempstr4.Length-1)
                    }

                    $htmlData += @{
                        Recommendation1 = $recommendation1
                        Recommendation2 = $recommendation2
                        Recommendation3 = $recommendation3
                        titlesplit = $titlesplit
                        outputfile = $outputfile
                        metricTag = $metricTag
                        OutputDataJSON = $OutputDataJSON
                        curTime = $curTime
                        tablestr = $tablestr
                        metricexplanation = $metricexplanation
                        HTMLChart = $HTMLChart
                        tempstr = $tempstr
                        tempstr2 = $tempstr2
                        tempstr3 = $tempstr3
                        tempstr4 = $tempstr4
                        datahash = $RecordingsHash
                        maxtick = $maxtick
                        mintick = $mintick
                    }
                }
            }
            if($TargetMetric -eq "UsersConnectingWithPSMInXDays"){
                $tagout = "connections"
                if([String]::IsNullOrEmpty($DayRange)){
                    Write-VPASOutput -str "NO DayRange SUPPLIED, ENTER DayRange (1 - 365): " -type Y
                    $DayRange = Read-Host
                }

                try{
                    $AmtDaysInt = [Int]$DayRange
                    if($AmtDaysInt -lt 1 -or $AmtDaysInt -gt 365){
                        Write-Verbose "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365"
                        Write-VPASOutput -str "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365" -type E
                        return $false
                    }
                }catch{
                    Write-Verbose "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365"
                    Write-VPASOutput -str "INVALID INPUT FOR DAY RANGE: $DayRange ...MUST BE AN INTEGER BETWEEN 1 - 365" -type E
                    Write-VPASOutput -str $_ -type E
                    return $false
                }

                if([String]::IsNullOrEmpty($AmtOfUsers)){
                    Write-VPASOutput -str "NO AmtOfUsers SUPPLIED, ENTER AmtOfUsers TO DISPLAY [ALL]: " -type Y
                    $AmtOfUsers = Read-Host
                    if([String]::IsNullOrEmpty($AmtOfUsers)){$AmtOfUsers = "ALL"}
                }
                try{
                    if($AmtOfUsers -eq "ALL"){
                        $AmtOfUsersInt = 9999
                    }
                    else{
                        $AmtOfUsersInt = [Int]$AmtOfUsers
                        if($AmtOfUsersInt -lt 1){
                            Write-Verbose "INVALID INPUT FOR AMOUNT OF USERS: $AmtOfUsers ...MUST BE A VALID INTEGER ABOVE 0 OR 'ALL'"
                            Write-VPASOutput -str "INVALID INPUT FOR AMOUNT OF USERS: $AmtOfUsers ...MUST BE A VALID INTEGER ABOVE 0 OR 'ALL'" -type E
                            return $false
                        }
                    }
                }catch{
                    Write-Verbose "INVALID INPUT FOR AMOUNT OF USERS: $AmtOfUsers ...MUST BE A VALID INTEGER ABOVE 0 OR 'ALL'"
                    Write-VPASOutput -str "INVALID INPUT FOR AMOUNT OF USERS: $AmtOfUsers ...MUST BE A VALID INTEGER ABOVE 0 OR 'ALL'" -type E
                    Write-VPASOutput -str $_ -type E
                    return $false
                }

                #INITIALIZE HASH
                $RecordingsHashTemp = @{}
                $MaxTime = ([int][double]::Parse((Get-Date (get-date (get-date).Date).ToLocalTime() -UFormat %s))) + 86400
                $TimeDiff = ($AmtDaysInt * 86400)
                $MinTime = $MaxTime - $TimeDiff
                $FromTime = $MinTime
                $ToTime = $MaxTime

                #GET RECORDINGS
                $AllRecordings = Get-VPASPSMSessions -SearchQuery " " -FromTime $FromTime -ToTime $ToTime
                if(!$AllRecordings){
                    Write-Verbose "FAILED TO RETRIEVE PSM SESSIONS"
                    Write-VPASOutput -str "FAILED TO RETRIEVE PSM SESSIONS" -type E
                    Write-VPASOutput -str $_ -type E
                    return $false
                }

                foreach($rec in $AllRecordings.Recordings){
                    $validtargetplatform = $false
                    $validtargetusername = $false
                    $EpochStart = $rec.Start
                    $TargetUser = $rec.User
                    $targetPlat = $rec.AccountPlatformID
                    $targetUsername = $rec.AccountUsername
                    $targetAddress = $rec.AccountAddress
                    $skipval = $false

                    if($PlatformSearchQuery.Count -eq 0){
                        $validtargetplatform = $true
                    }
                    else{
                        foreach($query in $PlatformSearchQuery){
                            if($targetPlat -match $query){
                                $validtargetplatform = $true
                            }
                        }
                    }

                    if($UsernameSearchQuery.Count -eq 0){
                        $validtargetusername = $true
                    }
                    else{
                        foreach($query in $UsernameSearchQuery){
                            if($targetUsername -match $query){
                                $validtargetusername = $true
                            }
                        }
                    }

                    if(!$validtargetplatform -or !$validtargetusername){
                        $skipval = $true
                    }

                    if(!$skipval){
                        foreach($ignoreval in $IgnorePlatforms){
                            if($targetPlat -match $ignoreval){
                                Write-Verbose "SKIPPING $targetUsername@$targetAddress : SKIP VALUE $ignoreval FOUND IN PLATFORM $targetPlat"
                                $skipval = $true
                            }
                        }
                    }

                    if(!$skipval){
                        foreach($ignoreval in $IgnoreUsernames){
                            if($targetUsername -match $ignoreval){
                                Write-Verbose "SKIPPING $targetUsername@$targetAddress : SKIP VALUE $ignoreval FOUND IN USERNAME $targetUsername"
                                $skipval = $true
                            }
                        }
                    }

                    if(!$skipval){
                        if($EpochStart -le $MaxTime -and $EpochStart -ge $MinTime){
                            if($RecordingsHashTemp.$TargetUser){
                                $curCount = $RecordingsHashTemp.$TargetUser.Count
                                $curCount += 1
                                $RecordingsHashTemp.$TargetUser.Count = $curCount
                                $RecordingsHashTemp.$TargetUser.RawData += $rec
                            }
                            else{
                                $RecordingsHashTemp += @{
                                    $TargetUser = @{
                                        Count = 1
                                        RawData = @($rec)
                                    }
                                }
                            }
                        }
                    }
                }

                #TRIM TOP USERS
                $RecordingsHash = @{}
                $HashUsers = $RecordingsHashTemp.Keys.Count
                if($HashUsers -le $AmtOfUsersInt){
                    $RecordingsHash = $RecordingsHashTemp
                }
                else{
                    #TOO MANY USERS RETURNED
                    $sortedUsers = $RecordingsHashTemp.GetEnumerator() | Sort-Object -Property { $_.Value.Count } -Descending
                    $TopKeys = $sortedUsers[0..($AmtOfUsersInt-1)].Key
                    foreach($key in $TopKeys){
                        $RecordingsHash += @{
                            $key = $RecordingsHashTemp.$key
                        }
                    }
                }

                if($MetricFormat -eq "JSON" -or $MetricFormat -eq "ALL"){
                    $outputfile = "$OutputDirectory/UsersConnectingWithPSMIn" + $AmtDaysInt + "Days.json"
                    $OutputDataJSON = $RecordingsHash | ConvertTo-Json -Depth 100
                    Write-Output $OutputDataJSON | Set-Content $outputfile
                }
                if($MetricFormat -eq "NONE"){
                    $htmlData += @{
                        datahash = $RecordingsHash
                    }
                }
                if($MetricFormat -eq "HTML" -or $MetricFormat -eq "ALL"){
                    $outputfile = "$OutputDirectory/UsersConnectingWithPSMIn" + $AmtDaysInt + "Days.html"
                    $titlesplit = "Users Connecting Via PSM In The Last $AmtDaysInt Days (Top $AmtOfUsers Users)"
                    $metricTag = "PSM Metrics"
                    $recommendation1 = "End users should use the PSM whenever possible to access privileged resources securely without having to manage or remember sensitive credentials. PSM handles the authentication process transparently, simplifying the login experience."
                    $recommendation2 = "End Users should use the PSM to provide an audit trail of user activities. This helps organizations demonstrate compliance with regulatory requirements and internal security policies."
                    $recommendation3 = "End users can have peace of mind knowing that their privileged access is protected by CyberArk's security solution. PSM helps mitigate security risks and ensures that sensitive information remains secure during privileged sessions."
                    $OutputDataJSON = $RecordingsHash | ConvertTo-Json -Depth 100
                    $curTime = get-date -Format "MM/dd/yyyy HH:mm:ss"
                    if($HTMLChart -eq "ALL"){
                        $tablestr = "Bar Graph, Line Graph, Pie Chart"
                    }
                    else{
                        $tablestr = $HTMLChart
                    }
                    $metricexplanation = "This metric tracks which end users are making connections via the PSM<br>(Note - this will only track successful PSM sessions)"

                    $tempstr = ""
                    $tempstr2 = ""
                    $tempstr3 = ""
                    $tempstr4 = ""
                    $AllKeys = $RecordingsHash.Keys
                    $GetMinMax = @()

                    foreach($key in $AllKeys){
                        $curCount = $RecordingsHash.$key.count
                        $GetMinMax += $curCount
                        $textColor =  '#{0:X6}' -f (Get-Random -Maximum 0x1000000)

                        $tempstr += "`"$key`","
                        $tempstr2 += "$curCount,"
                        $tempstr3 += "`"green`","
                        $tempstr4 += "`"$textColor`","
                    }

                    if($GetMinMax.count -ne 0){
                        $GetMinMax = $GetMinMax | Sort-Object
                        $mintick = 0
                        $maxtick = $GetMinMax[$GetMinMax.Count - 1]
                    }
                    else{
                        $mintick = 0
                        $maxtick = 0
                    }

                    if(![String]::IsNullOrEmpty($tempstr)){
                        $tempstr = $tempstr.Substring(0,$tempstr.Length-1)
                    }
                    if(![String]::IsNullOrEmpty($tempstr2)){
                        $tempstr2 = $tempstr2.Substring(0,$tempstr2.Length-1)
                    }
                    if(![String]::IsNullOrEmpty($tempstr3)){
                        $tempstr3 = $tempstr3.Substring(0,$tempstr3.Length-1)
                    }
                    if(![String]::IsNullOrEmpty($tempstr4)){
                        $tempstr4 = $tempstr4.Substring(0,$tempstr4.Length-1)
                    }

                    $htmlData += @{
                        Recommendation1 = $recommendation1
                        Recommendation2 = $recommendation2
                        Recommendation3 = $recommendation3
                        titlesplit = $titlesplit
                        outputfile = $outputfile
                        metricTag = $metricTag
                        OutputDataJSON = $OutputDataJSON
                        curTime = $curTime
                        tablestr = $tablestr
                        metricexplanation = $metricexplanation
                        HTMLChart = $HTMLChart
                        tempstr = $tempstr
                        tempstr2 = $tempstr2
                        tempstr3 = $tempstr3
                        tempstr4 = $tempstr4
                        datahash = $RecordingsHash
                        maxtick = $maxtick
                        mintick = $mintick
                    }
                }
            }

            $datahash = $htmlData.datahash
            if($MetricFormat -eq "HTML" -or $MetricFormat -eq "ALL"){

                #OUTPUT DATA IN A PRETTY HTML
write-output "
<!DOCTYPE html>
<html>
<head>
<title>$TargetMetric</title>
<style>
    body {
        font-family: Arial, sans-serif;
        background-color: #c0c0c0;
        margin: 0;
        padding: 20px;
    }
    .metrics-container3 {
        background-color: #333;
        border-radius: 16px;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
        padding: 20px;
        margin: 0;
        color: white;
        font-size: 24px;
        font-weight: bold;
        Text-align: center;
    }
    .metrics-container2 {
        background-color: #333;
        border-radius: 16px;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
        padding: 20px;
        margin: 0;
        color: white;
    }
    .metrics-container {
        background-color: #fff;
        border-radius: 16px;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
        padding: 20px;
        margin: 0;
    }
    .metric {
        margin-bottom: 10px;
    }
    .metric-label {
        font-weight: bold;
    }
</style>
</head>
<script src=`"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js`"></script>
<body>
<div style=`"max-width: 1200px; width: 100%; margin: 0 auto;`">
    <div style=`"width: 95%; margin-right: 1%; margin-left: 1%`" class=`"metrics-container3`">
        $titlesplit <small><small>(Powered By Vpas)</small></small>
    </div>
    <br>
    <div style=`"display: flex; width: 99%;`">
        <div style=`"width:48%; margin-left: 1%; margin-right: 1%;`" class=`"metrics-container`">
            <div class=`"metric`">
                <span class=`"metric-label`">Generated By:</span> $AuthenticatedAs
            </div>
            <div class=`"metric`">
                <span class=`"metric-label`">Generated Date:</span> $curTime
            </div>
            <div class=`"metric`">
                <span class=`"metric-label`">Metric Category:</span> $metricTag
            </div>
            <div class=`"metric`">
                <span class=`"metric-label`">Output Table(s):</span> $tablestr
            </div>
"
 | Set-content $outputfile
                if(![String]::IsNullOrEmpty($TargetSafeSTR)){
write-output "
            <div class=`"metric`">
                <span class=`"metric-label`">Target Safe(s):</span> $TargetSafeSTR
            </div>
"
 | Add-content $outputfile
                }
                if(![String]::IsNullOrEmpty($TargetPlatformSTR)){
write-output "
            <div class=`"metric`">
                <span class=`"metric-label`">Target Platform(s):</span> $TargetPlatformSTR
            </div>
"
 | Add-content $outputfile
                }
                if(![String]::IsNullOrEmpty($TargetUsernameSTR)){
write-output "
            <div class=`"metric`">
                <span class=`"metric-label`">Target Username(s):</span> $TargetUsernameSTR
            </div>
"
 | Add-content $outputfile
                }
                if(![String]::IsNullOrEmpty($SkipPlatformsSTR)){
write-output "
            <div class=`"metric`">
                <span class=`"metric-label`">Ignore Platform(s):</span> $SkipPlatformsSTR
            </div>
"
 | Add-content $outputfile
                }
                if(![String]::IsNullOrEmpty($SkipUsernameSTR)){
write-output "
            <div class=`"metric`">
                <span class=`"metric-label`">Ignore Username(s):</span> $SkipUsernameSTR
            </div>
"
 | Add-content $outputfile
                }
                if(![String]::IsNullOrEmpty($SkipSafeSTR)){
write-output "
            <div class=`"metric`">
                <span class=`"metric-label`">Ignore Safe(s):</span> $SkipSafeSTR
            </div>
"
 | Add-content $outputfile
                }

write-output "
        </div>
 
        <div style=`"width:48%; margin-left: 1%; margin-right: 1;%`" class=`"metrics-container`">
            <div class=`"metric`">
                <span class=`"metric-label`">Metric Type:</span><small> $titlesplit</small>
            </div>
            <div class=`"metric`">
                <span class=`"metric-label`">Explanation:</span> <small>$metricexplanation</small>
            </div>
        </div>
    </div>
    <br>
"
 | Add-content $outputfile

                if($HTMLChart -eq "BarGraph" -or $HTMLChart -eq "ALL"){
                    write-output "<div style=`"max-width:95%; margin-right: 1%; margin-left: 1%`"class=`"metrics-container`"><canvas height=`"500%`" id=`"myChartBAR`" style=`"width:100%;`"></canvas></div>`n<br><br>`n" | Add-Content $outputfile
                }
                if($HTMLChart -eq "LineGraph" -or $HTMLChart -eq "ALL"){
                    write-output "<div style=`"max-width:95%; margin-right: 1%; margin-left: 1%`"class=`"metrics-container`"><canvas height=`"500%`" id=`"myChartLINE`" style=`"width:100%;`"></canvas></div>`n<br><br>`n"| Add-Content $outputfile
                }
                if($HTMLChart -eq "PieChart" -or $HTMLChart -eq "ALL"){
                    write-output "<div style=`"max-width:95%; margin-right: 1%; margin-left: 1%`"class=`"metrics-container`"><canvas height=`"500%`" id=`"myChartPIE`" style=`"width:100%;`"></canvas></div>`n<br><br>`n"| Add-Content $outputfile
                }

Write-Output "
    <div style=`"display: flex;`">
        <div style=`"width:33%; margin-right: 1%; margin-left: 1%`" class=`"metrics-container`">
            <div class=`"metric`">
                <span class=`"metric-label`">Totals:</span>
            </div>
"
 | Add-Content $outputfile

                if($TargetMetric -eq "PSMSessionsInXDays"){
                    $countkeys = $datahash.Keys.Count
                    $i = 1
                    while($i -le $countkeys){
                        $Max = $datahash."Set$i".Max
                        $Min = $datahash."Set$i".Min
                        $curCount = $datahash."Set$i".Count
Write-Output "
            <div class=`"metric`">
                <span class=`"metric-label`"><small>&emsp;Set$i) $Max-$Min`:</small></span><small> $curCount $tagout</small>
            </div>
"
 | Add-Content $outputfile
                        $i += 1
                    }
                }
                else{
                    foreach($key in $datahash.Keys){
                        $curCount = $datahash."$key".count

Write-Output "
            <div class=`"metric`">
                <span class=`"metric-label`"><small>&emsp;$key`:</small></span><small> $curCount $tagout</small>
            </div>
"
 | Add-Content $outputfile
                    }
                }

Write-Output "
        </div>
        <div style=`"max-width:58%; width: 68%; margin-right: 1%; margin-left: 1%`" class=`"metrics-container`">
            <div class=`"metric`">
                <span class=`"metric-label`">Recommendations:</span>
            </div>
            <div class=`"metric`">
                <span class=`"metric-label`"><small>&emsp;1)</small></span> <small>$recommendation1</small>
            </div>
            <div class=`"metric`">
                <span class=`"metric-label`"><small>&emsp;2)</small></span> <small>$recommendation2</small>
            </div>
            <div class=`"metric`">
                <span class=`"metric-label`"><small>&emsp;3)</small></span> <small>$recommendation3</small>
            </div>
        </div>
    </div>
    <br>
"
 | Add-Content $outputfile
if(!$HideRawData){
Write-Output "
    <div style=`"max-width:95%; width: 95%; margin-right: 1%; margin-left: 1%`" class=`"metrics-container`">
        <div class=`"metric`">
            <span class=`"metric-label`">Raw Data:</span>
            <div><button onclick=`"copyText()`">Copy JSON</button></div>
            <br>
            <div style=`"max-width:95%; width: 95%; margin-right: 1%; margin-left: 1%`" class=`"metrics-container2`">
            <span id=`"CopyText`" ><small>$OutputDataJSON</small></span>
            </div>
        </div>
    </div>
"
 | Add-Content $outputfile
}

Write-Output "
</div>
<script>
"
 | Add-Content $outputfile
                if($HTMLChart -eq "BarGraph" -or $HTMLChart -eq "ALL"){
                    write-output "const xValuesBAR = [$tempstr];" | Add-Content $outputfile
                    write-output "const yValuesBAR = [$tempstr2];" | Add-Content $outputfile
                    write-output "const barColorsBAR = [$tempstr3];" | Add-Content $outputfile
                }
                if($HTMLChart -eq "LineGraph" -or $HTMLChart -eq "ALL"){
                    write-output "const xValuesLINE = [$tempstr];" | Add-Content $outputfile
                    write-output "const yValuesLINE = [$tempstr2];" | Add-Content $outputfile
                }
                if($HTMLChart -eq "PieChart" -or $HTMLChart -eq "ALL"){
                    write-output "const xValuesPIE = [$tempstr];" | Add-Content $outputfile
                    write-output "const yValuesPIE = [$tempstr2];" | Add-Content $outputfile
                    write-output "const barColorsPIE = [$tempstr4];" | Add-Content $outputfile
                }

if($HTMLChart -eq "BarGraph" -or $HTMLChart -eq "ALL"){
#BAR
Write-Output "
new Chart(`"myChartBAR`", {
    type: `"bar`",
    data: {
        labels: xValuesBAR,
        datasets: [{
            backgroundColor: barColorsBAR,
            data: yValuesBAR
        }]
    },
    options: {
        scales: {
            yAxes: [{
                ticks: {
                    beginAtZero: true
                }
            }]
        },
        legend: {display: false},
        title: {
            display: true,
            text: `"$titlesplit`"
        }
    }
});
"
 | Add-Content $outputfile
}
if($HTMLChart -eq "LineGraph" -or $HTMLChart -eq "ALL"){
#LINE
Write-Output "
new Chart(`"myChartLINE`", {
    type: `"line`",
    data: {
        labels: xValuesLINE,
        datasets: [{
            label: `"$titlesplit`",
            fill: false,
            lineTension: 0,
            backgroundColor: `"rgba(0,0,255,1.0)`",
            borderColor: `"rgba(0,0,255,0.1)`",
            data: yValuesLINE
        }]
    },
    options: {
        legend: {display: true},
        scales: {
            yAxes: [{ticks: {min: $mintick, max:$maxtick}}],
        }
    }
});
"
 | Add-Content $outputfile
}
if($HTMLChart -eq "PieChart" -or $HTMLChart -eq "ALL"){
#PIE
write-output "
new Chart(`"myChartPIE`", {
    type: `"pie`",
    data: {
        labels: xValuesPIE,
        datasets: [{
            backgroundColor: barColorsPIE,
            data: yValuesPIE
        }]
    },
    options: {
        title: {
            display: true,
            text: `"$titlesplit`"
        }
    }
});
function copyText() {
        var copyText = document.getElementById(`"CopyText`");
        var textArea = document.createElement(`"textarea`");
        textArea.value = copyText.textContent;
        document.body.appendChild(textArea);
        textArea.select();
        document.execCommand(`"Copy`");
        textArea.remove();
        alert(`"JSON copied to clipboard`");
    }
"
 | Add-Content $outputfile
}


                write-output "</script>`n</body>`n</html>" | Add-Content $outputfile
            }
            return $datahash

        }catch{
            Write-Verbose "UNABLE TO RUN REPORT...RETURNING FALSE"
            Write-VPASOutput -str "UNABLE TO RUN REPORT...RETURNING FALSE" -type E
            Write-VPASOutput -str $_ -type E
            return $false
        }
    }
    End{
        $log = Write-VPASTextRecorder -inputval $CommandName -token $token -LogType DIVIDER
    }
}