if ($PSVersionTable.PSVersion.Major -le 2) { function Exit-CoverageAnalysis { } function Get-CoverageReport { } function Show-CoverageReport { } function Enter-CoverageAnalysis { param ( $CodeCoverage ) if ($CodeCoverage) { & $SafeCommands['Write-Error'] 'Code coverage analysis requires PowerShell 3.0 or later.' } } return } function Enter-CoverageAnalysis { [CmdletBinding()] param ( [object[]] $CodeCoverage, [object] $PesterState ) $coverageInfo = foreach ($object in $CodeCoverage) { Get-CoverageInfoFromUserInput -InputObject $object } $PesterState.CommandCoverage = @(Get-CoverageBreakpoints -CoverageInfo $coverageInfo) } function Exit-CoverageAnalysis { param ([object] $PesterState) & $SafeCommands['Set-StrictMode'] -Off $breakpoints = @($PesterState.CommandCoverage.Breakpoint) -ne $null if ($breakpoints.Count -gt 0) { & $SafeCommands['Remove-PSBreakpoint'] -Breakpoint $breakpoints } } function Get-CoverageInfoFromUserInput { param ( [Parameter(Mandatory = $true)] [object] $InputObject ) if ($InputObject -is [System.Collections.IDictionary]) { $unresolvedCoverageInfo = Get-CoverageInfoFromDictionary -Dictionary $InputObject } else { $unresolvedCoverageInfo = New-CoverageInfo -Path ([string]$InputObject) } Resolve-CoverageInfo -UnresolvedCoverageInfo $unresolvedCoverageInfo } function New-CoverageInfo { param ([string] $Path, [string] $Function = $null, [int] $StartLine = 0, [int] $EndLine = 0) return [pscustomobject]@{ Path = $Path Function = $Function StartLine = $StartLine EndLine = $EndLine } } function Get-CoverageInfoFromDictionary { param ([System.Collections.IDictionary] $Dictionary) [string] $path = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'Path', 'p' if ([string]::IsNullOrEmpty($path)) { throw "Coverage value '$Dictionary' is missing required Path key." } $startLine = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'StartLine', 'Start', 's' $endLine = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'EndLine', 'End', 'e' [string] $function = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'Function', 'f' $startLine = Convert-UnknownValueToInt -Value $startLine -DefaultValue 0 $endLine = Convert-UnknownValueToInt -Value $endLine -DefaultValue 0 return New-CoverageInfo -Path $path -StartLine $startLine -EndLine $endLine -Function $function } function Convert-UnknownValueToInt { param ([object] $Value, [int] $DefaultValue = 0) try { return [int] $Value } catch { return $DefaultValue } } function Resolve-CoverageInfo { param ([psobject] $UnresolvedCoverageInfo) $path = $UnresolvedCoverageInfo.Path try { $resolvedPaths = & $SafeCommands['Resolve-Path'] -Path $path -ErrorAction Stop } catch { & $SafeCommands['Write-Error'] "Could not resolve coverage path '$path': $($_.Exception.Message)" return } $filePaths = foreach ($resolvedPath in $resolvedPaths) { $item = & $SafeCommands['Get-Item'] -LiteralPath $resolvedPath if ($item -is [System.IO.FileInfo] -and ('.ps1','.psm1') -contains $item.Extension) { $item.FullName } elseif (-not $item.PsIsContainer) { & $SafeCommands['Write-Warning'] "CodeCoverage path '$path' resolved to a non-PowerShell file '$($item.FullName)'; this path will not be part of the coverage report." } } $params = @{ StartLine = $UnresolvedCoverageInfo.StartLine EndLine = $UnresolvedCoverageInfo.EndLine Function = $UnresolvedCoverageInfo.Function } foreach ($filePath in $filePaths) { $params['Path'] = $filePath New-CoverageInfo @params } } function Get-CoverageBreakpoints { [CmdletBinding()] param ( [object[]] $CoverageInfo ) $fileGroups = @($CoverageInfo | & $SafeCommands['Group-Object'] -Property Path) foreach ($fileGroup in $fileGroups) { & $SafeCommands['Write-Verbose'] "Initializing code coverage analysis for file '$($fileGroup.Name)'" $totalCommands = 0 $analyzedCommands = 0 :commandLoop foreach ($command in Get-CommandsInFile -Path $fileGroup.Name) { $totalCommands++ foreach ($coverageInfoObject in $fileGroup.Group) { if (Test-CoverageOverlapsCommand -CoverageInfo $coverageInfoObject -Command $command) { $analyzedCommands++ New-CoverageBreakpoint -Command $command continue commandLoop } } } & $SafeCommands['Write-Verbose'] "Analyzing $analyzedCommands of $totalCommands commands in file '$($fileGroup.Name)' for code coverage" } } function Get-CommandsInFile { param ([string] $Path) $errors = $null $tokens = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref] $tokens, [ref] $errors) if ($PSVersionTable.PSVersion.Major -ge 5) { # In PowerShell 5.0, dynamic keywords for DSC configurations are represented by the DynamicKeywordStatementAst # class. They still trigger breakpoints, but are not a child class of CommandBaseAst anymore. $predicate = { $args[0] -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -or $args[0] -is [System.Management.Automation.Language.CommandBaseAst] } } else { $predicate = { $args[0] -is [System.Management.Automation.Language.CommandBaseAst] } } $searchNestedScriptBlocks = $true $ast.FindAll($predicate, $searchNestedScriptBlocks) } function Test-CoverageOverlapsCommand { param ([object] $CoverageInfo, [System.Management.Automation.Language.Ast] $Command) if ($CoverageInfo.Function) { Test-CommandInsideFunction -Command $Command -Function $CoverageInfo.Function } else { Test-CoverageOverlapsCommandByLineNumber @PSBoundParameters } } function Test-CommandInsideFunction { param ([System.Management.Automation.Language.Ast] $Command, [string] $Function) for ($ast = $Command; $null -ne $ast; $ast = $ast.Parent) { $functionAst = $ast -as [System.Management.Automation.Language.FunctionDefinitionAst] if ($null -ne $functionAst -and $functionAst.Name -like $Function) { return $true } } return $false } function Test-CoverageOverlapsCommandByLineNumber { param ([object] $CoverageInfo, [System.Management.Automation.Language.Ast] $Command) $commandStart = $Command.Extent.StartLineNumber $commandEnd = $Command.Extent.EndLineNumber $coverStart = $CoverageInfo.StartLine $coverEnd = $CoverageInfo.EndLine # An EndLine value of 0 means to cover the entire rest of the file from StartLine # (which may also be 0) if ($coverEnd -le 0) { $coverEnd = [int]::MaxValue } return (Test-RangeContainsValue -Value $commandStart -Min $coverStart -Max $coverEnd) -or (Test-RangeContainsValue -Value $commandEnd -Min $coverStart -Max $coverEnd) } function Test-RangeContainsValue { param ([int] $Value, [int] $Min, [int] $Max) return $Value -ge $Min -and $Value -le $Max } function New-CoverageBreakpoint { param ([System.Management.Automation.Language.Ast] $Command) if (IsIgnoredCommand -Command $Command) { return } $params = @{ Script = $Command.Extent.File Line = $Command.Extent.StartLineNumber Column = $Command.Extent.StartColumnNumber Action = { } } $breakpoint = & $SafeCommands['Set-PSBreakpoint'] @params [pscustomobject] @{ File = $Command.Extent.File Function = Get-ParentFunctionName -Ast $Command Line = $Command.Extent.StartLineNumber Command = Get-CoverageCommandText -Ast $Command Breakpoint = $breakpoint } } function IsIgnoredCommand { param ([System.Management.Automation.Language.Ast] $Command) if (-not $Command.Extent.File) { # This can happen if the script contains "configuration" or any similarly implemented # dynamic keyword. PowerShell modifies the script code and reparses it in memory, leading # to AST elements with no File in their Extent. return $true } if ($PSVersionTable.PSVersion.Major -ge 4) { if ($Command.Extent.Text -eq 'Configuration') { # More DSC voodoo. Calls to "configuration" generate breakpoints, but their HitCount # stays zero (even though they are executed.) For now, ignore them, unless we can come # up with a better solution. return $true } if (IsChildOfHashtableDynamicKeyword -Command $Command) { # The lines inside DSC resource declarations don't trigger their breakpoints when executed, # just like the "configuration" keyword itself. I don't know why, at this point, but just like # configuration, we'll ignore it so it doesn't clutter up the coverage analysis with useless junk. return $true } } if (IsClosingLoopCondition -Command $Command) { # For some reason, the closing expressions of do/while and do/until loops don't trigger their breakpoints. # To avoid useless clutter, we'll ignore those lines as well. return $true } return $false } function IsChildOfHashtableDynamicKeyword { param ([System.Management.Automation.Language.Ast] $Command) for ($ast = $Command.Parent; $null -ne $ast; $ast = $ast.Parent) { if ($PSVersionTable.PSVersion.Major -ge 5) { # The ast behaves differently for DSC resources with version 5+. There's a new DynamicKeywordStatementAst class, # and they no longer are represented by CommandAst objects. if ($ast -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -and $ast.CommandElements[-1] -is [System.Management.Automation.Language.HashtableAst]) { return $true } } else { if ($ast -is [System.Management.Automation.Language.CommandAst] -and $null -ne $ast.DefiningKeyword -and $ast.DefiningKeyword.BodyMode -eq [System.Management.Automation.Language.DynamicKeywordBodyMode]::Hashtable) { return $true } } } return $false } function IsClosingLoopCondition { param ([System.Management.Automation.Language.Ast] $Command) $ast = $Command while ($null -ne $ast.Parent) { if (($ast.Parent -is [System.Management.Automation.Language.DoWhileStatementAst] -or $ast.Parent -is [System.Management.Automation.Language.DoUntilStatementAst]) -and $ast.Parent.Condition -eq $ast) { return $true } $ast = $ast.Parent } return $false } function Get-ParentFunctionName { param ([System.Management.Automation.Language.Ast] $Ast) $parent = $Ast.Parent while ($null -ne $parent -and $parent -isnot [System.Management.Automation.Language.FunctionDefinitionAst]) { $parent = $parent.Parent } if ($null -eq $parent) { return '' } else { return $parent.Name } } function Get-CoverageCommandText { param ([System.Management.Automation.Language.Ast] $Ast) $reportParentExtentTypes = @( [System.Management.Automation.Language.ReturnStatementAst] [System.Management.Automation.Language.ThrowStatementAst] [System.Management.Automation.Language.AssignmentStatementAst] [System.Management.Automation.Language.IfStatementAst] ) $parent = Get-ParentNonPipelineAst -Ast $Ast if ($null -ne $parent) { if ($parent -is [System.Management.Automation.Language.HashtableAst]) { return Get-KeyValuePairText -HashtableAst $parent -ChildAst $Ast } elseif ($reportParentExtentTypes -contains $parent.GetType()) { return $parent.Extent.Text } } return $Ast.Extent.Text } function Get-ParentNonPipelineAst { param ([System.Management.Automation.Language.Ast] $Ast) $parent = $null if ($null -ne $Ast) { $parent = $Ast.Parent } while ($parent -is [System.Management.Automation.Language.PipelineAst]) { $parent = $parent.Parent } return $parent } function Get-KeyValuePairText { param ( [System.Management.Automation.Language.HashtableAst] $HashtableAst, [System.Management.Automation.Language.Ast] $ChildAst ) & $SafeCommands['Set-StrictMode'] -Off foreach ($keyValuePair in $HashtableAst.KeyValuePairs) { if ($keyValuePair.Item2.PipelineElements -contains $ChildAst) { return '{0} = {1}' -f $keyValuePair.Item1.Extent.Text, $keyValuePair.Item2.Extent.Text } } # This shouldn't happen, but just in case, default to the old output of just the expression. return $ChildAst.Extent.Text } function Get-CoverageMissedCommands { param ([object[]] $CommandCoverage) $CommandCoverage | & $SafeCommands['Where-Object'] { $_.Breakpoint.HitCount -eq 0 } } function Get-CoverageHitCommands { param ([object[]] $CommandCoverage) $CommandCoverage | & $SafeCommands['Where-Object'] { $_.Breakpoint.HitCount -gt 0 } } function Get-CoverageReport { param ([object] $PesterState) $totalCommandCount = $PesterState.CommandCoverage.Count $missedCommands = @(Get-CoverageMissedCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command) $hitCommands = @(Get-CoverageHitCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command) $analyzedFiles = @($PesterState.CommandCoverage | & $SafeCommands['Select-Object'] -ExpandProperty File -Unique) $fileCount = $analyzedFiles.Count $executedCommandCount = $totalCommandCount - $missedCommands.Count [pscustomobject] @{ NumberOfCommandsAnalyzed = $totalCommandCount NumberOfFilesAnalyzed = $fileCount NumberOfCommandsExecuted = $executedCommandCount NumberOfCommandsMissed = $missedCommands.Count MissedCommands = $missedCommands HitCommands = $hitCommands AnalyzedFiles = $analyzedFiles } } function Show-CoverageReport { param ([object] $CoverageReport) if ($null -eq $CoverageReport -or $CoverageReport.NumberOfCommandsAnalyzed -eq 0) { return } $totalCommandCount = $CoverageReport.NumberOfCommandsAnalyzed $fileCount = $CoverageReport.NumberOfFilesAnalyzed $executedPercent = ($CoverageReport.NumberOfCommandsExecuted / $CoverageReport.NumberOfCommandsAnalyzed).ToString("P2") $commandPlural = $filePlural = '' if ($totalCommandCount -gt 1) { $commandPlural = 's' } if ($fileCount -gt 1) { $filePlural = 's' } $commonParent = Get-CommonParentPath -Path $CoverageReport.AnalyzedFiles $report = $CoverageReport.MissedCommands | & $SafeCommands['Select-Object'] -Property @( @{ Name = 'File'; Expression = { Get-RelativePath -Path $_.File -RelativeTo $commonParent } } 'Function' 'Line' 'Command' ) Write-Screen '' Write-Screen 'Code coverage report:' Write-Screen "Covered $executedPercent of $totalCommandCount analyzed command$commandPlural in $fileCount file$filePlural." if ($CoverageReport.MissedCommands.Count -gt 0) { Write-Screen '' Write-Screen 'Missed commands:' $report | & $SafeCommands['Format-Table'] -AutoSize | & $SafeCommands['Out-String'] | Write-Screen } } function Get-CommonParentPath { param ([string[]] $Path) $pathsToTest = @( $Path | & $SafeCommands['Select-Object'] -Unique ) if ($pathsToTest.Count -gt 0) { $parentPath = & $SafeCommands['Split-Path'] -Path $pathsToTest[0] -Parent while ($parentPath.Length -gt 0) { $nonMatches = $pathsToTest -notmatch "^$([regex]::Escape($parentPath))" if ($nonMatches.Count -eq 0) { return $parentPath } else { $parentPath = & $SafeCommands['Split-Path'] -Path $parentPath -Parent } } } return [string]::Empty } function Get-RelativePath { param ( [string] $Path, [string] $RelativeTo ) return $Path -replace "^$([regex]::Escape($RelativeTo))\\?" } # SIG # Begin signature block # MIInVwYJKoZIhvcNAQcCoIInSDCCJ0QCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCqG+zazlxP++cv # 0EUGZRX6zP2XM6BmoNh/fiywwj2BLqCCC8MwggXaMIIEwqADAgECAhMzAAABP8rF # KBkLiTVYAAAAAAE/MA0GCSqGSIb3DQEBCwUAMIGOMQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMTgwNgYDVQQDEy9NaWNyb3NvZnQgV2luZG93cyBU # aGlyZCBQYXJ0eSBDb21wb25lbnQgQ0EgMjAxMjAeFw0yNTExMTMxOTU5NDJaFw0y # NjExMTAxOTU5NDJaMIGEMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMS4wLAYDVQQDEyVNaWNyb3NvZnQgV2luZG93cyAzcmQgcGFydHkgQ29tcG9u # ZW50MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA2Zy0BnZmL5lF9IDS # IFBJ81NqCWCPLo2bRGJObUnrlezQw+FWEDU9+poMEyRjgbgafifNKY4TJ3e9Od4p # q56xMXBcGYVIe546gz4p68MQG4iXqqSBB4kk5jwk5U7igTCfZIga6PFElV6Wm7kv # Bdw14NVgdJZZDdmIc9TdaWbxrxAda9IMjZNQYfJZ/WVinf0mPnYM2hQwj4Gl4DGC # 0/KO6U+ayXHAtcS9qj2UJYB7rCyteNydGWHaMa5B8fzOpSNS3ioJfYcBwSjfcBRD # pemnEb5BcIF10FVuNA4foeMz5emIZaGGl8XxVC9K79Xwkc571Sv899qEdYP8ZFW9 # yVXY8l1ptvk4nD52nq9ld4HjWA+FHmhbhKggbjEVymQee7fOgEWKE3Uc73YnTMGf # TXzDwH9jYip5fwls08LWs0HCINu/iA/OG/vm1jrJdK5wcBgX2B0fZPdwgLTEgs0R # rSIyv9WucI0S6XffXJuUH+lziAX7RmCPhy5kZR1R3LB0GnBlAgMBAAGjggG3MIIB # szATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUt3RSDxhJIhKufhwaOyWL # 3nMDoeUwVAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5k # IE9wZXJhdGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwODA5KzUwNjIzOTAfBgNV # HSMEGDAWgBRhcaeHr/9p1SF2T1KTKAC+eRKrhDB0BgNVHR8EbTBrMGmgZ6BlhmNo # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBX # aW5kb3dzJTIwVGhpcmQlMjBQYXJ0eSUyMENvbXBvbmVudCUyMENBJTIwMjAxMi5j # cmwwgYEGCCsGAQUFBwEBBHUwczBxBggrBgEFBQcwAoZlaHR0cDovL3d3dy5taWNy # b3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBXaW5kb3dzJTIwVGhp # cmQlMjBQYXJ0eSUyMENvbXBvbmVudCUyMENBJTIwMjAxMi5jcnQwDAYDVR0TAQH/ # BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAk+iGdVNjQ2VMiNXhflILGybQmTUMM+qd # BZ3KErdJ9wkVTN/fMukvmp9y1iF8Sz1NUqDNqiKLofcL0XukOu5W3zAofFlAs2tR # vf0ArWKgRP5gjpqXeo3xWRM/1LBYTDhwDmylfh36AnfErB+aHyoIr9an+2KqeIqj # 5VvFPgwJ1n6ZTXZMhjvYnIol/P+vwVroo2XKwbOL1/c79xRj7X8Lqw+7sVoIA9/P # ytqdSDV1ClBjltkRpdgwvbSDPzycfvN8V5pPFfkrqrcIHjaL2pe76nqRsEIPqiVQ # SmqiaJV6iprCSDGJYC4/4EMLIDZ4uf+m0XHW3Qzlr7RLlMdsJKny7jCCBeEwggPJ # oAMCAQICCmELqsEAAAAAAAkwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290 # IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEyMDQxODIzNDgzOFoXDTI3 # MDQxODIzNTgzOFowgY4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xODA2BgNVBAMTL01pY3Jvc29mdCBXaW5kb3dzIFRoaXJkIFBhcnR5IENvbXBv # bmVudCBDQSAyMDEyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo5ww # hAmnYy7PCkfw6iT5ozAgD15XMSaBmjEHslDUzmcJCGUKWqVLrtXtEC7npZm1n2gv # mItYAqwgtCnEcb0oHKX9PJtk5MXr32ElvPDuaL/Rp8t+KgKBTmRcDFOGeVcZN2G3 # mPkMoE4iWZv5Gy1nPCc8VpBm4/1/ZX0Phr01R+iKzPTajulqTqunVeyiiR7VM0VT # y/med73NLPkFuH90AR3o+xjhQ9EN6arcN2+9/rgP7R1NAUZOCqz8gujsVoMTjjoB # 7RRkdOpksmYQtmhtyHAAfVBILj1D7uAklcbNjsf9uOSVz91++5VeoQHNQ7EH16Qw # 7puGGipuwQtZonRviwIDAQABo4IBQzCCAT8wEAYJKwYBBAGCNxUBBAMCAQAwHQYD # VR0OBBYEFGFxp4ev/2nVIXZPUpMoAL55EquEMBkGCSsGAQQBgjcUAgQMHgoAUwB1 # AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaA # FNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j # cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8y # MDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6 # Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAt # MDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQBaimfazNX9DSZBd78KRni0s94S # aSt3I8JlLwFf0gP0YbpQnS6MOXLzbD5qsR52bey384LczLvFaXAoc2YXP1Tr7gEW # SMRG2RuAroE6jQ95bWiwnuotPznTyjh+vV58CG4Z3MbC9DgzaGHiUkeD4QABVtK6 # y4eCBTEKQYtO539fX+1f0zktReuiE7/9HsKYQXFhFl/ICnAlfFlpMSTkcecKuwQX # 959yHsnSuxq+PQL+CQyyQ7RZGplTk5YhX+DWtyYBQpU2rCf9vvSFd2g9GL30vpiI # IhGGUhbzRewDlxBwh6NwQ3E828mGAxcM9XNbxn3hXGTt18VI1+0y4tGq08+n9ldO # Yfl362fyiLPeANoDj9CKNDc+HdhiuNKx8+Evi3I7gZZ8b/zsZnZyYBsk8qCJbVtt # AC7vKN2GhwXCtLnlvmTCKvJKFVyY4sQnhf9S42J+D7ICC9dmxwqy0z0gBBRQMlmD # Cn2b7Vo4EgFSui9eIHKOSvH953ECjDvhB77Jc/TdR9i077SkszC5iT52yrkAmFZ+ # q+qKuKXQOKtpdxMLFC/pqkEf97q9Ois0iu4Kq2PmY/eIJI4gDSs7nePCSVKsnx8O # OTtd1G5QauZ9UjqqfDMVKQ0mXgFYp06pPXqEb3Q/YJ/kMk82AK9tcdM+pkZlX4F0 # 8f7BcdpMoEFagt3xHzGCGuowghrmAgEBMIGmMIGOMQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMTgwNgYDVQQDEy9NaWNyb3NvZnQgV2luZG93cyBU # aGlyZCBQYXJ0eSBDb21wb25lbnQgQ0EgMjAxMgITMwAAAT/KxSgZC4k1WAAAAAAB # PzANBglghkgBZQMEAgEFAKCB+jAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAv # BgkqhkiG9w0BCQQxIgQgI43BYpcft2y60jClV3MZsLYmW2Xl+pWs2B5o0UCvf2Aw # UAYKKwYBBAGCNwoDHDFCDEBCNUY0N0ZEMzUyN0VBMjcxQTJBQ0JGNEYyNjFBQ0Yw # NkFGQTBCMTQ5Q0E5NkIyNkIwMEQ1NjQ0NjkzOUYxMUIxMFoGCisGAQQBgjcCAQwx # TDBKoCSAIgBNAGkAYwByAG8AcwBvAGYAdAAgAFcAaQBuAGQAbwB3AHOhIoAgaHR0 # cDovL3d3dy5taWNyb3NvZnQuY29tL3dpbmRvd3MwDQYJKoZIhvcNAQEBBQAEggGA # wICNqR5sFM9oWLZ0acowros1TK/ixXjQ0Pgu2NGjgyQC/ptJKdc1B2xWZOnTqGdj # JArqxLwSm7HWqhh5vwYseKCMwRAQp2cDGTU9m/wR9jdFo7XPiEbNBPf5/dccq578 # D3BeGP2GQCi79OpHurLWAgcUw0+KYjTReQfMkikqYJDx+EU5Fnrp0wiSAPXolrzf # MmpQSxZwAG2asiJyzKdXt9GTKygYg/oZbP+hBmCaMsGWzatMti7C6fLvlCBOahr9 # +dj/1muIz1+hckOP5v3AXsRq7wPq8PrIcGwtdSKKiM6hmPTiXKvZmn/GgdYNi7G6 # HcXvgK3vzhSzzftNJNONvba1S1btwN2r+cv5m5WADpJN0aYDejENe01kZY2h8zSW # CdMipZFqh4HQpXzQh04qsgWJG3eL9ZfHcscpKkpm1GlCU1vbNW8zWEwd3c7yvIIO # 1fL2ehfSeH53xLKmYRt7A/gDwCLBDOZYOmOyMHcmlMMLEHFggUP80ourAmblLw2Y # oYIXlzCCF5MGCisGAQQBgjcDAwExgheDMIIXfwYJKoZIhvcNAQcCoIIXcDCCF2wC # AQMxDzANBglghkgBZQMEAgEFADCCAVIGCyqGSIb3DQEJEAEEoIIBQQSCAT0wggE5 # AgEBBgorBgEEAYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEIHyJuqLz1Pgk1Nstm1yW # E4V+ysorSMOEFq2bU9Fi8XKeAgZp530JL68YEzIwMjYwNTA2MTU0OTQ1LjgyMVow # BIACAfSggdGkgc4wgcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNV # BAsTHm5TaGllbGQgVFNTIEVTTjo4NjAzLTA1RTAtRDk0NzElMCMGA1UEAxMcTWlj # cm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEe0wggcgMIIFCKADAgECAhMzAAAC # JYDHN8bNqndJAAEAAAIlMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFBDQSAyMDEwMB4XDTI2MDIxOTE5NDAwMVoXDTI3MDUxNzE5NDAwMVowgcsx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p # Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNT # IEVTTjo4NjAzLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3Rh # bXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKbxEg/R # 4sDjpVwI+i++aqwiU3qqSbPkiwdaZRTSd5Sqny4bFp16j5LBYELBDlqVkj8M12ld # /KJktlHpdiClE8XN6kiXs4INvg20SyQkIhkORAw3Csf1jBTK7vUYaKCwsjF6V4e0 # De62hVN4eNLVvxSfA5FG2ScqTKtQCtPpmkHauh0hyZwty/fHfDCBiU6zQUSDkSxW # tlvss1z+d3RtcOn4dM5Za6Lx6hNXAl4vFxU/zr2gXyWLlJTzVpra0Ynr8mx6OLP0 # kxbxIlcoFPYMJcw5SQKwaOic9lGp++gxIhBmC1o5PIAmWu+zLRNnvxesaqjKC1CK # ZCds4Avgo0tIK5blNkRAZMcs5AkaCCBvePmAoLvvz5Eg8kD6f+GYcn/HipP8dNM+ # hV4wJy4EpatBdHX7+lhq7cXB7S1YjIb4tbORGv9k08+6lwDZhyLeqfwdH1HC9Cim # pI0nCfZGLpqbwBDJ9VXL8EHDS3qOmhE+PAq+5SN8LOlp7p247FC1DVcM308DbKX2 # wOSj/4BdX9I57x5rxChBy/ezcSuQb4unqGe/Do4w+JqfiCA2RG2C0HuujU6Kik5R # cmf1jkQ7clQBc1y4z2b7kzLVUS68bK2AAfe7GayVOdbdhut9rNrJIJJKdaSFo5nf # eGBu5RB8fufY0UQBRz9wXN+YJBSKRaKycljLAgMBAAGjggFJMIIBRTAdBgNVHQ4E # FgQUsTjSqhdO4wdfcB9lS7WfyfHaH3cwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXS # ZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIw # MTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0 # YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8E # DDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIB # AAed9zbJTKgdlu+/JUoW5eHkfHEci8hpH0lakh8hMmVz8qLTeO5H69yTOre2nl8U # fksvt4gVdEi6h7Ayy9Z4Wta5+utbgeGaELCSoCt8DULTGT4dpizY7jxhLExf2WBL # WRNMhvdix+gV0Wkq6s9/adzZh3jAuD4WDCaTGR7ITcxQpWdrxJl5WkSOdLm5wVyT # iys/ArY5EB/vQjbcYbI+GqAgpmmE1eFKxxMBCzIioHkbAMx1FXksrfs19ThibG8J # iHdMVgT8aHTVDrIm9/0fGIRmnBb6hSTSCu4ehuDeyAhHmt+BSjyXfS9SdoNgxw8A # KVoUwL9BsdlJpSFZdkbU45wynSD29hA0sMSoVfaOWq6/NVJLC0e2bUpOV0KNEQP6 # R0LJtw/Fs9qXAmKBdzUGwj0KK2dN/SWPBv02Rn8lUjz8PratdfOHPgXe7SJUbPCd # wZrFHEcb9e/idOumQ556mhhs0FsxZLYbWo/dePulV/T7ipHIConSy2NCOhU4kiZU # 9ZGPPk9HcOfpp1BUwEkMzqAOuPWtlMVWAK1OKOoZlIbO9ekaQXe9izITpkOZr+QZ # 2JR7mxp4jqUfro+JZZeCrG3uzLYTO/TIiNJW/54w5PZAxSJnpYJzuBW0CZel94i6 # z42aAW8z4hzVfnx7gj0QvhlICJ1KlZbQZlMs0LTaavIuMIIHcTCCBVmgAwIBAgIT # MwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv # b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcN # MzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIw # DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT # /e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYj # DLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/Y # JlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d # 9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVU # j9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFK # u75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231f # gLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C # 89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC # +hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2 # XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54W # cmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMG # CSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cV # XQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/ # BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2Nz # L1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcU # AgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8G # A1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeG # RWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jv # b0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUH # MAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2Vy # QXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9n # ATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP # +2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27Y # P0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8Z # thISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNh # cy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7G # dP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4J # vbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjo # iV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TO # PqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ # 1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NN # je6CbaUFEMFxBmoQtB1VM1izoXBm8qGCA1AwggI4AgEBMIH5oYHRpIHOMIHLMQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNy # b3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBF # U046ODYwMy0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w # IFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAFNv5so48CMIF+WHPDkRcG5JbF4OoIGD # MIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG # A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEL # BQACBQDtpb+3MCIYDzIwMjYwNTA2MTMyNTExWhgPMjAyNjA1MDcxMzI1MTFaMHcw # PQYKKwYBBAGEWQoEATEvMC0wCgIFAO2lv7cCAQAwCgIBAAICJzYCAf8wBwIBAAIC # Ew0wCgIFAO2nETcCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAK # MAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQsFAAOCAQEAGBwl5Fwn # fzRV1Z2xCiS5SYbqQ2+dTuLtFZrJR1zOfbk3RGl+Qi1nJXRzpYEYbNdyod+Y9hHF # Qi+A2bh2fuUtOzGIGC2pddwTAPUtG2crZpfHdHPIVyScbADDlcDFo5QFC8ZZGYda # FQrM30q8avIDahbbQJsJ4YPY9heKCDp+ZQhucXrD9mNnjd38OkNzPvz63sKTgU25 # tHSPITUQfQz36ZtugVFx3NVXrONprURFokEwh/ek3THfMdAzMUg9KThcJTSIAYPu # LabWAKouIj6Se1hYoktlSQIfj3Z3KYV6sq8r5fqj1OjyethY+W4qet50pdul0kc0 # knjcDyF6VRHUUDGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD # QSAyMDEwAhMzAAACJYDHN8bNqndJAAEAAAIlMA0GCWCGSAFlAwQCAQUAoIIBSjAa # BgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIGKpi1A5 # fUg4dFxuVH0snyUuoz/eiCbsDpVRWZCi39w0MIH6BgsqhkiG9w0BCRACLzGB6jCB # 5zCB5DCBvQQgVg3uiHo43fL3YKYCX+UXQJjCuNZZA/p0JTFqM9IcoRAwgZgwgYCk # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAiWAxzfGzap3SQAB # AAACJTAiBCAOSGCsR2IXr6NPEUhEjQudNS7D5wqVPooDREwed1lDWjANBgkqhkiG # 9w0BAQsFAASCAgA19CTLzJWTkssXvThb54YYteEUD3xIPvXjA9h6mwEM8TmOyghj # Tb5uwjSA2QkKvy4JEcPDDylpIYTcxBKo1JKIPjQ6UesPzlaCX+pcV68LZwEGsa88 # QOQxwRlU72njcBfShBTbgLZ5AVnLVvO+y52SlEPHzKUSDdY60PGKMlBjXHN9WED+ # 3KubIQY9QgwRLllE1fpYYUJCW199jfeTYrqitCYmauYb8IE8lIh50vV/MyGG47W3 # x9WAJ6ykN3zQ36lYbVdPqUmbno/CR1vFUJFTA9sue+cpYE/91w36JnF3TIPqapgY # a0y/km/BhiEDHNcSHmiCBjKKPfRlTmvjPUpnZ1vtbECP8PlUHoFdxIcchwQ9/ub6 # xVSFX2C1WrIB6kHjZ315g8p2PxAGZaILJDEaub0n98L4RT1TdnDhqOSyZjk9X32h # LxeFO32yB8h+oLfNemQMOffu5VlvQNpv03uniXtyfl+1YvEjyiln/XMB0f2fOs6+ # Es1dVIkmuRH6seIrg4k9Ye7qqggsmoed7vetTae561PpwYiYOAJ62BaG0rXJT/js # yU18+IEUu+NJyCOoexEwv2jtTKAjath+t70YZFwbSc934m4KLh4kNWmecq1HoeE4 # erFYCu+rgFwUrBk8iwO7vae3Ierdn3KBAj0mnJo2umSa132+rSeJJE0beg== # SIG # End signature block x

Windows NT KPTV 6.2 build 9200 (Windows Server 2012 Datacenter Edition) i586