public/Invoke-VPASMetricsCPM.ps1
<#
.Synopsis RUN VARIOUS CPM METRICS FROM CYBERARK CREATED BY: Vadim Melamed, EMAIL: vpasmodule@gmail.com .DESCRIPTION USE THIS FUNCTION TO GENERATE VARIOUS CPM RELATED METRICS FROM CYBERARK .LINK https://vpasmodule.com/commands/Invoke-VPASMetricsCPM .NOTES SelfHosted: TRUE PrivCloudStandard: TRUE SharedServices: TRUE .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: CPMAssignedToSafes, CPMAssignedToAccounts, CPMAccountManagementStatus .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 HideRawData Removes the RawData visual from the exported output Helpful when exporting to a PDF or document to remove extra not needed information .PARAMETER IgnoreSafes Wildcard value that will cause a record to be ignored from the metrics if the target record SafeName matches .PARAMETER SafeSearchQuery Wildcard value that will limit the metrics to only target records that match the searchquery via safe name .PARAMETER InputParameters HashTable of values containing the parameters required to make the API call .EXAMPLE $GenerateReport = Invoke-VPASMetricsCPM -TargetMetric CPMAccountManagementStatus -MetricFormat ALL -OutputDirectory C:\Temp\Metrics\CPMMetrics .EXAMPLE $GenerateReport = Invoke-VPASMetricsCPM -TargetMetric CPMAssignedToAccounts -MetricFormat ALL -OutputDirectory C:\Temp\Metrics\CPMMetrics .EXAMPLE $GenerateReport = Invoke-VPASMetricsCPM -TargetMetric CPMAssignedToSafes -MetricFormat ALL -OutputDirectory C:\Temp\Metrics\CPMMetrics .EXAMPLE $InputParameters = @{ TargetMetric = "CPMAssignedToSafes"|"CPMAssignedToAccounts"|"CPMAccountManagementStatus" MetricFormat = "JSON"|"HTML"|"ALL"|"NONE" OutputDirectory = "C:\temp\ReportOutputs" HideRawData = $true|$false IgnoreSafes = @("VaultInternal","System") SafeSearchQuery = @("vman-","vpas-") } $GenerateReport = Invoke-VPASMetricsCPM -InputParameters $InputParameters .OUTPUTS HashTable object if successful --- $false if failed #> function Invoke-VPASMetricsCPM{ [OutputType([bool])] [CmdletBinding(DefaultParameterSetName='Set1')] Param( [Parameter(Mandatory=$true,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true,HelpMessage="Enter TargetMetric to be generated (CPMAssignedToSafes, CPMAssignedToAccounts, CPMAccountManagementStatus)")] [ValidateSet('CPMAssignedToSafes','CPMAssignedToAccounts','CPMAccountManagementStatus')] [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=$false,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true)] [switch]$HideRawData, [Parameter(Mandatory=$false,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true)] [String[]]$IgnoreSafes, [Parameter(Mandatory=$false,ParameterSetName='Set1',ValueFromPipelineByPropertyName=$true)] [String[]]$SafeSearchQuery, [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","HideRawData","IgnoreSafes","SafeSearchQuery") 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{ $SkipSafeSTR = "" foreach($rec in $IgnoreSafes){ $SkipSafeSTR += "*" + $rec + "*; " } $TargetSafeSTR = "" foreach($rec in $SafeSearchQuery){ $TargetSafeSTR += "*" + $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 = "PieChart" } $htmlData = @{} if($TargetMetric -eq "CPMAssignedToSafes"){ $tagout = "safes" #INITIALIZE HASH $OutputHash = @{} if($SafeSearchQuery.count -gt 0){ $SafeMatrix = @() $AllSafes = @{ value = @() count = 0 } foreach($query in $SafeSearchQuery){ $smallquery = Get-VPASSafes -searchQuery $query foreach($rec in $smallquery.value){ $recSafeName = $rec.safename if(!$SafeMatrix.Contains($recSafeName)){ $AllSafes.value += $rec $AllSafes.Count += 1 $SafeMatrix += $recSafeName } } } } else{ $AllSafes = Get-VPASAllSafes } if(!$AllSafes){ Write-Verbose "FAILED TO RETRIEVE SAFES" Write-VPASOutput -str "FAILED TO RETRIEVE SAFES" -type E Write-VPASOutput -str $_ -type E return $false } foreach($rec in $AllSafes.value){ $targetCPM = $rec.managingCPM $targetSafe = $rec.safeName $skipval = $false if(!$skipval){ foreach($ignoreval in $IgnoreSafes){ if($targetSafe -match $ignoreval){ Write-Verbose "SKIPPING $targetSafe : SKIP VALUE $ignoreval FOUND IN SAFE $targetSafe" $skipval = $true } } } if(!$skipval){ if([string]::IsNullOrEmpty($targetCPM)){ $targetCPM = "NONE" } if($targetCPM -match ","){ $targetCPMSPlit = $targetCPM -split "," foreach($minirec in $targetCPMSPlit){ if($OutputHash.$minirec){ #$OutputHash.$minirec += 1 $OutputHash.$minirec.counter += 1 $OutputHash.$minirec.RawData += $rec } else{ $OutputHash += @{ #$minirec = 1 $minirec = @{ counter = 1 RawData = @($rec) } } } } } else{ if($OutputHash.$targetCPM){ #$OutputHash.$targetCPM += 1 $OutputHash.$targetCPM.counter += 1 $OutputHash.$targetCPM.RawData += $rec } else{ $OutputHash += @{ #$targetCPM = 1 $targetCPM = @{ counter = 1 RawData = @($rec) } } } } } } if($MetricFormat -eq "JSON" -or $MetricFormat -eq "ALL"){ $outputfile = "$OutputDirectory/CPMAssignedToSafes.json" $OutputDataJSON = $OutputHash | ConvertTo-Json -Depth 100 Write-Output $OutputDataJSON | Set-Content $outputfile } if($MetricFormat -eq "NONE"){ $htmlData += @{ datahash = $OutputHash } } if($MetricFormat -eq "HTML" -or $MetricFormat -eq "ALL"){ $outputfile = "$OutputDirectory/CPMAssignedToSafes.html" $titlesplit = "CPMs Assigned To Safes" $metricTag = "CPM Metrics" $recommendation1 = "Assigning a CPM to a safe gives the ability to automate password rotation policies. The CPM ensures that passwords stored within the safe are regularly rotated according to predefined schedules or trigger events. This reduces the risk of password-based attacks, such as credential theft or misuse, by ensuring that passwords are frequently changed and not reused across multiple systems." $recommendation2 = "Assigning a CPM to a safe enables organizations to define and enforce password complexity requirements at the safe level. Administrators can configure policies specifying minimum length, character types (uppercase, lowercase, digits, special characters, etc), and prohibited patterns. This ensures that passwords stored within the safe meet predefined strength criteria, making them more resilient to brute-force attacks and dictionary attacks." $recommendation3 = "CPM enables organizations to enforce password history and reuse policies at the safe level. Administrators can configure policies specifying the number of previous passwords to retain, as well as restrictions on password reuse within a defined timeframe. This prevents users from reusing old passwords and enhances security by reducing the risk of password-based attacks" $OutputDataJSON = $OutputHash | 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 number of safes different CPMs are assigned to<br>(Note - this metric will count a safe multiple times if multiple CPMs are assigned to the safe)" $tempstr = "" $tempstr2 = "" $tempstr3 = "" $tempstr4 = "" $AllKeys = $OutputHash.Keys $GetMinMax = @() foreach($key in $AllKeys){ $curCount = $OutputHash.$key.counter $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 = $OutputHash maxtick = $maxtick mintick = $mintick } } } if($TargetMetric -eq "CPMAssignedToAccounts"){ $tagout = "accounts" #INITIALIZE HASH $OutputHash = @{} if($SafeSearchQuery.count -gt 0){ $SafeMatrix = @() $AllSafes = @{ value = @() count = 0 } foreach($query in $SafeSearchQuery){ $smallquery = Get-VPASSafes -searchQuery $query -IncludeAccounts foreach($rec in $smallquery.value){ $recSafeName = $rec.safename if(!$SafeMatrix.Contains($recSafeName)){ $AllSafes.value += $rec $AllSafes.Count += 1 $SafeMatrix += $recSafeName } } } } else{ $AllSafes = Get-VPASAllSafes -IncludeAccounts } if(!$AllSafes){ Write-Verbose "FAILED TO RETRIEVE SAFES" Write-VPASOutput -str "FAILED TO RETRIEVE SAFES" -type E Write-VPASOutput -str $_ -type E return $false } foreach($rec in $AllSafes.value){ $safename = $rec.safeName $targetCPM = $rec.managingCPM $acctCount = $rec.accounts.count $skipval = $false if(!$skipval){ foreach($ignoreval in $IgnoreSafes){ if($safename -match $ignoreval){ Write-Verbose "SKIPPING $safename : SKIP VALUE $ignoreval FOUND IN SAFE $safename" $skipval = $true } } } if(!$skipval){ if($safename -ne "PasswordManager_Pending"){ if($acctCount){ if([string]::IsNullOrEmpty($targetCPM)){ $targetCPM = "NONE" } if($targetCPM -match ","){ $targetCPMSPlit = $targetCPM -split "," foreach($minirec in $targetCPMSPlit){ if($OutputHash.$minirec){ #$OutputHash.$minirec += $acctCount $OutputHash.$minirec.counter += $acctCount $OutputHash.$minirec.RawData += $rec } else{ $OutputHash += @{ #$minirec = $acctCount $minirec = @{ counter = $acctCount RawData = @($rec) } } } } } else{ if($OutputHash.$targetCPM){ #$OutputHash.$targetCPM += $acctCount $OutputHash.$targetCPM.counter += $acctCount $OutputHash.$targetCPM.RawData += $rec } else{ $OutputHash += @{ #$targetCPM = $acctCount $targetCPM = @{ counter = $acctCount RawData = @($rec) } } } } } } } } if($MetricFormat -eq "JSON" -or $MetricFormat -eq "ALL"){ $outputfile = "$OutputDirectory/CPMAssignedToAccounts.json" $OutputDataJSON = $OutputHash | ConvertTo-Json -Depth 100 Write-Output $OutputDataJSON | Set-Content $outputfile } if($MetricFormat -eq "NONE"){ $htmlData += @{ datahash = $OutputHash } } if($MetricFormat -eq "HTML" -or $MetricFormat -eq "ALL"){ $outputfile = "$OutputDirectory/CPMAssignedToAccounts.html" $titlesplit = "CPMs Assigned To Accounts" $metricTag = "CPM Metrics" $recommendation1 = "Having a CPM manage an account gives the ability for automatic password generation capabilities, allowing organizations to generate strong, random passwords for accounts stored within the safe. This eliminates the need for users to manually create passwords, reducing the risk of weak or predictable passwords being used." $recommendation2 = "CPM provides the capability to enable a One-Time Password flow, which is a password that is for single-use or for a limited duration. This is commonly used to enhance an organization's security posture." $recommendation3 = "Depending on the server specifications that the CPM is deployed to (assuming large implementation), a single CPM can handle about 100,000 accounts. An organization should consider deploying multiple CPMs to handle the workload for environments with large amount of accounts. Keep in mind, only one CPM should be assigned to a safe" $OutputDataJSON = $OutputHash | 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 number of accounts different CPMs are managing<br>(Note - an account will count multiple times if multiple CPMs are assigned at safe level)" $tempstr = "" $tempstr2 = "" $tempstr3 = "" $tempstr4 = "" $AllKeys = $OutputHash.Keys $GetMinMax = @() foreach($key in $AllKeys){ $curCount = $OutputHash.$key.counter $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 = $OutputHash maxtick = $maxtick mintick = $mintick } } } if($TargetMetric -eq "CPMAccountManagementStatus"){ $tagout = "accounts" #INITIALIZE DATE HASH $AccountsHash = @{ ManagedAccounts = @{ counter = 0 RawData = @() } NoCPMAssigned = @{ counter = 0 RawData = @() } NoPlatformActions = @{ counter = 0 RawData = @() } InactivePlatform = @{ counter = 0 RawData = @() } AccountDisabled = @{ counter = 0 RawData = @() } Unknown = @{ counter = 0 RawData = @() } } if($SafeSearchQuery.count -gt 0){ $AccountMatrix = @() $TempAllAccounts = Get-VPASAllAccounts if(!$TempAllAccounts){ Write-Verbose "FAILED TO PULL ALL ACCOUNTS" Write-VPASOutput -str "FAILED TO PULL ALL ACCOUNTS" -type E Write-VPASOutput -str $_ -type E return $false } $AllAccounts = @{ value = @() count = 0 } foreach($rec in $TempAllAccounts.value){ $recSafeName = $rec.safename $recAcctID = $rec.id foreach($query in $SafeSearchQuery){ if($recSafeName -match $query){ if(!$AccountMatrix.Contains($recAcctID)){ $AllAccounts.value += $rec $AllAccounts.count += 1 $AccountMatrix += $recAcctID } } } } } else{ $AllAccounts = Get-VPASAllAccounts if(!$AllAccounts){ Write-Verbose "FAILED TO PULL ALL ACCOUNTS" Write-VPASOutput -str "FAILED TO PULL ALL ACCOUNTS" -type E Write-VPASOutput -str $_ -type E return $false } } $SafeHash = @{} $PlatformHash = @{} if($SafeSearchQuery.count -gt 0){ $SafeMatrix = @() $AllSafes = @{ value = @() count = 0 } foreach($query in $SafeSearchQuery){ $smallquery = Get-VPASSafes -searchQuery $query foreach($rec in $smallquery.value){ $recSafeName = $rec.safename if(!$SafeMatrix.Contains($recSafeName)){ $AllSafes.value += $rec $AllSafes.Count += 1 $SafeMatrix += $recSafeName } } } } else{ $AllSafes = Get-VPASAllSafes } if(!$AllSafes){ Write-Verbose "FAILED TO RETRIEVE SAFES" Write-VPASOutput -str "FAILED TO RETRIEVE SAFES" -type E Write-VPASOutput -str $_ -type E return $false } foreach($safe in $AllSafes.value){ $recSafeName = $safe.safeName if($recSafeName -ne "PasswordManager_Pending"){ if(!$SafeHash.$recSafeName){ $managingCPM = $safe.managingCPM if([String]::IsNullOrEmpty($managingCPM)){ $managingCPM = "NONE" } $SafeHash += @{ $recSafeName = $managingCPM } } } } $AllPlatforms = Get-VPASAllPlatforms if(!$AllPlatforms){ Write-Verbose "FAILED TO PULL ALL PLATFORMS" Write-VPASOutput -str "FAILED TO PULL ALL PLATFORMS" -type E Write-VPASOutput -str $_ -type E return $false } foreach($plat in $AllPlatforms.Platforms){ $recPlatformID = $plat.general.id if(!$PlatformHash.$recPlatformID){ $val = $plat.privilegedAccessWorkflows.enforceOnetimePasswordAccess if($val){ $OTP = "YES" } else{ $OTP = "NO" } $val = $plat.general.active if($val){ $Active = "YES" } else{ $Active = "NO" } $val = $plat.credentialsManagement.performPeriodicChange if($val){ $ChangeStyle = "AUTOMATIC" } else{ $ChangeStyle = "MANUAL" } $val = $plat.credentialsManagement.performPeriodicVerification if($val){ $VerifyStyle = "AUTOMATIC" } else{ $VerifyStyle = "MANUAL" } $PlatformHash += @{ $recPlatformID = @{ Change = $ChangeStyle Verify = $VerifyStyle OneTimePassword = $OTP Active = $Active } } } } $PlatformHash += @{ "BLANK_PLATFORM" = @{ Change = "MANUAL" Verify = "MANUAL" OneTimePassword = "NO" Active = "YES" } } foreach($acct in $AllAccounts.value){ $recPlatformID = $acct.platformId $recAcctID = $acct.id $recSafeName = $acct.safeName $recStatus = $acct.secretManagement.automaticManagementEnabled $skipval = $false if(!$skipval){ foreach($ignoreval in $IgnoreSafes){ if($recSafeName -match $ignoreval){ Write-Verbose "SKIPPING $recSafeName : SKIP VALUE $ignoreval FOUND IN SAFE $recSafeName" $skipval = $true } } } if(!$skipval){ if([String]::IsNullOrEmpty($recPlatformID)){ $recPlatformID = "BLANK_PLATFORM" } #CALCULATE STATUS $UnknownFlag = $false $recordedEntry = $false #FIRST PASS, NO CPM ON SAFE if(!$recordedEntry){ if($SafeHash.$recSafeName){ if($SafeHash.$recSafeName -eq "NONE"){ #$AccountsHash.NoCPMAssigned += 1 $AccountsHash.NoCPMAssigned.counter += 1 $AccountsHash.NoCPMAssigned.RawData += $acct $UnknownFlag = $false $recordedEntry = $true } } else{ $UnknownFlag = $true } } #SECOND PASS, PLATFORM ACTIVE OR NOT if(!$recordedEntry){ if($PlatformHash.$recPlatformID){ if($PlatformHash.$recPlatformID.Active -eq "NO"){ #$AccountsHash.InactivePlatform += 1 $AccountsHash.InactivePlatform.counter += 1 $AccountsHash.InactivePlatform.RawData += $acct $UnknownFlag = $false $recordedEntry = $true } } else{ $UnknownFlag = $true } } #THIRD PASS, PLATFORM ACTIONS if(!$recordedEntry){ if($PlatformHash.$recPlatformID){ if($PlatformHash.$recPlatformID.Change -eq "MANUAL" -and $PlatformHash.$recPlatformID.Verify -eq "MANUAL" -and $PlatformHash.$recPlatformID.OneTimePassword -eq "NO"){ #$AccountsHash.NoPlatformActions += 1 $AccountsHash.NoPlatformActions.counter += 1 $AccountsHash.NoPlatformActions.RawData += $acct $UnknownFlag = $false $recordedEntry = $true } } else{ $UnknownFlag = $true } } #FOURTH PASS, ACCOUNT IS INACTIVE if(!$recordedEntry){ if(!$recStatus){ #$AccountsHash.AccountDisabled += 1 $AccountsHash.AccountDisabled.counter += 1 $AccountsHash.AccountDisabled.RawData += $acct $UnknownFlag = $false $recordedEntry = $true } } #FIFTH/SIXTH PASS, UNKNOWN/MANAGED if(!$recordedEntry){ if($UnknownFlag){ #$AccountsHash.Unknown += 1 $AccountsHash.Unknown.counter += 1 $AccountsHash.Unknown.RawData += $acct } else{ #$AccountsHash.ManagedAccounts += 1 $AccountsHash.ManagedAccounts.counter += 1 $AccountsHash.ManagedAccounts.RawData += $acct } } } } if($MetricFormat -eq "JSON" -or $MetricFormat -eq "ALL"){ $outputfile = "$OutputDirectory/CPMAccountManagementStatus.json" $OutputDataJSON = $AccountsHash | ConvertTo-Json -Depth 100 Write-Output $OutputDataJSON | Set-Content $outputfile } if($MetricFormat -eq "NONE"){ $htmlData += @{ datahash = $OutputHash } } if($MetricFormat -eq "HTML" -or $MetricFormat -eq "ALL"){ $outputfile = "$OutputDirectory/CPMAccountManagementStatus.html" $titlesplit = "CPM Account Management Status" $metricTag = "CPM Metrics" $recommendation1 = "Verification ensures that the credentials stored in the Vault are accurate and up-to-date. This helps prevent potential issues that may arise from using outdated or incorrect credentials, such as authentication failures or access denials. Consider setting up verification for accounts that are not rotating" $recommendation2 = "Configure automated password rotation policies for privileged accounts within CyberArk. By regularly rotating passwords, you can help ensure that accounts remain active and accessible while also enhancing security by reducing the risk of credential compromise." $recommendation3 = "The CPM automates various management tasks related to the safe, such as password rotation, access provisioning, and policy enforcement. By assigning a CPM to a safe, you can streamline these management processes and ensure consistency in policy enforcement across your organization's CyberArk environment." $OutputDataJSON = $AccountsHash | 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 current account management status of accounts in the system. The accounts do NOT double count (meaning for example an account will not be recorded as `"NoCPMAssigned`" as well as `"AccountDisabled`").<br>Order of priority goes: NoCPMAssigned > InactivePlatform > NoPlatformActions > AccountDisabled > Managed > Unknown" $tempstr = "" $tempstr2 = "" $tempstr3 = "" $tempstr4 = "" $AllKeys = $AccountsHash.Keys $AmtKeys = $AllKeys.Count $counter = $AmtKeys $GetMinMax = @() foreach($key in $AllKeys){ $curCount = $AccountsHash.$key.counter $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 = $AccountsHash maxtick = $maxtick mintick = $mintick } } } #ADDING FIX TO LIMIT OUTPUT LABEL QUANTITY...top10 $datalimit = 9 $newtempstrsplit = $tempstr -split "," $newtempstr2split = $tempstr2 -split "," $newtempstr4split = $tempstr4 -split "," if($newtempstrsplit.Count -gt $datalimit){ $stiched = for($i = 0; $i -lt $newtempstrsplit.Count; $i++){ [PSCustomObject]@{ Label = $newtempstrsplit[$i] Value = [int]$newtempstr2split[$i] Color = $newtempstr4split[$i] } } $topValues = $stiched | Sort-Object -Property Value -Descending | Select-Object -First ($newtempstrsplit.Count) $tempstr = "" $tempstr2 = "" $tempstr4 = "" $tempcount = 0 for($i = 0; $i -lt $topValues.Count; $i++){ $splitval = $topValues[$i].Value $splitlabel = $topValues[$i].Label $splitcolor = $topValues[$i].Color if($i -lt $datalimit){ $tempstr += "$splitlabel," $tempstr2 += "$splitval," $tempstr4 += "$splitcolor," } else{ $tempcount += $splitval } } $lastcolor = $topValues[$datalimit].Color $tempstr += "`"Other`"," $tempstr2 += "$tempcount," $tempstr4 += "$lastcolor," 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($tempstr4)){ $tempstr4 = $tempstr4.Substring(0,$tempstr4.Length-1) } } $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; } button { padding: 8px 12px; background-color: #444; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; } button:hover { background-color: #666; } canvas { max-width: 100%; height: auto; } #totalsTable tbody tr:nth-child(even) { background-color: #f2f2f2; } #totalsTable tbody tr:nth-child(odd) { background-color: #ffffff; } .dt-row-dark { background-color: #2c2c2c !important; color: #e0e0e0 !important; } .dt-row-light { background-color: #1a1a1a !important; color: #e0e0e0 !important; } .dataTables_filter input { background-color: #ffffff; color: #000000; border: 1px solid #ccc; border-radius: 6px; padding: 5px 8px; } body.dark-mode .dataTables_filter input { background-color: #2c2c2c; color: #e0e0e0; border: 1px solid #555; } .dataTables_filter input::placeholder { color: #999; } body.dark-mode .dataTables_filter input::placeholder { color: #bbb; } body.dark-mode .dataTables_filter label { color: white; } </style> </head> <link rel=`"stylesheet`" type=`"text/css`" href=`"https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css`"> <script src=`"https://code.jquery.com/jquery-3.7.0.min.js`"></script> <script src=`"https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js`"></script> <script src=`"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js`"></script> <script src=`"https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@0.7.0`"></script> <body> <div style=`"max-width: 1200px; width: 100%; margin: 0 auto;`"> <div style=`"width: 95%; margin-right: 1%; margin-left: 1%; display: flex; justify-content: space-between; align-items: center;`" class=`"metrics-container3`"> <div style=`"margin-left: 20%;`">$titlesplit <small><small>(Powered By Vpas)</small></small></div> <button onclick=`"toggleDarkMode()`" id=`"darkModeBtn`" style=`"padding: 8px 12px; background-color: #444; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: bold;`">Dark Mode</button> </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> " | 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($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> <div style=`"display: flex; align-items: flex-start; justify-content: space-between; max-width: 95%; margin: 0 auto;`" class=`"metrics-container`"> <div style=`"width: 10%;`"> <button onclick=`"toggleLabels()`" style=`"width: 100%; height: 40px;`">Toggle Labels</button> <button onclick=`"regenerateColors()`" style=`"width: 100%; height: 40px; margin-top: 10px;`">Randomize</button> </div> <!-- Chart container --> <div style=`"flex: 1;`"> <canvas id=`"myChartPIE`" width=`"400`" height=`"400`"></canvas> </div> <div style=`"display: flex; flex-direction: column; gap: 10px; margin-left: 20px;`"> <button onclick=`"setChartType('pie')`">Pie</button> <button onclick=`"setChartType('doughnut')`">Doughnut</button> <button onclick=`"setChartType('line')`">Line</button> <button onclick=`"setChartType('bar')`">Bar</button> </div> </div> <br><br> " | Add-Content $outputfile Write-Output " <div style=`"max-width: 95%; margin-left: 0.75%; margin-top: -1%; margin-right: 0.75%`" class=`"metrics-container`"> <span class=`"metric-label`">Totals:</span> <div id=`"totalsTableWrapper`" style=`"margin-left: 10%; width: 80%; max-height: 300px; overflow-y: auto; transition: max-height 0.5s ease; color:black;`"> <table id=`"totalsTable`" style=`"width: 100%;`"> <thead> <tr> <th>Value</th> <th>Count</th> </tr> </thead> <tbody> " | Add-Content $outputfile foreach($key in $datahash.Keys){ $curCount = $datahash."$key".counter Write-Output " <tr><td>$key</td><td>$curCount</td></tr>" | Add-Content $outputfile } Write-Output " </tbody> </table> </div> <div style=`"text-align: right; margin-top: 10px;`"> <button id=`"toggleButton`" onclick=`"toggleTable()`">Expand</button> </div> </div> <div style=`"max-width: 95%; margin-left: 0.75%; margin-top: 2%; margin-right: 0.75%`" class=`"metrics-container`"> <div class=`"metric`"> <span class=`"metric-label`">Recommendations:</span> </div> <div class=`"metric`"> <span class=`"metric-label`"><small> 1)</small></span> <small>$recommendation1</small> </div> <div class=`"metric`"> <span class=`"metric-label`"><small> 2)</small></span> <small>$recommendation2</small> </div> <div class=`"metric`"> <span class=`"metric-label`"><small> 3)</small></span> <small>$recommendation3</small> </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> const xValues = [$tempstr]; const yValues = [$tempstr2]; const colors = [$tempstr4]; let currentChart; let labelsVisible = true; let chartInstance; let currentType = `"pie`"; let darkMode = false; function renderChart(type) { if (chartInstance) { chartInstance.destroy(); } const chartFontColor = darkMode ? '#ffffff' : '#333333'; chartInstance = new Chart(`"myChartPIE`", { type: type, data: { labels: xValues, datasets: [{ backgroundColor: type === 'line' || type === 'bar' ? colors[0] : colors, borderColor: type === 'line' ? (darkMode ? '#ffffff' : '#000000') : '#ffffff', borderWidth: 2, hoverBorderColor: `"#000`", hoverBorderWidth: 2, data: yValues, fill: false }] }, options: { responsive: true, maintainAspectRatio: false, layout: { padding: { top: 10, bottom: 10 } }, title: { display: true, text: `"$titlesplit`", fontSize: 18, fontStyle: `"bold`", fontColor: chartFontColor, padding: 20 }, legend: { display: !(type === 'bar' || type === 'line'), position: 'bottom', labels: { fontColor: chartFontColor, fontSize: 14, padding: 20 } }, plugins: { datalabels: { display: labelsVisible, color: 'white', backgroundColor: 'rgba(0, 0, 0, 0.7)', borderRadius: 6, padding: { top: 6, bottom: 6, left: 10, right: 10 }, font: { weight: 'bold', size: 13 }, formatter: function(value, context) { return context.chart.data.labels[context.dataIndex] + ': ' + value; } } }, tooltips: { backgroundColor: '#000', titleFontSize: 14, bodyFontSize: 13, cornerRadius: 4, caretSize: 6, xPadding: 12, yPadding: 8 }, scales: (type === 'line' || type === 'bar') ? { yAxes: [{ ticks: { beginAtZero: true, fontColor: chartFontColor }, gridLines: { color: '#ccc' } }], xAxes: [{ ticks: { fontColor: chartFontColor }, gridLines: { color: '#ccc' } }] } : {} }, plugins: [ChartDataLabels] }); } function setChartType(type) { currentType = type; renderChart(currentType); } function toggleLabels() { labelsVisible = !labelsVisible; renderChart(currentType); } // Initialize with pie chart Chart.defaults.global.maintainAspectRatio = false; renderChart(currentType); 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`"); } function getRandomColor() { // Generate a random color in RGB format const r = Math.floor(Math.random() * 256); const g = Math.floor(Math.random() * 256); const b = Math.floor(Math.random() * 256); return { r, g, b, hex: rgbToHex(r, g, b) }; } function rgbToHex(r, g, b) { return `"#`" + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join(''); } function colorDistance(c1, c2) { // Euclidean distance in RGB space return Math.sqrt( Math.pow(c1.r - c2.r, 2) + Math.pow(c1.g - c2.g, 2) + Math.pow(c1.b - c2.b, 2) ); } function regenerateColors() { const minDistance = 100; // Adjust for stricter color difference const newColors = []; while (newColors.length < xValues.length) { let newColor = getRandomColor(); let isDistinct = newColors.every(existing => colorDistance(existing, newColor) >= minDistance); if (isDistinct) { newColors.push(newColor); } } for (let i = 0; i < newColors.length; i++) { colors[i] = newColors[i].hex; } renderChart(currentType); } `$(document).ready(function() { `$('#totalsTable').DataTable({ paging: false, searching: true, info: false, order: [[1, 'desc']], columnDefs: [ { targets: 1, className: 'dt-body-right' } ] }); }); let expanded = false; function toggleTable() { const wrapper = document.getElementById('totalsTableWrapper'); const button = document.getElementById('toggleButton'); if (!expanded) { wrapper.style.maxHeight = `"none`"; wrapper.style.overflowY = `"visible`"; button.textContent = `"Collapse`"; } else { wrapper.style.maxHeight = `"300px`"; wrapper.style.overflowY = `"auto`"; button.textContent = `"Expand`"; } expanded = !expanded; } function toggleDarkMode() { darkMode = !darkMode; document.body.classList.toggle(`"dark-mode`", darkMode); if (darkMode) { document.body.style.backgroundColor = `"#121212`"; document.body.style.color = `"#e0e0e0`"; document.querySelectorAll('.metrics-container, .metrics-container2, .metrics-container3, #totalsTable thead th').forEach(el => { el.style.backgroundColor = `"#1e1e1e`"; el.style.color = `"#e0e0e0`"; }); document.getElementById('totalsTableWrapper').style.backgroundColor = `"#1e1e1e`"; `$('#totalsTable').DataTable().destroy(); `$('#totalsTable').DataTable({ paging: false, searching: true, info: false, order: [[1, 'desc']], columnDefs: [ { targets: 1, className: 'dt-body-right' } ], `"stripeClasses`": ['dt-row-dark', 'dt-row-light'] }); Chart.defaults.global.defaultFontColor = '#e0e0e0'; renderChart(currentType); document.getElementById('darkModeBtn').innerText = `"Light Mode`"; } else { document.body.style.backgroundColor = `"#c0c0c0`"; document.body.style.color = `"#000`"; document.querySelectorAll('.metrics-container, .metrics-container2, .metrics-container3, #totalsTable thead th').forEach(el => { el.style.backgroundColor = `"#fff`"; el.style.color = `"#000`"; }); document.getElementById('totalsTableWrapper').style.backgroundColor = `"#fff`"; `$('#totalsTable').DataTable().destroy(); `$('#totalsTable').DataTable({ paging: false, searching: true, info: false, order: [[1, 'desc']], columnDefs: [ { targets: 1, className: 'dt-body-right' } ], `"stripeClasses`": [] }); Chart.defaults.global.defaultFontColor = '#333'; renderChart(currentType); document.getElementById('darkModeBtn').innerText = `"Dark Mode`"; } } " | 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 } } # SIG # Begin signature block # MIIrpgYJKoZIhvcNAQcCoIIrlzCCK5MCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUxN2dDWIiqsDw/ntULyax7b48 # yOuggiTgMIIFbzCCBFegAwIBAgIQSPyTtGBVlI02p8mKidaUFjANBgkqhkiG9w0B # AQwFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVy # MRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEh # MB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTIxMDUyNTAwMDAw # MFoXDTI4MTIzMTIzNTk1OVowVjELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3Rp # Z28gTGltaXRlZDEtMCsGA1UEAxMkU2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5n # IFJvb3QgUjQ2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjeeUEiIE # JHQu/xYjApKKtq42haxH1CORKz7cfeIxoFFvrISR41KKteKW3tCHYySJiv/vEpM7 # fbu2ir29BX8nm2tl06UMabG8STma8W1uquSggyfamg0rUOlLW7O4ZDakfko9qXGr # YbNzszwLDO/bM1flvjQ345cbXf0fEj2CA3bm+z9m0pQxafptszSswXp43JJQ8mTH # qi0Eq8Nq6uAvp6fcbtfo/9ohq0C/ue4NnsbZnpnvxt4fqQx2sycgoda6/YDnAdLv # 64IplXCN/7sVz/7RDzaiLk8ykHRGa0c1E3cFM09jLrgt4b9lpwRrGNhx+swI8m2J # mRCxrds+LOSqGLDGBwF1Z95t6WNjHjZ/aYm+qkU+blpfj6Fby50whjDoA7NAxg0P # OM1nqFOI+rgwZfpvx+cdsYN0aT6sxGg7seZnM5q2COCABUhA7vaCZEao9XOwBpXy # bGWfv1VbHJxXGsd4RnxwqpQbghesh+m2yQ6BHEDWFhcp/FycGCvqRfXvvdVnTyhe # Be6QTHrnxvTQ/PrNPjJGEyA2igTqt6oHRpwNkzoJZplYXCmjuQymMDg80EY2NXyc # uu7D1fkKdvp+BRtAypI16dV60bV/AK6pkKrFfwGcELEW/MxuGNxvYv6mUKe4e7id # FT/+IAx1yCJaE5UZkADpGtXChvHjjuxf9OUCAwEAAaOCARIwggEOMB8GA1UdIwQY # MBaAFKARCiM+lvEH7OKvKe+CpX/QMKS0MB0GA1UdDgQWBBQy65Ka/zWWSC8oQEJw # IDaRXBeF5jAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zATBgNVHSUE # DDAKBggrBgEFBQcDAzAbBgNVHSAEFDASMAYGBFUdIAAwCAYGZ4EMAQQBMEMGA1Ud # HwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0FBQUNlcnRpZmlj # YXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0 # cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBDAUAA4IBAQASv6Hvi3Sa # mES4aUa1qyQKDKSKZ7g6gb9Fin1SB6iNH04hhTmja14tIIa/ELiueTtTzbT72ES+ # BtlcY2fUQBaHRIZyKtYyFfUSg8L54V0RQGf2QidyxSPiAjgaTCDi2wH3zUZPJqJ8 # ZsBRNraJAlTH/Fj7bADu/pimLpWhDFMpH2/YGaZPnvesCepdgsaLr4CnvYFIUoQx # 2jLsFeSmTD1sOXPUC4U5IOCFGmjhp0g4qdE2JXfBjRkWxYhMZn0vY86Y6GnfrDyo # XZ3JHFuu2PMvdM+4fvbXg50RlmKarkUT2n/cR/vfw1Kf5gZV6Z2M8jpiUbzsJA8p # 1FiAhORFe1rYMIIGFDCCA/ygAwIBAgIQeiOu2lNplg+RyD5c9MfjPzANBgkqhkiG # 9w0BAQwFADBXMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVk # MS4wLAYDVQQDEyVTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIFJvb3QgUjQ2 # MB4XDTIxMDMyMjAwMDAwMFoXDTM2MDMyMTIzNTk1OVowVTELMAkGA1UEBhMCR0Ix # GDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEsMCoGA1UEAxMjU2VjdGlnbyBQdWJs # aWMgVGltZSBTdGFtcGluZyBDQSBSMzYwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAw # ggGKAoIBgQDNmNhDQatugivs9jN+JjTkiYzT7yISgFQ+7yavjA6Bg+OiIjPm/N/t # 3nC7wYUrUlY3mFyI32t2o6Ft3EtxJXCc5MmZQZ8AxCbh5c6WzeJDB9qkQVa46xiY # Epc81KnBkAWgsaXnLURoYZzksHIzzCNxtIXnb9njZholGw9djnjkTdAA83abEOHQ # 4ujOGIaBhPXG2NdV8TNgFWZ9BojlAvflxNMCOwkCnzlH4oCw5+4v1nssWeN1y4+R # laOywwRMUi54fr2vFsU5QPrgb6tSjvEUh1EC4M29YGy/SIYM8ZpHadmVjbi3Pl8h # JiTWw9jiCKv31pcAaeijS9fc6R7DgyyLIGflmdQMwrNRxCulVq8ZpysiSYNi79tw # 5RHWZUEhnRfs/hsp/fwkXsynu1jcsUX+HuG8FLa2BNheUPtOcgw+vHJcJ8HnJCrc # UWhdFczf8O+pDiyGhVYX+bDDP3GhGS7TmKmGnbZ9N+MpEhWmbiAVPbgkqykSkzyY # Vr15OApZYK8CAwEAAaOCAVwwggFYMB8GA1UdIwQYMBaAFPZ3at0//QET/xahbIIC # L9AKPRQlMB0GA1UdDgQWBBRfWO1MMXqiYUKNUoC6s2GXGaIymzAOBgNVHQ8BAf8E # BAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNVHSUEDDAKBggrBgEFBQcDCDAR # BgNVHSAECjAIMAYGBFUdIAAwTAYDVR0fBEUwQzBBoD+gPYY7aHR0cDovL2NybC5z # ZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljVGltZVN0YW1waW5nUm9vdFI0Ni5jcmww # fAYIKwYBBQUHAQEEcDBuMEcGCCsGAQUFBzAChjtodHRwOi8vY3J0LnNlY3RpZ28u # Y29tL1NlY3RpZ29QdWJsaWNUaW1lU3RhbXBpbmdSb290UjQ2LnA3YzAjBggrBgEF # BQcwAYYXaHR0cDovL29jc3Auc2VjdGlnby5jb20wDQYJKoZIhvcNAQEMBQADggIB # ABLXeyCtDjVYDJ6BHSVY/UwtZ3Svx2ImIfZVVGnGoUaGdltoX4hDskBMZx5NY5L6 # SCcwDMZhHOmbyMhyOVJDwm1yrKYqGDHWzpwVkFJ+996jKKAXyIIaUf5JVKjccev3 # w16mNIUlNTkpJEor7edVJZiRJVCAmWAaHcw9zP0hY3gj+fWp8MbOocI9Zn78xvm9 # XKGBp6rEs9sEiq/pwzvg2/KjXE2yWUQIkms6+yslCRqNXPjEnBnxuUB1fm6bPAV+ # Tsr/Qrd+mOCJemo06ldon4pJFbQd0TQVIMLv5koklInHvyaf6vATJP4DfPtKzSBP # kKlOtyaFTAjD2Nu+di5hErEVVaMqSVbfPzd6kNXOhYm23EWm6N2s2ZHCHVhlUgHa # C4ACMRCgXjYfQEDtYEK54dUwPJXV7icz0rgCzs9VI29DwsjVZFpO4ZIVR33LwXyP # DbYFkLqYmgHjR3tKVkhh9qKV2WCmBuC27pIOx6TYvyqiYbntinmpOqh/QPAnhDge # xKG9GX/n1PggkGi9HCapZp8fRwg8RftwS21Ln61euBG0yONM6noD2XQPrFwpm3Gc # uqJMf0o8LLrFkSLRQNwxPDDkWXhW+gZswbaiie5fd/W2ygcto78XCSPfFWveUOSZ # 5SqK95tBO8aTHmEa4lpJVD7HrTEn9jb1EGvxOb1cnn0CMIIGGjCCBAKgAwIBAgIQ # Yh1tDFIBnjuQeRUgiSEcCjANBgkqhkiG9w0BAQwFADBWMQswCQYDVQQGEwJHQjEY # MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdvIFB1Ymxp # YyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNMzYwMzIx # MjM1OTU5WjBUMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVk # MSswKQYDVQQDEyJTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgQ0EgUjM2MIIB # ojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAmyudU/o1P45gBkNqwM/1f/bI # U1MYyM7TbH78WAeVF3llMwsRHgBGRmxDeEDIArCS2VCoVk4Y/8j6stIkmYV5Gej4 # NgNjVQ4BYoDjGMwdjioXan1hlaGFt4Wk9vT0k2oWJMJjL9G//N523hAm4jF4UjrW # 2pvv9+hdPX8tbbAfI3v0VdJiJPFy/7XwiunD7mBxNtecM6ytIdUlh08T2z7mJEXZ # D9OWcJkZk5wDuf2q52PN43jc4T9OkoXZ0arWZVeffvMr/iiIROSCzKoDmWABDRzV # /UiQ5vqsaeFaqQdzFf4ed8peNWh1OaZXnYvZQgWx/SXiJDRSAolRzZEZquE6cbcH # 747FHncs/Kzcn0Ccv2jrOW+LPmnOyB+tAfiWu01TPhCr9VrkxsHC5qFNxaThTG5j # 4/Kc+ODD2dX/fmBECELcvzUHf9shoFvrn35XGf2RPaNTO2uSZ6n9otv7jElspkfK # 9qEATHZcodp+R4q2OIypxR//YEb3fkDn3UayWW9bAgMBAAGjggFkMIIBYDAfBgNV # HSMEGDAWgBQy65Ka/zWWSC8oQEJwIDaRXBeF5jAdBgNVHQ4EFgQUDyrLIIcouOxv # SK4rVKYpqhekzQwwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw # EwYDVR0lBAwwCgYIKwYBBQUHAwMwGwYDVR0gBBQwEjAGBgRVHSAAMAgGBmeBDAEE # ATBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsLnNlY3RpZ28uY29tL1NlY3Rp # Z29QdWJsaWNDb2RlU2lnbmluZ1Jvb3RSNDYuY3JsMHsGCCsGAQUFBwEBBG8wbTBG # BggrBgEFBQcwAoY6aHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGlj # Q29kZVNpZ25pbmdSb290UjQ2LnA3YzAjBggrBgEFBQcwAYYXaHR0cDovL29jc3Au # c2VjdGlnby5jb20wDQYJKoZIhvcNAQEMBQADggIBAAb/guF3YzZue6EVIJsT/wT+ # mHVEYcNWlXHRkT+FoetAQLHI1uBy/YXKZDk8+Y1LoNqHrp22AKMGxQtgCivnDHFy # AQ9GXTmlk7MjcgQbDCx6mn7yIawsppWkvfPkKaAQsiqaT9DnMWBHVNIabGqgQSGT # rQWo43MOfsPynhbz2Hyxf5XWKZpRvr3dMapandPfYgoZ8iDL2OR3sYztgJrbG6VZ # 9DoTXFm1g0Rf97Aaen1l4c+w3DC+IkwFkvjFV3jS49ZSc4lShKK6BrPTJYs4NG1D # GzmpToTnwoqZ8fAmi2XlZnuchC4NPSZaPATHvNIzt+z1PHo35D/f7j2pO1S8BCys # QDHCbM5Mnomnq5aYcKCsdbh0czchOm8bkinLrYrKpii+Tk7pwL7TjRKLXkomm5D1 # Umds++pip8wH2cQpf93at3VDcOK4N7EwoIJB0kak6pSzEu4I64U6gZs7tS/dGNSl # jf2OSSnRr7KWzq03zl8l75jy+hOds9TWSenLbjBQUGR96cFr6lEUfAIEHVC1L68Y # 1GGxx4/eRI82ut83axHMViw1+sVpbPxg51Tbnio1lB93079WPFnYaOvfGAA0e0zc # fF/M9gXr+korwQTh2Prqooq2bYNMvUoUKD85gnJ+t0smrWrb8dee2CvYZXD5laGt # aAxOfy/VKNmwuWuAh9kcMIIGRzCCBK+gAwIBAgIQacs5SDkvNuif0aEmZmr03jAN # BgkqhkiG9w0BAQwFADBUMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBM # aW1pdGVkMSswKQYDVQQDEyJTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgQ0Eg # UjM2MB4XDTI1MDEyOTAwMDAwMFoXDTI4MDEyOTIzNTk1OVowXjELMAkGA1UEBhMC # VVMxEzARBgNVBAgMCk5ldyBKZXJzZXkxHDAaBgNVBAoME0N5YmVyTWVsIENvbnN1 # bHRpbmcxHDAaBgNVBAMME0N5YmVyTWVsIENvbnN1bHRpbmcwggIiMA0GCSqGSIb3 # DQEBAQUAA4ICDwAwggIKAoICAQDBQmSvdfamF8o0CJr4vbHCcJ4rwx6T1HR3d32u # 4aIf9v9p/GV4nFdG4PP9SMjWw7Nx9CLFqGPpkw7aDU2IxwpfPYExDzkCj2pgiyeV # KlL0itTlPocb6i1cZLe/WHV7aUkGkVlfvyYIqdJ9uw711dhNWmMhlqo+/qyp+gpK # qaiFHm6mWNVg2KLTH5Pu38cBoGhS1tn7mlQbtALNjehkpFw2AAntEIBzM3ZEg9WB # xQlgYY0yAPkydYbJfTEOEFJqHUPTSV46jx22Jb9dl0cEIPsGrCp+Jo5Ugusp9oZE # CZ8bGt7Vc9jYoIWGpqcRDq1JZFNCSVvNE4N3ECGjq6W3kYW7ot0CP1DkpJ93a5wr # ksQ6bvYGUy3lghkMvzjkkq/NVUDEVcdNR7PsUFf654vSw+iLINZ+9kYg+Znplfnd # T/JSMJDAaWkM5oLu6+ao0774QWrsHOttz7M8EDU+3PntYHglwWoej6qXIFRurgXd # wAXXyXYcSmkOTbPqrjSwsbs8CuSwGqebbRSDKfjRzDqQ9D1AZ/JHHaaUkBbAYBsV # MrvypDSrP/1o37mt4Zky28BnEp5ztEGp0HJ44X4rFVWWz+BfeuZWcVUcGKW2YFHo # bNwGmJ/OanLvlnmtpZIRLF9ZkbzCHHomi+RId4g3fc3FsGxKqEW9Vj8PCumwKc6L # UwZU4wIDAQABo4IBiTCCAYUwHwYDVR0jBBgwFoAUDyrLIIcouOxvSK4rVKYpqhek # zQwwHQYDVR0OBBYEFCiCHmEfvPkU1uIc2sPugFDBq88SMA4GA1UdDwEB/wQEAwIH # gDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMEoGA1UdIARDMEEw # NQYMKwYBBAGyMQECAQMCMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5j # b20vQ1BTMAgGBmeBDAEEATBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLnNl # Y3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ0NBUjM2LmNybDB5Bggr # BgEFBQcBAQRtMGswRAYIKwYBBQUHMAKGOGh0dHA6Ly9jcnQuc2VjdGlnby5jb20v # U2VjdGlnb1B1YmxpY0NvZGVTaWduaW5nQ0FSMzYuY3J0MCMGCCsGAQUFBzABhhdo # dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAYEAmLUUP/C5 # nHN/qX27dIrfNezHdUul/uhOA5CwNkD7P4pvLJButR/S1OmvozuzJJTce6824Iyl # nXkRwUFj04XLbodkBL7+YwQ5ml7CjdDSVo+sI/38jcEQ6FgosV/TTJSiFAgqMNwk # x/kSzvQ1/Ufp5YVKggCXGJ4VitIzl5nMbzzu35G/uy4vmCQfh0KPYUTJYiRsF6Z3 # XJiIVtYrEwN/ikif/WFGrzsFj1OOWHNn5qDOP80xExmRS09z/wdZE9RdjPv5fYLn # KWy1+GQ/w1vzg/l2vUXIgBV0MxalUfTP4V9Spsodrb+noPXiCy5n+6hy9yCf3EQb # 3G1n8rT/a454fLSijMm6bhrgBRqhPUUtn6ZIBdEJzJUI6ftuXrQnB/U7zf32xcTT # AW7WPem7DFK/4JrSaxiXcSkxQ4kXJDVoDPUJdpb0c5XdWVJO0DCkB35ONEIoqT6V # jEIjLPSw9UXE420r1OIpV8FRJqrW4Fr5RUveEUlyF+FyygVOYZECNsjRMIIGYjCC # BMqgAwIBAgIRAKQpO24e3denNAiHrXpOtyQwDQYJKoZIhvcNAQEMBQAwVTELMAkG # A1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEsMCoGA1UEAxMjU2Vj # dGlnbyBQdWJsaWMgVGltZSBTdGFtcGluZyBDQSBSMzYwHhcNMjUwMzI3MDAwMDAw # WhcNMzYwMzIxMjM1OTU5WjByMQswCQYDVQQGEwJHQjEXMBUGA1UECBMOV2VzdCBZ # b3Jrc2hpcmUxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEwMC4GA1UEAxMnU2Vj # dGlnbyBQdWJsaWMgVGltZSBTdGFtcGluZyBTaWduZXIgUjM2MIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEA04SV9G6kU3jyPRBLeBIHPNyUgVNnYayfsGOy # YEXrn3+SkDYTLs1crcw/ol2swE1TzB2aR/5JIjKNf75QBha2Ddj+4NEPKDxHEd4d # En7RTWMcTIfm492TW22I8LfH+A7Ehz0/safc6BbsNBzjHTt7FngNfhfJoYOrkugS # aT8F0IzUh6VUwoHdYDpiln9dh0n0m545d5A5tJD92iFAIbKHQWGbCQNYplqpAFas # HBn77OqW37P9BhOASdmjp3IijYiFdcA0WQIe60vzvrk0HG+iVcwVZjz+t5OcXGTc # xqOAzk1frDNZ1aw8nFhGEvG0ktJQknnJZE3D40GofV7O8WzgaAnZmoUn4PCpvH36 # vD4XaAF2CjiPsJWiY/j2xLsJuqx3JtuI4akH0MmGzlBUylhXvdNVXcjAuIEcEQKt # OBR9lU4wXQpISrbOT8ux+96GzBq8TdbhoFcmYaOBZKlwPP7pOp5Mzx/UMhyBA93P # QhiCdPfIVOCINsUY4U23p4KJ3F1HqP3H6Slw3lHACnLilGETXRg5X/Fp8G8qlG5Y # +M49ZEGUp2bneRLZoyHTyynHvFISpefhBCV0KdRZHPcuSL5OAGWnBjAlRtHvsMBr # I3AAA0Tu1oGvPa/4yeeiAyu+9y3SLC98gDVbySnXnkujjhIh+oaatsk/oyf5R2vc # xHahajMCAwEAAaOCAY4wggGKMB8GA1UdIwQYMBaAFF9Y7UwxeqJhQo1SgLqzYZcZ # ojKbMB0GA1UdDgQWBBSIYYyhKjdkgShgoZsx0Iz9LALOTzAOBgNVHQ8BAf8EBAMC # BsAwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDBKBgNVHSAE # QzBBMDUGDCsGAQQBsjEBAgEDCDAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3Rp # Z28uY29tL0NQUzAIBgZngQwBBAIwSgYDVR0fBEMwQTA/oD2gO4Y5aHR0cDovL2Ny # bC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljVGltZVN0YW1waW5nQ0FSMzYuY3Js # MHoGCCsGAQUFBwEBBG4wbDBFBggrBgEFBQcwAoY5aHR0cDovL2NydC5zZWN0aWdv # LmNvbS9TZWN0aWdvUHVibGljVGltZVN0YW1waW5nQ0FSMzYuY3J0MCMGCCsGAQUF # BzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAYEA # AoE+pIZyUSH5ZakuPVKK4eWbzEsTRJOEjbIu6r7vmzXXLpJx4FyGmcqnFZoa1dzx # 3JrUCrdG5b//LfAxOGy9Ph9JtrYChJaVHrusDh9NgYwiGDOhyyJ2zRy3+kdqhwtU # lLCdNjFjakTSE+hkC9F5ty1uxOoQ2ZkfI5WM4WXA3ZHcNHB4V42zi7Jk3ktEnkSd # ViVxM6rduXW0jmmiu71ZpBFZDh7Kdens+PQXPgMqvzodgQJEkxaION5XRCoBxAwW # wiMm2thPDuZTzWp/gUFzi7izCmEt4pE3Kf0MOt3ccgwn4Kl2FIcQaV55nkjv1gOD # cHcD9+ZVjYZoyKTVWb4VqMQy/j8Q3aaYd/jOQ66Fhk3NWbg2tYl5jhQCuIsE55Vg # 4N0DUbEWvXJxtxQQaVR5xzhEI+BjJKzh3TQ026JxHhr2fuJ0mV68AluFr9qshgwS # 5SpN5FFtaSEnAwqZv3IS+mlG50rK7W3qXbWwi4hmpylUfygtYLEdLQukNEX1jiOK # MIIGgjCCBGqgAwIBAgIQNsKwvXwbOuejs902y8l1aDANBgkqhkiG9w0BAQwFADCB # iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl # cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV # BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMjEw # MzIyMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjBXMQswCQYDVQQGEwJHQjEYMBYGA1UE # ChMPU2VjdGlnbyBMaW1pdGVkMS4wLAYDVQQDEyVTZWN0aWdvIFB1YmxpYyBUaW1l # IFN0YW1waW5nIFJvb3QgUjQ2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC # AgEAiJ3YuUVnnR3d6LkmgZpUVMB8SQWbzFoVD9mUEES0QUCBdxSZqdTkdizICFNe # INCSJS+lV1ipnW5ihkQyC0cRLWXUJzodqpnMRs46npiJPHrfLBOifjfhpdXJ2aHH # sPHggGsCi7uE0awqKggE/LkYw3sqaBia67h/3awoqNvGqiFRJ+OTWYmUCO2GAXse # PHi+/JUNAax3kpqstbl3vcTdOGhtKShvZIvjwulRH87rbukNyHGWX5tNK/WABKf+ # Gnoi4cmisS7oSimgHUI0Wn/4elNd40BFdSZ1EwpuddZ+Wr7+Dfo0lcHflm/FDDrO # J3rWqauUP8hsokDoI7D/yUVI9DAE/WK3Jl3C4LKwIpn1mNzMyptRwsXKrop06m7N # UNHdlTDEMovXAIDGAvYynPt5lutv8lZeI5w3MOlCybAZDpK3Dy1MKo+6aEtE9vti # TMzz/o2dYfdP0KWZwZIXbYsTIlg1YIetCpi5s14qiXOpRsKqFKqav9R1R5vj3Nge # vsAsvxsAnI8Oa5s2oy25qhsoBIGo/zi6GpxFj+mOdh35Xn91y72J4RGOJEoqzEIb # W3q0b2iPuWLA911cRxgY5SJYubvjay3nSMbBPPFsyl6mY4/WYucmyS9lo3l7jk27 # MAe145GWxK4O3m3gEFEIkv7kRmefDR7Oe2T1HxAnICQvr9sCAwEAAaOCARYwggES # MB8GA1UdIwQYMBaAFFN5v1qqK0rPVIDh2JvAnfKyA2bLMB0GA1UdDgQWBBT2d2rd # P/0BE/8WoWyCAi/QCj0UJTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB # /zATBgNVHSUEDDAKBggrBgEFBQcDCDARBgNVHSAECjAIMAYGBFUdIAAwUAYDVR0f # BEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdFJT # QUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMDUGCCsGAQUFBwEBBCkwJzAlBggr # BgEFBQcwAYYZaHR0cDovL29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwF # AAOCAgEADr5lQe1oRLjlocXUEYfktzsljOt+2sgXke3Y8UPEooU5y39rAARaAdAx # UeiX1ktLJ3+lgxtoLQhn5cFb3GF2SSZRX8ptQ6IvuD3wz/LNHKpQ5nX8hjsDLRhs # yeIiJsms9yAWnvdYOdEMq1W61KE9JlBkB20XBee6JaXx4UBErc+YuoSb1SxVf7nk # NtUjPfcxuFtrQdRMRi/fInV/AobE8Gw/8yBMQKKaHt5eia8ybT8Y/Ffa6HAJyz9g # vEOcF1VWXG8OMeM7Vy7Bs6mSIkYeYtddU1ux1dQLbEGur18ut97wgGwDiGinCwKP # yFO7ApcmVJOtlw9FVJxw/mL1TbyBns4zOgkaXFnnfzg4qbSvnrwyj1NiurMp4pmA # WjR+Pb/SIduPnmFzbSN/G8reZCL4fvGlvPFk4Uab/JVCSmj59+/mB2Gn6G/UYOy8 # k60mKcmaAZsEVkhOFuoj4we8CYyaR9vd9PGZKSinaZIkvVjbH/3nlLb0a7SBIkiR # zfPfS9T+JesylbHa1LtRV9U/7m0q7Ma2CQ/t392ioOssXW7oKLdOmMBl14suVFBm # bzrt5V5cQPnwtd3UOTpS9oCG+ZZheiIvPgkDmA8FzPsnfXW5qHELB43ET7HHFHeR # PRYrMBKjkb8/IN7Po0d0hQoF4TeMM+zYAJzoKQnVKOLg8pZVPT8xggYwMIIGLAIB # ATBoMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzAp # BgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYCEGnLOUg5 # Lzbon9GhJmZq9N4wCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKEC # gAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwG # CisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFPr1jrUXGnHmnR5QGJxRIyQEuszk # MA0GCSqGSIb3DQEBAQUABIICAG1qS75ZwZyeRwqz3nDpccZClBPXercbElOxCDg3 # KL4AxZ/VG44PpfqVRfW5MbmdhPyw0/xXnhlX8kjSfoA9K1ol3nnpeFE9OWf6Ppix # uAfmzxOJ9eWQJQThDZ1hwG/lQYN9bRwsPxTnN/TTR2YqvEBC4pwjv4lF1iuFDycg # Di3ivdd8kmh9C6GZr3TqRlmsVvyxWH5VQG43dCNd4aOwX0ss0bi5ptOrXF0P/OVK # 73aP27F0+XtFwAbyRZXI9n3GukefRsPvOR747ypmQLvkQC0v8fu+Keg2JMeW/0xH # svVnkwEHSPHCsbcLqWWTStJ3xdrRxv/Yyu6x6VJtWq2O7rilL/xb8M2NpFldVWI5 # nOZP0snLcSNEnqLsNjeG84PSSPNr+ZevDxXMcQsyZnxjvUjPv3CT3XqnYmrIFBQx # sR4IWT3Ck5dg+qdWAxX5Iy7NGp/1ZocUWX8nc6XiF4fmgeTY2PEIT2d5sfgiabZ8 # 0Jl3BmIVB82JlXxRZjV/iwGQJ5Vkzd68iPm2niCzDco9bIwiPlJpaV3gXqi08GPb # orEhcC64VG9ge5Er+tOWk9o65OUoHdOzt9wGlUgxnASNL1xcVSIGzRT6+n6WZv7w # ilzDnzDyNxAbkOt/81plztf6NYewr2QboJ5HsO6OZwJrNbgXqE4I8iPQ6d9k4+X8 # YsyuoYIDIzCCAx8GCSqGSIb3DQEJBjGCAxAwggMMAgEBMGowVTELMAkGA1UEBhMC # R0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEsMCoGA1UEAxMjU2VjdGlnbyBQ # dWJsaWMgVGltZSBTdGFtcGluZyBDQSBSMzYCEQCkKTtuHt3XpzQIh616TrckMA0G # CWCGSAFlAwQCAgUAoHkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG # 9w0BCQUxDxcNMjUwNzE3MDQ1ODM3WjA/BgkqhkiG9w0BCQQxMgQwThuUZYMxiJNu # qOjV4SUdqqPlt38dhVdXAWSTA14jPfOzsqvI71AF7rOSJqnjMJU7MA0GCSqGSIb3 # DQEBAQUABIICAB96m965zIzi+gOv9K+Y7aUw1kTdX/qAUvoyYPkojFRkZxf4SolL # av3U6MNMKPoGPt+C/TnGVIuIk2ShtyUTFzwH3xFdEbG29vUCgOPKKJeAV0HXdvSa # wfbBvYWiWhgOHDaXOBIl5s4k0T6Z+hcKKvRUMng3vhm7rOGnFPo0zae8a/f0M6Ud # PMJniJfsAgdCJzTkeLbYws88B8w03AGLN4gciy/McCpXefx1yTo7AYvEtamx3hXM # 6tNxMwuVzgNWItsf1I6ObZmUXnR6rHx1QtO5+QJPbw/zKXLVw6KLNZwiWLUsF6Zt # 4k2WnjvMkek3zFdWBc0UO0Z09WhKk7bJZ5hAhRmiF/dtUbg7mTgFQcl+kRTiZEUC # Q8NmtqBWj/fDueTgDRWF/VhdmJodWtJP1v8BvZhkkRkQxsttT67dEv4reMDAvaW3 # zLqYIDAbIrU/wUnnFgeFTAhPgpirzcIXNZnybWY/76HnJJU+txxgIZsbg55aKKL8 # 8VSm8LS+KTvuMOK+/aVsXQS3nLLcrxuogzR+7tLSUv9xZyelC7gN35zol6cQcAv2 # 5XTMpss2OvKwprBkKrGrzY31tIhMnYOTNB2wQ7zWHwpPOLVRNphbQQSG/ycOE51N # NzpIEGaQNOVjO2n19X+4RK1H1rEqcFN8JeZZoBraji/Uyzc3b8I4puGp # SIG # End signature block |