Automation/Generate-VaaSReport.ps1
<##############################################################
# # # Copyright (C) Microsoft Corporation. All rights reserved. # # # ##############################################################> <# .SYNOPSIS Launches a VaaS Test Pass based off of the supplied parameters. .DESCRIPTION Launches a VaaS Test Pass based off of the supplied parameters using the AzureStackVaaS PowerShell cmdlets. .PARAMETER VaaSSolutionName VaaSSolutionName .PARAMETER VaaSTestPassName VaaSTestPassName .PARAMETER VaaSAccountCreds VaaSAccountCreds - PSCredential type .PARAMETER VaaSApplicationId VaaSApplicationId .PARAMETER VaaSAccountTenantId VaaSAccountTenantId .PARAMETER ProcessTestLogsFromPath Specifies directory containing zipped log files (Results) to be processed when generating the VaaS Report. If left empty, logs will be downloaded to a subdirectory named "Logs" for processing. .PARAMETER LogFilePath Path to log files from tests. .PARAMETER VaaSPackageUri Optional. Overrides default VaaS package storage location .PARAMETER VaaSPortalUri Optional. Specifies VaaS Portal Endpoint. .PARAMETER VaaSBaseServiceUri Optional. Specifies VaaS Service Endpoint. .PARAMETER VaaSServiceResourceId VaaSServiceResourceId .PARAMETER ParseLocalLogs ParseLocalLogs switch is used in conjunction with parameters .PARAMETER UseVaaSSPNAuth The UseVaaSSPNAuth switch specifies that SPN credentials are supplied via parameter VaaSAccountCreds. Note that parameters VaaSApplicationId and VaaSApplicationUri are not applicable when specifying the UseVaaSSPNAuth switch. .EXAMPLE $secpasswd = ConvertTo-SecureString “*****************” -AsPlainText -Force $creds = New-Object System.Management.Automation.PSCredential (“******************”, $secpasswd) .\Generate-VaaSReport.ps1 -VaaSAccountCreds $creds -VaaSAccountTenantId <TenantId> -VaaSSolutionName <SolutionName> -VaaSTestPassName <TestPassName> .LINK https://www.powershellgallery.com/packages/AzureStackVaaS #> param( [Parameter(Mandatory=$true)][string]$VaaSSolutionName=$null, [Parameter(Mandatory=$true)][string]$VaaSTestPassName=$null, [Parameter(Mandatory=$true)][ValidateNotNull()][PSCredential]$VaaSAccountCreds, [Parameter(Mandatory=$false)][string]$VaaSApplicationId=$null, [Parameter(Mandatory=$true)][ValidateNotNull()][string]$VaaSAccountTenantId, [Parameter(Mandatory=$false)][string]$ProcessTestLogsFromPath=$null, [Parameter(Mandatory=$false)][string]$LogFilePath=$null, [Parameter(Mandatory=$false)][string]$VaaSPackageUri=$null, [Parameter(Mandatory=$false)][string]$VaaSPortalUri=$null, [Parameter(Mandatory=$false)][string]$VaaSApplicationUri=$null, [Parameter(Mandatory=$false)][string]$VaaSBaseServiceUri=$null, [Parameter(Mandatory=$false)][string]$VaaSServiceResourceId=$null, [Parameter(Mandatory=$false)][switch]$ParseLocalLogs, [Parameter(Mandatory=$false)][switch]$UseVaaSSPNAuth ) [System.Reflection.Assembly]::LoadWithPartialName("mscorlib") | Out-Null Add-Type -TypeDefinition @" public struct TestData { public string testName; public System.TimeSpan duration; public int testsPassed; public int testsFailed; public int testsTotal; } "@ New-Variable -Name BaseURIVaaSStorage -Value "https://vaastestpacksprodeastus.blob.core.windows.net" -Option Constant New-Variable -Name BaseURIVaaSPortal -Value "https://azurestackvalidation.com" -Option Constant New-Variable -Name VaaSPackageDirectory -Value "$env:Temp\VaaS" New-Variable -Name ProductionRun -Value $true function CreateVaaSLogDirectory { if (!(Test-Path -Path "$LogFilePath" -PathType Container)) { Write-Verbose "Creating VaaS Log Directory ..." New-Item -ItemType Directory -Path $LogFilePath | Out-Null } } function InstallVaaSPSModules { Write-Host "Installing Azure Stack VaaS PowerShell Module" if($script:ProductionRun -eq $true) { Write-Host "Installing Azure Stack VaaS PowerShell Module from PowerShell Gallery ..." Install-Module -Name AzureStackVaaS -Scope AllUsers -AllowClobber -Force -ErrorVariable InstallError -ErrorAction SilentlyContinue if($InstallError) { Write-Warning "Couldn't install Azure Stack VaaS PowerShell module..." } } else { $vaasPSModulesUri = "$VaaSPackageUri/AzureStackVaaS.zip" Write-Host "Downloading latest Azure Stack VaaS PowerShell Module from:" Write-Host $vaasPSModulesUri try { Invoke-WebRequest -Uri $vaasPSModulesUri -outfile "$VaaSPackageDirectory\AzureStackVaaS.zip" } catch { Write-Warning "Unable to download file from $vaasPSModulesUri" Write-Warning "Attempting retry in 30 seconds ..." Start-Sleep -s 30 | Out-Null try { Invoke-WebRequest -Uri $vaasPSModulesUri -outfile "$VaaSPackageDirectory\AzureStackVaaS.zip" } catch { Write-Warning "Unable to download file on retry from:`r`n$vaasPSModulesUri" return } } $psModulePath = $env:ProgramFiles + '\WindowsPowerShell\Modules' Write-Host "Extracting latest Azure Stack VaaS PowerShell Module to:`r`n$psModulePath" Expand-Archive -Path "$VaaSPackageDirectory\AzureStackVaaS.zip" -DestinationPath "$psModulePath" -Force -ErrorAction SilentlyContinue -ErrorVariable ExpandError if($ExpandError) { if($ExpandError[0].CategoryInfo.Reason -imatch 'UnauthorizedAccessException') { Write-Warning "A PowerShell lock currently exists on the Azure Stack VaaS PowerShell." Write-Warning "To update binaries, please close the applicable PowerShell window or ISE." } } } } function DownloadLogs { param( [parameter(Mandatory=$true)][string]$testLaunchName, [parameter(Mandatory=$true)][string]$testName, [parameter(Mandatory=$true)][string]$archiveFullName ) Get-AzureStackVaaSTestLaunchLogs -SolutionName $VaaSSolutionName -TestPassName $VaaSTestPassName -TestLaunchName $testLaunchName -Path $archiveFullName -ErrorAction SilentlyContinue -ErrorVariable downloadError if($downloadError) { if($downloadError[0].CategoryInfo.Reason -imatch 'AggregateException') { Write-Warning "Unable to download logs for test $testLaunchName to $archiveFullName" Write-Warning "Command used to download logs:" Write-Warning "Get-AzureStackVaaSTestLaunchLogs -SolutionName $VaaSSolutionName -TestPassName $VaaSTestPassName -TestLaunchName $testLaunchName -Path $archiveFullName" Write-Warning "" Start-Sleep -s 2 return "" } } Write-Host "Logs for test $testName [$testLaunchName] downloaded to $archiveFullName" return $archiveFullName } function ParseTrxLog { param( [parameter(Mandatory=$true)][string]$trxFullName ) try { $resultXML = [xml](Get-Content -Path $trxFullName) } catch { Write-Warning "Log file appears to be corrupt. Please check file content structure ..." return $null } if(!($resultXML)) { Write-Error "Unable to get contents of file $trxFullName!" return $null } Write-Host "Parsing TRX log $trxFullName ..." $testData = New-Object TestData $testData.duration = [DateTime]::Parse($($resultXML.TestRun.Times.finish)) - [DateTime]::Parse($($resultXML.TestRun.Times.start)) $testData.testsPassed = $resultXML.TestRun.ResultSummary.Counters.passed $testData.testsFailed = $resultXML.TestRun.ResultSummary.Counters.failed $testData.testsTotal = $resultXML.TestRun.ResultSummary.Counters.total return $testData } function ParseWtlLog { param( [parameter(Mandatory=$true)][string]$wtlFullName ) try { $resultXML = [xml](Get-Content -Path $wtlFullName) } catch { Write-Warning "Log file appears to be corrupt. Please check file content structure ..." return $null } if(!($resultXML)) { Write-Error "Unable to get contents of file $wtlFullName ..." return $null } Write-Host "Parsing $wtlFullName ..." [uint64] $OneSecond100ns = 10000000; $testData = New-Object TestData $startDateTime = ([DateTime] ("{1:D2}/{2:D2}/{0:D4} {3:D2}:{4:D2}:{5:D2}.{6:D3}" -f $resultXML.'WTT-Logger'.RTI.BaseTime.Split(': '))) $frequency = [uint64]$resultXML.'WTT-Logger'.RTI.Frequency $lastCA = [uint64]($resultXML.'WTT-Logger'.GetElementsByTagName("EndTest") | Select-Object -Last 1).CA $testData.duration = $startDateTime.AddTicks( ($lastCA*$OneSecond100ns) / $frequency) - $startDateTime $testData.testsPassed = 0 $testData.testsFailed = 0 $testData.testsTotal = ($resultXML.'WTT-Logger'.GetElementsByTagName("EndTest")).Count $results = $resultXML.'WTT-Logger'.EndTest foreach($result in $results) { if($result.Result -imatch "Pass") { $testData.testsPassed++ } elseif($result.Result -imatch "fail") { $testData.testsFailed++ } } return $testData } function IsLogDirectoryExist { param( [parameter(Mandatory=$true)][string]$LogFilePath ) Write-Host "Verifying if $LogFilePath exists ..." if(Test-Path -LiteralPath $LogFilePath -PathType Container) { return $true } return $false } function IsLogsExist { param( [parameter(Mandatory=$true)][string]$testArchive ) Write-Host "Verifying $testArchive exists ..." if(Test-Path -Path $testArchive -PathType Leaf) { return $true } return $false } function IsTrxLogFormat { param( [parameter(Mandatory=$true)][string]$logPath ) Write-Host "Searching for TRX log file in $logPath..." $logFile = Get-ChildItem -Path "$logPath\*.trx" -Recurse -Force if(!($logFile)) { return $false } $logFileFullName = ($logFile | Sort-Object LastWriteTime -descending)[0].FullName Write-Host "Found TRX log file at: $logFileFullName" -ForegroundColor Green return $true } function IsWtlLogFormat { param( [parameter(Mandatory=$true)][string]$logPath ) Write-Host "Searching for WTL log file in $logPath..." $logFile = Get-ChildItem -Path "$logPath\*.wtl" -Recurse -Force if($logFile -eq $null) { Write-Host "Log file with WTL file extension not found ..." Write-Host "Searching for WTL log file with XML file extension..." $logFile = Get-ChildItem -Path "$logPath\*.xml" -Recurse -Force if($logFile -eq $null) { Write-Host "No files with XML file extension found ..." return $false } $logFileFullName = ($logFile | Sort-Object LastWriteTime -descending)[0].FullName Write-Host "Verifying that $logFileFullName contains WTT-Logger element ..." try { $resultXML = [xml](Get-Content -Path $logFileFullName) } catch { Write-Warning "Log file appears to be corrupt. Please check file content structure ..." return $false } $elements = $resultXML.GetElementsByTagName("WTT-Logger") if($elements.Count -eq 0) { Write-Warning "WTT-Logger document element not found in $logFileFullName ..." return $false } Write-Host "Found WTT-Logger document element" } $logFileFullName = ($logFile | Sort-Object LastWriteTime -descending)[0].FullName Write-Host "Found WTL log file at: $logFileFullName" -ForegroundColor Green return $true } function IsTestCompleted { param( [parameter(Mandatory=$true)][PSVaaSTestManifest]$vaasTestManifest ) if(($vaasTestManifest.State -eq "Accepted") -or ($vaasTestManifest.State -eq "Running")) { return $false } return $true } function GetTrxLogFileFullName { param( [parameter(Mandatory=$true)][string]$logPath ) $logFile = Get-ChildItem -Path "$logPath\*.trx" -Recurse -Force if(!($logFile)) { return $null } $logFileFullName = ($logFile | Sort-Object LastWriteTime -descending)[0].FullName return $logFileFullName } function GetWtlLogFileFullName { param( [parameter(Mandatory=$true)][string]$logPath ) $logFile = Get-ChildItem -Path "$logPath\*.wtl" -Recurse -Force if($logFile -eq $null) { $logFile = Get-ChildItem -Path "$logPath\*.xml" -Recurse -Force if($logFile -eq $null) { return $null } $logFileFullName = ($logFile | Sort-Object LastWriteTime -descending)[0].FullName $resultXML = [xml](Get-Content -Path $logFileFullName) $elements = $resultXML.GetElementsByTagName("WTT-Logger") if($elements.Count -eq 0) { return $null } } else { $logFileFullName = ($logFile | Sort-Object LastWriteTime -descending)[0].FullName } return $logFileFullName } function GetAllTestsFromTestPass { $vaasTestManifests = Find-AzureStackVaaSTestLaunch -SolutionName $VaaSSolutionName -TestPassName $VaaSTestPassName -ErrorAction SilentlyContinue -ErrorVariable vaasTestManifestError if($vaasTestManifestError -or ($vaasTestManifests -eq $null)) { Write-Warning "Did not find any test manifest data for Solution $VaaSSolutionName Test pass $VaaSTestPassName" $emptyArray = @() return ,$emptyArray } return $vaasTestManifests } function GetPendingTests() { param( [parameter(Mandatory=$true)][AllowEmptyCollection()][Object[]]$vaasTestManifests ) $pendingTests = $vaasTestManifests | ? {$_.State -eq "Accepted" -or $_.State -eq "Running"} -ErrorAction SilentlyContinue -ErrorVariable vaasTestManifestError if($vaasTestManifestError) { Write-Verbose "No pending tests found for Solution $VaaSSolutionName Test pass $VaaSTestPassName" $emptyArray = @() return ,$emptyArray } return $pendingTests } function GetCanceledTests() { param( [parameter(Mandatory=$true)][AllowEmptyCollection()][Object[]]$vaasTestManifests ) $cancelledTests = $vaasTestManifests | ? {$_.State -eq "Cancelled"} -ErrorAction SilentlyContinue -ErrorVariable vaasTestManifestError if($vaasTestManifestError) { Write-Verbose "No cancelled tests found for Solution $VaaSSolutionName Test pass $VaaSTestPassName" $emptyArray = @() return ,$emptyArray } return $cancelledTests } function GetAbortedTests() { param( [parameter(Mandatory=$true)][AllowEmptyCollection()][Object[]]$vaasTestManifests ) $abortedTests = $vaasTestManifests | ? {$_.State -eq "Aborted"} -ErrorAction SilentlyContinue -ErrorVariable vaasTestManifestError if($vaasTestManifestError) { Write-Verbose "No aborted tests found for Solution $VaaSSolutionName Test pass $VaaSTestPassName" $emptyArray = @() return ,$emptyArray } return $abortedTests } function GetRantoCompletionTests() { param( [parameter(Mandatory=$true)][AllowEmptyCollection()][Object[]]$vaasTestManifests ) $completedTests = $vaasTestManifests | ? {$_.State -eq "Succeeded" -or $_.State -eq "Failed"} -ErrorAction SilentlyContinue -ErrorVariable vaasTestManifestError if($vaasTestManifestError) { Write-Verbose "No completed tests found for Solution $VaaSSolutionName Test pass $VaaSTestPassName" $emptyArray = @() return ,$emptyArray } return $completedTests } function GetTestStatus { param( [parameter(Mandatory=$true)][PSVaaSTestManifest]$vaasTestManifest ) return $($vaasTestManifest.State) } function ValidateAndResolveAuthMethodParameters { if((($VaaSPortalUri -eq "") -or ($VaaSPortalUri -imatch $BaseURIVaaSPortal)) -and ($VaaSBaseServiceUri -eq "") -and ($VaaSServiceResourceId -eq "")) { $script:ProductionRun = $true } elseif((($VaaSPortalUri -ne "") -or (!($VaaSPortalUri -imatch $BaseURIVaaSPortal))) -and ($VaaSBaseServiceUri -ne "") -and ($VaaSServiceResourceId -ne "")) { $script:ProductionRun = $false } else { throw [System.ArgumentException] "Parameters VaaSPortalUri, VaaSBaseServiceUri, and VaaSServiceResourceId must be either all specified, or none specified ..." } if(($script:ProductionRun) -or ($UseVaaSSPNAuth)) { if(($VaaSApplicationId -ne "") -or ($VaaSApplicationUri -ne "")) { throw [System.ArgumentException] "Parameters VaaSApplicationId and/or VaaSApplicationUri should not be specified when launching a production level run or when using SPN credentials ..." } } elseif(!$UseVaaSSPNAuth) { Write-Host "Verifying that parameters VaaSApplicationId and VaaSApplicationUri are present as`r`nthe UseVaaSSPNAuth switch was not specified..." if(($VaaSApplicationId -eq "") -or ($VaaSApplicationUri -eq "")) { throw [System.ArgumentException] "Parameters VaaSApplicationId and VaaSApplicationUri must both be specified when using non-SPN credentials are supplied ..." } } } function ValidateAndResolveTestParameters { if($VaaSPackageUri -eq "") { $script:VaaSPackageUri = "$BaseURIVaaSStorage/packages" Write-Host "VaaS Storage endpoint updated to:`r`n$VaaSPackageUri" } if($VaaSPortalUri -eq "") { $script:VaaSPortalUri = $BaseURIVaaSPortal Write-Host "VaaS Portal endpoint updated to:`r`n$VaaSPortalUri" } if($ProcessTestLogsFromPath -ne "") { if(!(IsLogDirectoryExist -LogFilePath $ProcessTestLogsFromPath)) { throw [System.ArgumentException] "Directory $ProcessTestLogsFromPath cannot be found or is not accessible. Please check and try again ..." } } } function ValidateAndResolveParameters { Write-Host "Validating and Resolving Parameters" ValidateAndResolveAuthMethodParameters ValidateAndResolveTestParameters } function AddAzureAccount { Write-Host "Adding AzureStack VaaS Account" if($script:ProductionRun -eq $true) { Write-Host "Production Run ..." if($UseVaaSSPNAuth) { Write-Host "Using SPN Authentication" Add-AzureStackVaaSAccount -Credential $VaaSAccountCreds -ServicePrincipal -TenantId $VaaSAccountTenantId -ErrorAction Stop } else { Write-Host "Using User Authentication" Add-AzureStackVaaSAccount -Credential $VaaSAccountCreds -TenantId $VaaSAccountTenantId -ErrorAction Stop } } else { Write-Host "Non-Production Run ..." if($UseVaaSSPNAuth) { Write-Host "Using SPN Authentication" Add-AzureStackVaaSAccount -Credential $VaaSAccountCreds -ServicePrincipal -TenantId $VaaSAccountTenantId -TenantServiceBaseUri $VaaSBaseServiceUri -TenantServiceResourceId $VaaSServiceResourceId -ErrorAction Stop } else { Write-Host "Using User Authentication" Add-AzureStackVaaSAccount -Credential $VaaSAccountCreds -ApplicationId $VaaSApplicationId -ApplicationUri $VaaSApplicationUri -TenantId $VaaSAccountTenantId -TenantServiceBaseUri $VaaSBaseServiceUri -TenantServiceResourceId $VaaSServiceResourceId -ErrorAction Stop } } } function GenerateWarningBlock { param( [parameter(Mandatory=$true)][ValidateNotNull()][string]$warningTitle, [parameter(Mandatory=$true)][Object[]]$vaasTestManifests ) [string]$warningData = "" $warningData += "<li>$warningTitle</li>`n" $warningData += " <ul>`n" foreach($test in $vaasTestManifests) { $warningData += "<li>$($test.TestName) [$($test.TestLaunchName)]</li>`n" } $warningData += " </ul>`n" return $warningData } function GeneratePreContent { $testPass = Get-AzureStackVaaSTestPass -SolutionName $VaaSSolutionName -Name $VaaSTestPassName -ErrorAction Stop [string]$preContent = "<p class='body'><b>Tenant Id:</b> $VaaSAccountTenantId</p>`n" $preContent += "<p class='body'><b>Solution Name:</b> $VaaSSolutionName</p>`n" $preContent += "<p class='body'><b>Test Pass Name:</b> $VaaSTestPassName</p>`n" $preContent += "<p class='body'><b>Test Pass Date Created:</b> $($testPass.CreatedTimeUtc) (UTC)</p>`n" $preContent += "<p class='body'><b>Test Pass Tags</b><br/>`n" foreach ($tg in $testPass.Tags.GetEnumerator()) { $preContent += "$($tg.Name.PadLeft(16, " ")) : $($tg.Value)<br/>`n" } $preContent += "</p>`n" $preContent += "<p class='body'><b>Test Details</b></p>`n" return $preContent } function GeneratePostContent { param( [parameter(Mandatory=$true)][AllowEmptyCollection()][Object[]]$vaasTestManifests, [parameter(Mandatory=$true)][AllowEmptyCollection()][Object[]]$vaasTestsWOLogs, [parameter(Mandatory=$true)][AllowEmptyCollection()][Object[]]$vaasTestsWOSupportedLogs, [parameter(Mandatory=$true)][AllowEmptyCollection()][Object[]]$vaasTestsLogError ) [bool]$Notes = $false [string]$noteData = "" [string]$postContent = "<p class='body'><b>Warning(s):</b><br/>`n" if($vaasTestManifests) { $cancelledTests = GetCanceledTests -vaasTestManifests $vaasTestManifests if($cancelledTests.Count -gt 0) { $noteData += GenerateWarningBlock -warningTitle "Test pass has one or more tests in canceled state:" -vaasTestManifests $cancelledTests } $abortedTests = GetAbortedTests -vaasTestManifests $vaasTestManifests if($abortedTests.Count -gt 0) { $noteData += GenerateWarningBlock -warningTitle "Test pass has one or more tests in aborted state:" -vaasTestManifests $abortedTests } $pendingTests = GetPendingTests -vaasTestManifests $vaasTestManifests if($pendingTests.Count -gt 0) { $noteData += GenerateWarningBlock -warningTitle "Test pass has one or more tests in a pending state:" -vaasTestManifests $pendingTests } if($vaasTestsWOLogs.Count -gt 0) { $noteData += GenerateWarningBlock -warningTitle "Test pass has one or more tests without logs:" -vaasTestManifests $vaasTestsWOLogs } if($vaasTestsWOSupportedLogs.Count -gt 0) { $noteData += GenerateWarningBlock -warningTitle "Test pass has one or more tests without supported log files (TRX, WTL):" -vaasTestManifests $vaasTestsWOSupportedLogs } if($vaasTestsLogError.Count -gt 0) { $noteData += GenerateWarningBlock -warningTitle "Test pass has one or more tests where the log archive could not be extracted:" -vaasTestManifests $vaasTestsLogError } if($noteData -eq "") { $noteData += "<i>N/A</i><br/>`n" } else { $postContent += "<div>`n" $postContent += " <ul>`n" $noteData += " </ul>`n" $noteData += "</div>`n" } } else { $noteData += "<i>No tests were found for this test pass.</i><br/>`n" } $postContent += $noteData $postContent += "</p>" $postContent += "<p class='body'><b>Portal URL:</b><br/>`n" $postContent += "<a href='$VaaSPortalUri/TestPass/Manage/Summary?solutionName=$VaaSSolutionName&testPassName=$VaaSTestPassName'>" $postContent += "$VaaSPortalUri/TestPass/Manage/Summary?solutionName=$VaaSSolutionName&testPassName=$VaaSTestPassName</a></p>`n" $postContent += "<p class='body'><b>Contact:</b><br/>`n" $postContent += " <a href=""mailto:vaashelp@microsoft.com"">vaashelp@microsoft.com</a></p>`n" $postContent += "<p class='body'><font size=""-1"">Generated using VaaS Reporting Module version 1.1.4</font></p>" return $postContent } function GetTestDataFromManifest { param( [parameter(Mandatory=$true)][AllowEmptyCollection()][Object]$TestManifest ) $testData = New-Object TestData $testData.testName = $TestManifest.TestName $testData.duration = 0 $testData.testsPassed = $TestManifest.Passed $testData.testsFailed = $TestManifest.Failed $testData.testsTotal = $TestManifest.Total return $testData } function Main { $Container = @() $totalTests = 0 $totalPassed = 0 $totalFailed = 0 $totalDuration = New-TimeSpan $style = Get-Content -Path ".\VaaSStyle.css" $vaasTestsWOLogs = @() $vaasTestsWOSupportedLogs = @() $vaasTestsLogError = @() [bool]$downloadArchive = $true $jsonResults = @() if($ProcessTestLogsFromPath -eq "") { $testArchivePath = $LogFilePath } else { $testArchivePath = $ProcessTestLogsFromPath $downloadArchive = $false } $vaasTestManifests = GetAllTestsFromTestPass foreach($vaasTestManifest in $vaasTestManifests) { if($ParseLocalLogs) { $testArchive = $testArchivePath + "\" + $vaasTestManifest.TestLaunchName + ".zip" if($downloadArchive) { $testArchive = DownloadLogs -testLaunchName $vaasTestManifest.TestLaunchName -testName $vaasTestManifest.TestName -archiveFullName $testArchive if($testArchive -eq "") { Write-Warning "Skipping test $($vaasTestManifest.TestName)" $vaasTestsWOLogs += $vaasTestManifest continue } } if(!(IsLogsExist -testArchive $testArchive)) { Write-Warning "Skipping test $($vaasTestManifest.TestName), as log archive at $testArchive does not exist ..." $vaasTestsWOLogs += $vaasTestManifest continue } $outputPath = "$testArchivePath\$($vaasTestManifest.TestLaunchName)" Write-Host "Expanding Archive $testArchive to path $outputPath ..." try { Expand-Archive -LiteralPath "$testArchive" -DestinationPath "$outputPath" -Force -ErrorAction Ignore } catch { Write-Error "PowerShell cmdlet Expand-Archive failed to extract log files from $testArchive" $vaasTestsLogError += $vaasTestManifest continue } if(IsTrxLogFormat -logPath $outputPath) { $logFileFullName = GetTrxLogFileFullName -logPath $outputPath $testData = ParseTrxLog -trxFullName $logFileFullName if($testData -eq $null) { $vaasTestsWOSupportedLogs += $vaasTestManifest continue } } elseif(IsWtlLogFormat -logPath $outputPath) { $logFileFullName = GetWtlLogFileFullName -logPath $outputPath $testData = ParseWtlLog -wtlFullName $logFileFullName if($testData -eq $null) { $vaasTestsWOSupportedLogs += $vaasTestManifest continue } } else { $vaasTestsWOSupportedLogs += $vaasTestManifest continue } } else { $testData = GetTestDataFromManifest -TestManifest $vaasTestManifest } $jsonResults += $testData $totalDuration += $testData.duration $totalPassed += $testData.testsPassed $totalFailed += $testData.testsFailed $totalTests += $testData.testsTotal if ($testData.testsTotal -gt 0) { if ($testData.testsPassed -gt 0) { $passedString = 'Green' + $testData.testsPassed.ToString() } else { $passedString = 'Red' + $testData.testsPassed.ToString() } $passedPerc = [math]::round(($testData.testsPassed/$testData.testsTotal)*100,2) if($passedPerc -gt 0) { $passedPercString = 'Green' + $passedPerc.ToString() } else { $passedPercString = 'Red' + $passedPerc.ToString() } $failedPerc = [math]::round(($testData.testsFailed/$testData.testsTotal)*100,2) if (($failedPerc -eq 0) -and ($passedPerc -gt 0)) { $failedPercString= 'Green' + $failedPerc.ToString() } else { $failedPercString= 'Red' + $failedPerc.ToString() } if (($testData.testsFailed -eq 0) -and ($testData.testsPassed -gt 0)) { $failedString = 'Green' + $testData.testsFailed.ToString() } else { $failedString = 'Red' + $testData.testsFailed.ToString() } } else { $passedString = $failedString = $passedPercString = $failedPercString = 'Red0'; } $totalString = "$($testData.testsTotal)" $testLaunchNameString = $vaasTestManifest.TestLaunchName.ToString() $logsLink = "<a href='$VaaSPortalUri/TestPass/Manage/DownloadLog?solutionName=$VaaSSolutionName&testPassName=$VaaSTestPassName&testManifestName=$testLaunchNameString'>Download</a>" $blob = New-Object System.Object $blob | Add-Member -type NoteProperty -name "Test Name" -value "$($vaasTestManifest.TestName)" $blob | Add-Member -type NoteProperty -name Total -value $totalString $blob | Add-Member -type NoteProperty -name Passed -value $passedString $blob | Add-Member -type NoteProperty -name Failed -value $failedString $blob | Add-Member -type NoteProperty -name Passed% -value "$passedPercString%" $blob | Add-Member -type NoteProperty -name Failed% -value "$failedPercString%" $blob | Add-Member -type NoteProperty -name Duration -Value $testData.duration.ToString(); $blob | Add-Member -type NoteProperty -name Logs -Value "$logsLink" $Container += $blob } if ($totalTests -gt 0) { $totalPassedPerc = 'Green' + [math]::round(($totalPassed/$totalTests)*100,2) $totalFailedPercVal = [math]::round(($totalFailed/$totalTests)*100,2) } else { $totalPassedPerc = 'Red0'; $totalFailedPercVal = 0 } $totalPassedString = "Green$totalPassed" if ($totalFailedPercVal -eq 0) { $totalFailedPerc = 'Green' + $totalFailedPercVal.ToString(); } else { $totalFailedPerc = 'Red' + $totalFailedPercVal.ToString(); } if ($totalFailed -eq 0) { $totalFailedString = "Green$totalFailed" } else { $totalFailedString = "Red$totalFailed" } $totalTestsString = "$totalTests" $blob = New-Object System.Object $blob | Add-Member -type NoteProperty -name "Test Name" -value "Total" $blob | Add-Member -type NoteProperty -name Total -value $totalTestsString $blob | Add-Member -type NoteProperty -name Passed -value $totalPassedString $blob | Add-Member -type NoteProperty -name Failed -value $totalFailedString $blob | Add-Member -type NoteProperty -name Passed% -value "$totalPassedPerc%" $blob | Add-Member -type NoteProperty -name Failed% -value "$totalFailedPerc%" $blob | Add-Member -type NoteProperty -name Duration -Value $totalDuration.ToString() $Container += $blob $preContent = GeneratePreContent $postContent = GeneratePostContent -vaasTestManifests $vaasTestManifests -vaasTestsWOLogs $vaasTestsWOLogs -vaasTestsWOSupportedLogs $vaasTestsWOSupportedLogs -vaasTestsLogError $vaasTestsLogError $results = $Container | ConvertTo-Html -Head $style -PreContent $preContent -PostContent $postContent $results = $results -Replace "<td>Green","<td class='pass'>" $results = $results -Replace "<td>Red","<td class='fail'>" $results = $results -Replace "<","<" $results = $results -Replace ">",">" $results = $results -Replace "'","'" $results | Out-File -FilePath ".\VaaSResults-$VaaSSolutionName-$VaaSTestPassName.html" -Encoding ASCII $jsonResults | ConvertTo-Json | Out-File -FilePath ".\VaaSResults-$VaaSSolutionName-$VaaSTestPassName.json" } Set-PSDebug -Strict if($LogFilePath -eq "") { $LogFilePath = (Get-Item -Path ".\" -Verbose).FullName + '\Logs' Write-Host "Defaulting to VaaS log file path $LogFilePath, as a log file path was not provided ..." } CreateVaaSLogDirectory $logFileName = "$LogFilePath\Generate-VaaSReport_$(Get-Date -Format yyyyMMdd-HHmmss).log" Start-Transcript -Path $logFileName Write-Output "`r`n" [int]$reportResult = 0 try { Write-Output "PsBoundParameters: " foreach($keyValue in $PsBoundParameters.GetEnumerator()) { Write-Output "$($keyValue.Key)=$($keyValue.Value)" } Write-Output "" ValidateAndResolveParameters InstallVaaSPSModules Import-Module AzureStackVaaS -Force -ErrorAction Stop Write-Host "`r`nAzureStackVaaS module version loaded: $((Get-Module -Name AzureStackVaaS).Version.ToString())`r`n" AddAzureAccount Main } catch { $_ | Format-List * -Force | Out-Default; $_.InvocationInfo | Format-List * | Out-Default $Exception = $_.Exception for ($i = 0; $Exception; $i++, ($Exception = $_.InnerException)) { "$i" * 80 $Exception | Format-List * -Force | Out-Default } $reportResult = 1 } finally { Remove-Module AzureStackVaaS -Force -ErrorAction Ignore Stop-Transcript } return $reportResult # SIG # Begin signature block # MIIdpQYJKoZIhvcNAQcCoIIdljCCHZICAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUfGAqLnFZvxl76FV9B2b0jwY3 # aGygghhlMIIEwzCCA6ugAwIBAgITMwAAALWsfW2HayYRRwAAAAAAtTANBgkqhkiG # 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw # HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTYwOTA3MTc1ODQ0 # WhcNMTgwOTA3MTc1ODQ0WjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNO # OkI4RUMtMzBBNC03MTQ0MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT # ZXJ2aWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApXwz2j7k2rDl # 2QO9eyz1qUm3FyqD7dksbP5M3NCOq/j95vpOeHG2w0S1SyNmN8VEqjiHSeopO5b+ # VbOIbpqqG9PyfyDc0WdzIilufZOuwyZI15hI3uRgZ78E/cbljXUW5Me75jGGEOlr # Gek41eOyGRUxkejFapqkiHCLxHSMHEpPdT95ylPhuLz7Bq01fsQSbclDoQye3EzO # YFlqcFMYb3s61siEbpvKgf0qcQjPzAh3vsySXqzeeLc3Kzss74E9HDduQGO1ZZTZ # FadL4bzwlgVhux25DZr0zqybZIBiy8/J9oyKCi2OuWLqxf+YgSWp0YMY9ktvKwGr # VW7W8/UJVwIDAQABo4IBCTCCAQUwHQYDVR0OBBYEFIMd6iA083bzGHST2k2O6R6l # XnyFMB8GA1UdIwQYMBaAFCM0+NlSRnAK7UD7dvuzK7DDNbMPMFQGA1UdHwRNMEsw # SaBHoEWGQ2h0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3Rz # L01pY3Jvc29mdFRpbWVTdGFtcFBDQS5jcmwwWAYIKwYBBQUHAQEETDBKMEgGCCsG # AQUFBzAChjxodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY3Jv # c29mdFRpbWVTdGFtcFBDQS5jcnQwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI # hvcNAQEFBQADggEBAAez+vxJWgDsgMtouMLKUcbt+zRbXcxWm2HmTU7rhIVVyh2E # IFS5ebVknSGsKoR1/xlEmnMo3fHtvWaDRo/2qXIg1jMnOQp1d4wqFh9hKfnDeCQA # 9tCnM8C/mYu3axXxKmyxJXDOm2MqcoZ9CBlmk96o/hzV9QWo5c+Y94j7qEYpGRPG # 6Adqoc/HNxnce3Ik0ZlpbD8TbmbIjDORxQ3Jjbn3AGXBQ+smsInwWFzut2EwpGPC # 2xWhLjXLdzJReIM1geh3oM/wti4zZ4w7hr4CvedMnU29OkcnoyMEUAQnZfB7PsXm # adKxnklsJCsr1UOu7g/nwX5/mcw7R9G3RSvrI0EwggYHMIID76ADAgECAgphFmg0 # AAAAAAAcMA0GCSqGSIb3DQEBBQUAMF8xEzARBgoJkiaJk/IsZAEZFgNjb20xGTAX # BgoJkiaJk/IsZAEZFgltaWNyb3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBSb290 # IENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0wNzA0MDMxMjUzMDlaFw0yMTA0MDMx # MzAzMDlaMHcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xITAf # BgNVBAMTGE1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQTCCASIwDQYJKoZIhvcNAQEB # BQADggEPADCCAQoCggEBAJ+hbLHf20iSKnxrLhnhveLjxZlRI1Ctzt0YTiQP7tGn # 0UytdDAgEesH1VSVFUmUG0KSrphcMCbaAGvoe73siQcP9w4EmPCJzB/LMySHnfL0 # Zxws/HvniB3q506jocEjU8qN+kXPCdBer9CwQgSi+aZsk2fXKNxGU7CG0OUoRi4n # rIZPVVIM5AMs+2qQkDBuh/NZMJ36ftaXs+ghl3740hPzCLdTbVK0RZCfSABKR2YR # JylmqJfk0waBSqL5hKcRRxQJgp+E7VV4/gGaHVAIhQAQMEbtt94jRrvELVSfrx54 # QTF3zJvfO4OToWECtR0Nsfz3m7IBziJLVP/5BcPCIAsCAwEAAaOCAaswggGnMA8G # A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFCM0+NlSRnAK7UD7dvuzK7DDNbMPMAsG # A1UdDwQEAwIBhjAQBgkrBgEEAYI3FQEEAwIBADCBmAYDVR0jBIGQMIGNgBQOrIJg # QFYnl+UlE/wq4QpTlVnkpKFjpGEwXzETMBEGCgmSJomT8ixkARkWA2NvbTEZMBcG # CgmSJomT8ixkARkWCW1pY3Jvc29mdDEtMCsGA1UEAxMkTWljcm9zb2Z0IFJvb3Qg # Q2VydGlmaWNhdGUgQXV0aG9yaXR5ghB5rRahSqClrUxzWPQHEy5lMFAGA1UdHwRJ # MEcwRaBDoEGGP2h0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1 # Y3RzL21pY3Jvc29mdHJvb3RjZXJ0LmNybDBUBggrBgEFBQcBAQRIMEYwRAYIKwYB # BQUHMAKGOGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljcm9z # b2Z0Um9vdENlcnQuY3J0MBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEB # BQUAA4ICAQAQl4rDXANENt3ptK132855UU0BsS50cVttDBOrzr57j7gu1BKijG1i # uFcCy04gE1CZ3XpA4le7r1iaHOEdAYasu3jyi9DsOwHu4r6PCgXIjUji8FMV3U+r # kuTnjWrVgMHmlPIGL4UD6ZEqJCJw+/b85HiZLg33B+JwvBhOnY5rCnKVuKE5nGct # xVEO6mJcPxaYiyA/4gcaMvnMMUp2MT0rcgvI6nA9/4UKE9/CCmGO8Ne4F+tOi3/F # NSteo7/rvH0LQnvUU3Ih7jDKu3hlXFsBFwoUDtLaFJj1PLlmWLMtL+f5hYbMUVbo # nXCUbKw5TNT2eb+qGHpiKe+imyk0BncaYsk9Hm0fgvALxyy7z0Oz5fnsfbXjpKh0 # NbhOxXEjEiZ2CzxSjHFaRkMUvLOzsE1nyJ9C/4B5IYCeFTBm6EISXhrIniIh0EPp # K+m79EjMLNTYMoBMJipIJF9a6lbvpt6Znco6b72BJ3QGEe52Ib+bgsEnVLaxaj2J # oXZhtG6hE6a/qkfwEm/9ijJssv7fUciMI8lmvZ0dhxJkAj0tr1mPuOQh5bWwymO0 # eFQF1EEuUKyUsKV4q7OglnUa2ZKHE3UiLzKoCG6gW4wlv6DvhMoh1useT8ma7kng # 9wFlb4kLfchpyOZu6qeXzjEp/w7FW1zYTRuh2Povnj8uVRZryROj/TCCBhEwggP5 # oAMCAQICEzMAAACOh5GkVxpfyj4AAAAAAI4wDQYJKoZIhvcNAQELBQAwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMTAeFw0xNjExMTcyMjA5MjFaFw0xODAy # MTcyMjA5MjFaMIGDMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MQ0wCwYDVQQLEwRNT1BSMR4wHAYDVQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24w # ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDQh9RCK36d2cZ61KLD4xWS # 0lOdlRfJUjb6VL+rEK/pyefMJlPDwnO/bdYA5QDc6WpnNDD2Fhe0AaWVfIu5pCzm # izt59iMMeY/zUt9AARzCxgOd61nPc+nYcTmb8M4lWS3SyVsK737WMg5ddBIE7J4E # U6ZrAmf4TVmLd+ArIeDvwKRFEs8DewPGOcPUItxVXHdC/5yy5VVnaLotdmp/ZlNH # 1UcKzDjejXuXGX2C0Cb4pY7lofBeZBDk+esnxvLgCNAN8mfA2PIv+4naFfmuDz4A # lwfRCz5w1HercnhBmAe4F8yisV/svfNQZ6PXlPDSi1WPU6aVk+ayZs/JN2jkY8fP # AgMBAAGjggGAMIIBfDAfBgNVHSUEGDAWBgorBgEEAYI3TAgBBggrBgEFBQcDAzAd # BgNVHQ4EFgQUq8jW7bIV0qqO8cztbDj3RUrQirswUgYDVR0RBEswSaRHMEUxDTAL # BgNVBAsTBE1PUFIxNDAyBgNVBAUTKzIzMDAxMitiMDUwYzZlNy03NjQxLTQ0MWYt # YmM0YS00MzQ4MWU0MTVkMDgwHwYDVR0jBBgwFoAUSG5k5VAF04KqFzc3IrVtqMp1 # ApUwVAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br # aW9wcy9jcmwvTWljQ29kU2lnUENBMjAxMV8yMDExLTA3LTA4LmNybDBhBggrBgEF # BQcBAQRVMFMwUQYIKwYBBQUHMAKGRWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w # a2lvcHMvY2VydHMvTWljQ29kU2lnUENBMjAxMV8yMDExLTA3LTA4LmNydDAMBgNV # HRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBEiQKsaVPzxLa71IxgU+fKbKhJ # aWa+pZpBmTrYndJXAlFq+r+bltumJn0JVujc7SV1eqVHUqgeSxZT8+4PmsMElSnB # goSkVjH8oIqRlbW/Ws6pAR9kRqHmyvHXdHu/kghRXnwzAl5RO5vl2C5fAkwJnBpD # 2nHt5Nnnotp0LBet5Qy1GPVUCdS+HHPNIHuk+sjb2Ns6rvqQxaO9lWWuRi1XKVjW # kvBs2mPxjzOifjh2Xt3zNe2smjtigdBOGXxIfLALjzjMLbzVOWWplcED4pLJuavS # Vwqq3FILLlYno+KYl1eOvKlZbiSSjoLiCXOC2TWDzJ9/0QSOiLjimoNYsNSa5jH6 # lEeOfabiTnnz2NNqMxZQcPFCu5gJ6f/MlVVbCL+SUqgIxPHo8f9A1/maNp39upCF # 0lU+UK1GH+8lDLieOkgEY+94mKJdAw0C2Nwgq+ZWtd7vFmbD11WCHk+CeMmeVBoQ # YLcXq0ATka6wGcGaM53uMnLNZcxPRpgtD1FgHnz7/tvoB3kH96EzOP4JmtuPe7Y6 # vYWGuMy8fQEwt3sdqV0bvcxNF/duRzPVQN9qyi5RuLW5z8ME0zvl4+kQjOunut6k # LjNqKS8USuoewSI4NQWF78IEAA1rwdiWFEgVr35SsLhgxFK1SoK3hSoASSomgyda # Qd691WZJvAuceHAJvDCCB3owggVioAMCAQICCmEOkNIAAAAAAAMwDQYJKoZIhvcN # AQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAw # BgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEx # MB4XDTExMDcwODIwNTkwOVoXDTI2MDcwODIxMDkwOVowfjELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUg # U2lnbmluZyBQQ0EgMjAxMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB # AKvw+nIQHC6t2G6qghBNNLrytlghn0IbKmvpWlCquAY4GgRJun/DDB7dN2vGEtgL # 8DjCmQawyDnVARQxQtOJDXlkh36UYCRsr55JnOloXtLfm1OyCizDr9mpK656Ca/X # llnKYBoF6WZ26DJSJhIv56sIUM+zRLdd2MQuA3WraPPLbfM6XKEW9Ea64DhkrG5k # NXimoGMPLdNAk/jj3gcN1Vx5pUkp5w2+oBN3vpQ97/vjK1oQH01WKKJ6cuASOrdJ # Xtjt7UORg9l7snuGG9k+sYxd6IlPhBryoS9Z5JA7La4zWMW3Pv4y07MDPbGyr5I4 # ftKdgCz1TlaRITUlwzluZH9TupwPrRkjhMv0ugOGjfdf8NBSv4yUh7zAIXQlXxgo # tswnKDglmDlKNs98sZKuHCOnqWbsYR9q4ShJnV+I4iVd0yFLPlLEtVc/JAPw0Xpb # L9Uj43BdD1FGd7P4AOG8rAKCX9vAFbO9G9RVS+c5oQ/pI0m8GLhEfEXkwcNyeuBy # 5yTfv0aZxe/CHFfbg43sTUkwp6uO3+xbn6/83bBm4sGXgXvt1u1L50kppxMopqd9 # Z4DmimJ4X7IvhNdXnFy/dygo8e1twyiPLI9AN0/B4YVEicQJTMXUpUMvdJX3bvh4 # IFgsE11glZo+TzOE2rCIF96eTvSWsLxGoGyY0uDWiIwLAgMBAAGjggHtMIIB6TAQ # BgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQUSG5k5VAF04KqFzc3IrVtqMp1ApUw # GQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB # /wQFMAMBAf8wHwYDVR0jBBgwFoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0f # BFMwUTBPoE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJv # ZHVjdHMvTWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcB # AQRSMFAwTgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kv # Y2VydHMvTWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNydDCBnwYDVR0gBIGX # MIGUMIGRBgkrBgEEAYI3LgMwgYMwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvZG9jcy9wcmltYXJ5Y3BzLmh0bTBABggrBgEFBQcC # AjA0HjIgHQBMAGUAZwBhAGwAXwBwAG8AbABpAGMAeQBfAHMAdABhAHQAZQBtAGUA # bgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAZ/KGpZjgVHkaLtPYdGcimwuWEeFj # kplCln3SeQyQwWVfLiw++MNy0W2D/r4/6ArKO79HqaPzadtjvyI1pZddZYSQfYtG # UFXYDJJ80hpLHPM8QotS0LD9a+M+By4pm+Y9G6XUtR13lDni6WTJRD14eiPzE32m # kHSDjfTLJgJGKsKKELukqQUMm+1o+mgulaAqPyprWEljHwlpblqYluSD9MCP80Yr # 3vw70L01724lruWvJ+3Q3fMOr5kol5hNDj0L8giJ1h/DMhji8MUtzluetEk5CsYK # wsatruWy2dsViFFFWDgycScaf7H0J/jeLDogaZiyWYlobm+nt3TDQAUGpgEqKD6C # PxNNZgvAs0314Y9/HG8VfUWnduVAKmWjw11SYobDHWM2l4bf2vP48hahmifhzaWX # 0O5dY0HjWwechz4GdwbRBrF1HxS+YWG18NzGGwS+30HHDiju3mUv7Jf2oVyW2ADW # oUa9WfOXpQlLSBCZgB/QACnFsZulP0V3HjXG0qKin3p6IvpIlR+r+0cjgPWe+L9r # t0uX4ut1eBrs6jeZeRhL/9azI2h15q/6/IvrC4DqaTuv/DDtBEyO3991bWORPdGd # Vk5Pv4BXIqF4ETIheu9BCrE/+6jMpF3BoYibV3FWTkhFwELJm3ZbCoBIa/15n8G9 # bW1qyVJzEw16UM0xggSqMIIEpgIBATCBlTB+MQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5n # IFBDQSAyMDExAhMzAAAAjoeRpFcaX8o+AAAAAACOMAkGBSsOAwIaBQCggb4wGQYJ # KoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQB # gjcCARUwIwYJKoZIhvcNAQkEMRYEFFEH/hJJCMan0bhDrcyak1SaHw0bMF4GCisG # AQQBgjcCAQwxUDBOoDCALgBHAGUAbgBlAHIAYQB0AGUALQBWAGEAYQBTAFIAZQBw # AG8AcgB0AC4AcABzADGhGoAYaHR0cDovL3d3dy5taWNyb3NvZnQuY29tMA0GCSqG # SIb3DQEBAQUABIIBAEDpqiyz7w3+x/R+h/BPecum8lsv66wc1dgV3YdgyI2YWzCH # +vAqL8RXvBeKhliinaxtWRGaKRfljBOi3Zodv6jtYhyFcXiEVqKsY9Vc8Is6AtD3 # T9I11IpXENA/VuQY3dGjUorSOyGoB/Pk3mpHwv1QpFe2Pa0A2ztIOfGKBYQW7sM4 # 8/2jeEiFVw4ePixbUFrnHtz06R472WOWVd0weNoxxzD3f8RUtohi8QMdMdgpzmi8 # 7kyBhnPpq3Vi0OMWVqzZbLEl4PsuZQgBQav9I2dshFYjwjPexobQuEr8q3lKZaZ1 # iyPdZg322zUP10u9AdCqIJjhRi2peOTe+KvfWEKhggIoMIICJAYJKoZIhvcNAQkG # MYICFTCCAhECAQEwgY4wdzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEhMB8GA1UEAxMYTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBAhMzAAAAtax9 # bYdrJhFHAAAAAAC1MAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN # AQcBMBwGCSqGSIb3DQEJBTEPFw0xNzAzMjkyMDIyMDNaMCMGCSqGSIb3DQEJBDEW # BBQmYDYYJcpFtgrw1KrVaSMsmhGljDANBgkqhkiG9w0BAQUFAASCAQBBy3Cj4KoE # zjUfa0pLcZkeA920nnYAF48iGqknNnLz/lbV3dSiL1HWdf1winKyEMwOpUiLg5Y6 # WpEmsILAEsjrzNuBuY2l+E+2YZkrfaUpxcFmow+GkyAxlMBgVplv6xXaoFbQ8d48 # 977fU4okUo7NJwrvoiu/40QXyE8PAWFjOGd4P0EWBZkpsQXrjlBBg1ZmVCk/6yzn # meoAv1IFgIErIhfMejzXXqV2swVnP/ROiHEe18JIppyRWfa1y3RxeLGU5aZMxKNj # 6r5RHM5FMaxS2A785Wn/3kLp+EL9N17FvKwoHYO8w43Z+kUzgZrILcqg8LHv4GaV # HSu5Ni+MCNOr # SIG # End signature block |