mirror of
https://github.com/github/awesome-copilot.git
synced 2026-10-13 15:24:25 +00:00
579 lines
19 KiB
PowerShell
579 lines
19 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$PackagePath,
|
|
[string]$ReportPath = "",
|
|
[string]$ExistingReportPath = "",
|
|
[int]$ReportWaitSeconds = 60
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
Set-StrictMode -Version Latest
|
|
|
|
# WACK rejects a -reportoutputpath that does not end in .xml. It writes the XML
|
|
# report to the requested path and an HTML rendering of it to
|
|
# %LOCALAPPDATA%\Microsoft\AppCertKit\<xml base name>.htm. The XML report is
|
|
# authoritative. The HTML report is only a fallback when no XML report exists.
|
|
$kitDirectory = Join-Path $env:LOCALAPPDATA "Microsoft\AppCertKit"
|
|
$htmlExtensions = @(".htm", ".html")
|
|
# The English strings results.xsl shows for each OVERALL_RESULT value.
|
|
$resultNames = @{
|
|
PASS = "PASSED"
|
|
FAIL = "FAILED"
|
|
WARNING = "PASSED WITH WARNINGS"
|
|
}
|
|
$knownResults = @($resultNames.Values) + "INCOMPLETE"
|
|
|
|
function Resolve-AppCert {
|
|
$candidates = @(
|
|
@(
|
|
(Join-Path ${env:ProgramFiles(x86)} "Windows Kits\10\App Certification Kit\appcert.exe"),
|
|
(Join-Path $env:ProgramFiles "Windows Kits\10\App Certification Kit\appcert.exe")
|
|
) | Where-Object { $_ -and (Test-Path -LiteralPath $_) }
|
|
)
|
|
if (-not $candidates.Count) {
|
|
throw "appcert.exe was not found. Install the Windows App Certification Kit."
|
|
}
|
|
return $candidates[0]
|
|
}
|
|
|
|
function Test-IsAdministrator {
|
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
|
return $principal.IsInRole(
|
|
[Security.Principal.WindowsBuiltInRole]::Administrator
|
|
)
|
|
}
|
|
|
|
function Read-XmlDocument($Source) {
|
|
$settings = [Xml.XmlReaderSettings]::new()
|
|
$settings.DtdProcessing = [Xml.DtdProcessing]::Ignore
|
|
$settings.XmlResolver = $null
|
|
$reader = [Xml.XmlReader]::Create($Source, $settings)
|
|
try {
|
|
$document = [Xml.XmlDocument]::new()
|
|
$document.Load($reader)
|
|
return $document
|
|
} finally {
|
|
$reader.Dispose()
|
|
}
|
|
}
|
|
|
|
function Get-PackageIdentity([string]$Path) {
|
|
if ([IO.Path]::GetExtension($Path) -notin @(".msix", ".appx")) {
|
|
return $null
|
|
}
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
$archive = [IO.Compression.ZipFile]::OpenRead($Path)
|
|
try {
|
|
$entry = $archive.GetEntry("AppxManifest.xml")
|
|
if ($null -eq $entry) {
|
|
throw "The package does not contain AppxManifest.xml."
|
|
}
|
|
$stream = $entry.Open()
|
|
try {
|
|
$manifest = Read-XmlDocument $stream
|
|
} finally {
|
|
$stream.Dispose()
|
|
}
|
|
} finally {
|
|
$archive.Dispose()
|
|
}
|
|
$identity = $manifest.DocumentElement.SelectSingleNode(
|
|
"*[local-name()='Identity']"
|
|
)
|
|
if ($null -eq $identity) {
|
|
throw "AppxManifest.xml does not contain an Identity element."
|
|
}
|
|
return [pscustomobject]@{
|
|
Name = $identity.GetAttribute("Name")
|
|
Version = $identity.GetAttribute("Version")
|
|
}
|
|
}
|
|
|
|
function Read-WackXmlReport([string]$Path) {
|
|
try {
|
|
$stream = [IO.File]::OpenRead($Path)
|
|
try {
|
|
$document = Read-XmlDocument $stream
|
|
} finally {
|
|
$stream.Dispose()
|
|
}
|
|
} catch {
|
|
return $null
|
|
}
|
|
$root = $document.DocumentElement
|
|
if (
|
|
$null -eq $root -or
|
|
$root.LocalName -ne "REPORT" -or
|
|
-not $root.HasAttribute("OVERALL_RESULT")
|
|
) {
|
|
return $null
|
|
}
|
|
|
|
$rawResult = $root.GetAttribute("OVERALL_RESULT")
|
|
# results.xsl treats a report without VALIDATION_TYPE as an unfinished
|
|
# interim report.
|
|
$overallResult = if (-not $root.HasAttribute("VALIDATION_TYPE")) {
|
|
"INCOMPLETE"
|
|
} elseif ($resultNames.ContainsKey($rawResult)) {
|
|
$resultNames[$rawResult]
|
|
} else {
|
|
$rawResult
|
|
}
|
|
|
|
$nonPassingTests = @(
|
|
foreach ($test in $root.SelectNodes("REQUIREMENTS/REQUIREMENT/TEST")) {
|
|
$resultNode = $test.SelectSingleNode("RESULT")
|
|
$testResult = if ($null -ne $resultNode) {
|
|
$resultNode.InnerText.Trim()
|
|
} else {
|
|
""
|
|
}
|
|
if ($testResult -eq "PASS") {
|
|
continue
|
|
}
|
|
[ordered]@{
|
|
requirement = $test.ParentNode.GetAttribute("TITLE")
|
|
test = $test.GetAttribute("NAME")
|
|
result = $testResult
|
|
optional = $test.GetAttribute("OPTIONAL") -eq "TRUE"
|
|
messages = @(
|
|
foreach ($message in $test.SelectNodes("MESSAGES/MESSAGE")) {
|
|
$message.GetAttribute("TEXT")
|
|
}
|
|
)
|
|
}
|
|
}
|
|
)
|
|
|
|
return [pscustomobject]@{
|
|
Path = $Path
|
|
OverallResult = $overallResult
|
|
AppName = $root.GetAttribute("APP_NAME")
|
|
AppVersion = $root.GetAttribute("APP_VERSION")
|
|
KitVersion = $root.GetAttribute("VERSION")
|
|
KitIsLatest = $root.GetAttribute("LATEST_VERSION") -ne "FALSE"
|
|
PartialRun = $root.GetAttribute("PARTIAL_RUN") -eq "TRUE"
|
|
ReportTime = $root.GetAttribute("ReportGenerationTime")
|
|
NonPassingTests = $nonPassingTests
|
|
}
|
|
}
|
|
|
|
function Read-WackHtmlReport([string]$Path) {
|
|
try {
|
|
$content = [IO.File]::ReadAllText($Path)
|
|
} catch {
|
|
return $null
|
|
}
|
|
$overall = [regex]::Match(
|
|
$content,
|
|
'(?is)<div[^>]*\bclass="overall"[^>]*>(.*?)</div>'
|
|
)
|
|
if (-not $overall.Success) {
|
|
return $null
|
|
}
|
|
$label = [regex]::Match(
|
|
$overall.Groups[1].Value,
|
|
'(?is)<span[^>]*>(.*?)</span>'
|
|
)
|
|
if (-not $label.Success) {
|
|
return $null
|
|
}
|
|
$text = [regex]::Replace($label.Groups[1].Value, '<[^>]+>', ' ')
|
|
$text = ([Net.WebUtility]::HtmlDecode($text) -replace '\s+', ' ').Trim()
|
|
$overallResult = if ($text -like "Incomplete*") {
|
|
"INCOMPLETE"
|
|
} else {
|
|
$text.ToUpperInvariant()
|
|
}
|
|
$version = [regex]::Match(
|
|
$content,
|
|
'(?is)<dt>\s*App version:\s*</dt>\s*<dd[^>]*>\s*([^<]*?)\s*</dd>'
|
|
)
|
|
return [pscustomobject]@{
|
|
Path = $Path
|
|
Content = $content
|
|
OverallResult = $overallResult
|
|
# Only English reports are recognized. Localized labels are not.
|
|
Recognized = $overallResult -in $knownResults
|
|
AppVersion = if ($version.Success) { $version.Groups[1].Value } else { "" }
|
|
}
|
|
}
|
|
|
|
# WACK copies ReportGenerationTime into the HTML report, which ties an HTML
|
|
# report to the XML report it was rendered from.
|
|
function Test-ReportPair($XmlReport, $HtmlReport) {
|
|
return [bool](
|
|
$XmlReport.ReportTime -and
|
|
$HtmlReport.Content.Contains($XmlReport.ReportTime)
|
|
)
|
|
}
|
|
|
|
function Test-NewFile([string]$Path, [datetime]$Since) {
|
|
return (
|
|
(Test-Path -LiteralPath $Path -PathType Leaf) -and
|
|
(Get-Item -LiteralPath $Path).LastWriteTime -ge $Since
|
|
)
|
|
}
|
|
|
|
function Get-NewKitFiles([string[]]$Extensions, [datetime]$Since) {
|
|
if (-not (Test-Path -LiteralPath $kitDirectory -PathType Container)) {
|
|
return
|
|
}
|
|
Get-ChildItem -LiteralPath $kitDirectory -File -ErrorAction SilentlyContinue |
|
|
Where-Object {
|
|
$_.Extension -in $Extensions -and $_.LastWriteTime -ge $Since
|
|
} |
|
|
Sort-Object LastWriteTime -Descending |
|
|
ForEach-Object { $_.FullName }
|
|
}
|
|
|
|
function Find-WackXmlReport([string]$RequestedPath, [datetime]$Since) {
|
|
$candidates = [Collections.Generic.List[string]]::new()
|
|
if (Test-NewFile $RequestedPath $Since) {
|
|
$candidates.Add($RequestedPath)
|
|
}
|
|
# AppCertKit holds many unrelated XML files, so each one is checked for a
|
|
# REPORT root element.
|
|
foreach ($path in @(Get-NewKitFiles @(".xml") $Since)) {
|
|
$candidates.Add($path)
|
|
}
|
|
foreach ($candidate in $candidates) {
|
|
$report = Read-WackXmlReport $candidate
|
|
if ($null -ne $report) {
|
|
return $report
|
|
}
|
|
}
|
|
return $null
|
|
}
|
|
|
|
function Find-WackHtmlReport(
|
|
[string]$XmlPath,
|
|
[datetime]$Since,
|
|
$XmlReport,
|
|
[switch]$SearchKitDirectory
|
|
) {
|
|
$baseName = [IO.Path]::GetFileNameWithoutExtension($XmlPath)
|
|
$candidates = [Collections.Generic.List[string]]::new()
|
|
foreach ($extension in $htmlExtensions) {
|
|
$paths = @(
|
|
[IO.Path]::ChangeExtension($XmlPath, $extension),
|
|
(Join-Path $kitDirectory ($baseName + $extension))
|
|
)
|
|
foreach ($path in $paths) {
|
|
if (Test-NewFile $path $Since) {
|
|
$candidates.Add($path)
|
|
}
|
|
}
|
|
}
|
|
if ($SearchKitDirectory) {
|
|
foreach ($path in @(Get-NewKitFiles $htmlExtensions $Since)) {
|
|
$candidates.Add($path)
|
|
}
|
|
}
|
|
foreach ($candidate in $candidates) {
|
|
$report = Read-WackHtmlReport $candidate
|
|
if (
|
|
$null -ne $report -and
|
|
($null -eq $XmlReport -or (Test-ReportPair $XmlReport $report))
|
|
) {
|
|
return $report
|
|
}
|
|
}
|
|
return $null
|
|
}
|
|
|
|
function Save-Report([string]$Source, [string]$Destination) {
|
|
# Skip the copy when the destination already holds the same report. This
|
|
# also covers a source and destination that are the same file.
|
|
if (
|
|
-not (Test-Path -LiteralPath $Destination -PathType Leaf) -or
|
|
(Get-FileHash -LiteralPath $Source).Hash -ne
|
|
(Get-FileHash -LiteralPath $Destination).Hash
|
|
) {
|
|
[IO.File]::Copy($Source, $Destination, $true)
|
|
}
|
|
return $Destination
|
|
}
|
|
|
|
$resolvedPackage = (Resolve-Path -LiteralPath $PackagePath).ProviderPath
|
|
if (-not $ReportPath) {
|
|
$ReportPath = [IO.Path]::Combine(
|
|
[IO.Path]::GetDirectoryName($resolvedPackage),
|
|
[IO.Path]::GetFileNameWithoutExtension($resolvedPackage) +
|
|
"-WACK-report.xml"
|
|
)
|
|
}
|
|
# Relative paths are resolved against the current PowerShell location.
|
|
$ReportPath = [IO.Path]::GetFullPath(
|
|
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(
|
|
$ReportPath
|
|
)
|
|
)
|
|
$reportExtension = [IO.Path]::GetExtension($ReportPath).ToLowerInvariant()
|
|
if ($reportExtension -ne ".xml" -and $reportExtension -notin $htmlExtensions) {
|
|
throw "ReportPath must end in .xml, .htm, or .html."
|
|
}
|
|
# WACK always receives the .xml path. The HTML copy is saved beside it.
|
|
$xmlPath = [IO.Path]::ChangeExtension($ReportPath, ".xml")
|
|
$htmlPath = if ($reportExtension -eq ".xml") {
|
|
[IO.Path]::ChangeExtension($ReportPath, ".htm")
|
|
} else {
|
|
$ReportPath
|
|
}
|
|
[void][IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($ReportPath))
|
|
|
|
$appCert = $null
|
|
$exitCode = $null
|
|
$xmlReport = $null
|
|
$htmlReport = $null
|
|
$warnings = [Collections.Generic.List[string]]::new()
|
|
$errors = [Collections.Generic.List[string]]::new()
|
|
|
|
if ($ExistingReportPath) {
|
|
$existingReport = (Resolve-Path -LiteralPath $ExistingReportPath).ProviderPath
|
|
$existingExtension = [IO.Path]::GetExtension($existingReport).ToLowerInvariant()
|
|
if ($existingExtension -eq ".xml") {
|
|
$xmlReport = Read-WackXmlReport $existingReport
|
|
if ($null -eq $xmlReport) {
|
|
throw "'$existingReport' is not a WACK XML report."
|
|
}
|
|
$htmlReport = Find-WackHtmlReport $existingReport ([datetime]::MinValue) $xmlReport
|
|
} elseif ($existingExtension -in $htmlExtensions) {
|
|
$htmlReport = Read-WackHtmlReport $existingReport
|
|
if ($null -eq $htmlReport) {
|
|
throw "'$existingReport' is not a WACK HTML report."
|
|
}
|
|
$siblingXml = [IO.Path]::ChangeExtension($existingReport, ".xml")
|
|
if (Test-Path -LiteralPath $siblingXml -PathType Leaf) {
|
|
$candidate = Read-WackXmlReport $siblingXml
|
|
if ($null -ne $candidate -and (Test-ReportPair $candidate $htmlReport)) {
|
|
$xmlReport = $candidate
|
|
} else {
|
|
$warnings.Add(
|
|
"Ignored '$siblingXml' because it is not the XML report for this HTML report."
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
throw "ExistingReportPath must be a WACK .xml, .htm, or .html report."
|
|
}
|
|
} else {
|
|
if (-not (Test-IsAdministrator)) {
|
|
throw (
|
|
"The WACK command-line runner requires an elevated PowerShell process. " +
|
|
"Ask the user to open an elevated terminal, then run this script there. " +
|
|
"Do not attempt to approve the operating-system authentication prompt."
|
|
)
|
|
}
|
|
|
|
$appCert = Resolve-AppCert
|
|
& $appCert reset | Out-Host
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "appcert.exe reset failed with exit code $LASTEXITCODE."
|
|
}
|
|
|
|
# Reports older than this run are ignored. The margin covers file-system
|
|
# timestamp granularity.
|
|
$startedAt = (Get-Date).AddSeconds(-5)
|
|
& $appCert test `
|
|
-appxpackagepath $resolvedPackage `
|
|
-reportoutputpath $xmlPath |
|
|
Out-Host
|
|
$exitCode = $LASTEXITCODE
|
|
|
|
# After a failed run, look for a report once instead of waiting for one.
|
|
$waitSeconds = if ($exitCode -eq 0) { $ReportWaitSeconds } else { 0 }
|
|
$deadline = (Get-Date).AddSeconds($waitSeconds)
|
|
$htmlGraceStarted = $false
|
|
while ($true) {
|
|
# An interim report can still be replaced by the final one.
|
|
if ($null -eq $xmlReport -or $xmlReport.OverallResult -eq "INCOMPLETE") {
|
|
$latestXmlReport = Find-WackXmlReport $xmlPath $startedAt
|
|
if ($null -ne $latestXmlReport) {
|
|
$xmlReport = $latestXmlReport
|
|
}
|
|
}
|
|
$htmlSearchPath = if ($null -ne $xmlReport) { $xmlReport.Path } else { $xmlPath }
|
|
$htmlReport = Find-WackHtmlReport $htmlSearchPath $startedAt $xmlReport -SearchKitDirectory
|
|
$xmlFinished = $null -ne $xmlReport -and $xmlReport.OverallResult -ne "INCOMPLETE"
|
|
if ($xmlFinished -and $null -ne $htmlReport) {
|
|
break
|
|
}
|
|
if ($xmlFinished -and -not $htmlGraceStarted) {
|
|
# The XML report decides the result, so the HTML report only gets
|
|
# a short grace period.
|
|
$htmlGraceStarted = $true
|
|
$graceDeadline = (Get-Date).AddSeconds(10)
|
|
if ($graceDeadline -lt $deadline) {
|
|
$deadline = $graceDeadline
|
|
}
|
|
}
|
|
if ((Get-Date) -ge $deadline) {
|
|
break
|
|
}
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
|
|
if ($null -eq $xmlReport -and $null -eq $htmlReport) {
|
|
if ($exitCode -ne 0) {
|
|
throw "appcert.exe test failed with exit code $exitCode and produced no report."
|
|
}
|
|
throw (
|
|
"WACK returned success but no report was found at '$xmlPath' or under " +
|
|
"'$kitDirectory'."
|
|
)
|
|
}
|
|
if ($exitCode -ne 0) {
|
|
$errors.Add("appcert.exe test exited with code $exitCode.")
|
|
}
|
|
}
|
|
|
|
if ($null -ne $xmlReport) {
|
|
$overallResult = $xmlReport.OverallResult
|
|
$resultSource = "xml"
|
|
$reportAppName = $xmlReport.AppName
|
|
$reportAppVersion = $xmlReport.AppVersion
|
|
$nonPassingTests = @($xmlReport.NonPassingTests)
|
|
if ($xmlReport.PartialRun) {
|
|
$errors.Add("Not all WACK tests ran (PARTIAL_RUN is TRUE).")
|
|
}
|
|
if (-not $xmlReport.KitIsLatest) {
|
|
$warnings.Add(
|
|
"WACK $($xmlReport.KitVersion) is not the latest version, and WACK warns " +
|
|
"that the Store submission may fail. Update the kit and rerun it."
|
|
)
|
|
}
|
|
if ($null -eq $htmlReport) {
|
|
$warnings.Add(
|
|
"No HTML report from the same WACK run was found. The XML report decides the result."
|
|
)
|
|
} elseif ($htmlReport.Recognized -and $htmlReport.OverallResult -ne $overallResult) {
|
|
$errors.Add(
|
|
"The XML report says $overallResult, but the HTML report says " +
|
|
"$($htmlReport.OverallResult)."
|
|
)
|
|
}
|
|
} else {
|
|
$overallResult = $htmlReport.OverallResult
|
|
$resultSource = "html"
|
|
$reportAppName = $null
|
|
$reportAppVersion = $htmlReport.AppVersion
|
|
$nonPassingTests = @()
|
|
$warnings.Add(
|
|
"No XML report was found. The result comes from the HTML report, and " +
|
|
"individual test results are not listed."
|
|
)
|
|
}
|
|
|
|
if ($overallResult -ne "PASSED") {
|
|
$errors.Add("WACK overall result is '$overallResult'.")
|
|
}
|
|
|
|
$optionalFailures = @($nonPassingTests | Where-Object { $_.optional }).Count
|
|
if ($optionalFailures) {
|
|
$warnings.Add(
|
|
"$optionalFailures optional test(s) did not pass. Optional tests do not " +
|
|
"block Store onboarding, but investigate them."
|
|
)
|
|
}
|
|
$requiredFailures = $nonPassingTests.Count - $optionalFailures
|
|
if ($requiredFailures) {
|
|
$warnings.Add(
|
|
"$requiredFailures required test(s) did not pass. Review nonPassingTests."
|
|
)
|
|
}
|
|
|
|
$packageIdentity = $null
|
|
try {
|
|
$packageIdentity = Get-PackageIdentity $resolvedPackage
|
|
if ($null -eq $packageIdentity) {
|
|
$warnings.Add(
|
|
"Only .msix and .appx packages are matched to the report. Check the " +
|
|
"report's app name and version by hand."
|
|
)
|
|
}
|
|
} catch {
|
|
$warnings.Add(
|
|
"The package identity could not be read, so the report was not matched " +
|
|
"to the package: $($_.Exception.Message)"
|
|
)
|
|
}
|
|
if ($null -ne $packageIdentity) {
|
|
if ($reportAppName -and $reportAppName -ne $packageIdentity.Name) {
|
|
$errors.Add(
|
|
"The report is for '$reportAppName', but the package identity is " +
|
|
"'$($packageIdentity.Name)'."
|
|
)
|
|
}
|
|
if (-not $reportAppVersion) {
|
|
$warnings.Add(
|
|
"The report's app version could not be read, so it was not matched " +
|
|
"to the package version."
|
|
)
|
|
} elseif ($reportAppVersion -ne $packageIdentity.Version) {
|
|
$errors.Add(
|
|
"The report is for version $reportAppVersion, but the package is " +
|
|
"version $($packageIdentity.Version)."
|
|
)
|
|
}
|
|
}
|
|
|
|
$savedXmlReport = if ($null -ne $xmlReport) {
|
|
Save-Report $xmlReport.Path $xmlPath
|
|
} else {
|
|
$null
|
|
}
|
|
$savedHtmlReport = if ($null -ne $htmlReport) {
|
|
Save-Report $htmlReport.Path $htmlPath
|
|
} else {
|
|
$null
|
|
}
|
|
$unsavedPaths = @()
|
|
if ($null -eq $savedXmlReport) {
|
|
$unsavedPaths += $xmlPath
|
|
}
|
|
if ($null -eq $savedHtmlReport) {
|
|
$unsavedPaths += $htmlPath
|
|
}
|
|
foreach ($unsavedPath in $unsavedPaths) {
|
|
if (Test-Path -LiteralPath $unsavedPath -PathType Leaf) {
|
|
$warnings.Add(
|
|
"'$unsavedPath' already existed and was not replaced. It is not part " +
|
|
"of this result."
|
|
)
|
|
}
|
|
}
|
|
|
|
$result = [ordered]@{
|
|
package = $resolvedPackage
|
|
packageSha256 = (
|
|
Get-FileHash -LiteralPath $resolvedPackage -Algorithm SHA256
|
|
).Hash.ToLowerInvariant()
|
|
packageIdentityName = if ($null -ne $packageIdentity) { $packageIdentity.Name } else { $null }
|
|
packageVersion = if ($null -ne $packageIdentity) { $packageIdentity.Version } else { $null }
|
|
appCert = $appCert
|
|
exitCode = $exitCode
|
|
overallResult = $overallResult
|
|
resultSource = $resultSource
|
|
reportAppName = $reportAppName
|
|
reportAppVersion = $reportAppVersion
|
|
kitVersion = if ($null -ne $xmlReport) { $xmlReport.KitVersion } else { $null }
|
|
reportTime = if ($null -ne $xmlReport) { $xmlReport.ReportTime } else { $null }
|
|
xmlReport = $savedXmlReport
|
|
htmlReport = $savedHtmlReport
|
|
sourceXmlReport = if ($null -ne $xmlReport) { $xmlReport.Path } else { $null }
|
|
sourceHtmlReport = if ($null -ne $htmlReport) { $htmlReport.Path } else { $null }
|
|
nonPassingTests = $nonPassingTests
|
|
warnings = @($warnings)
|
|
errors = @($errors)
|
|
passed = $errors.Count -eq 0
|
|
}
|
|
$result | ConvertTo-Json -Depth 6
|
|
|
|
if ($errors.Count) {
|
|
$reviewPath = if ($savedXmlReport) { $savedXmlReport } else { $savedHtmlReport }
|
|
throw "WACK verification failed. $($errors -join ' ') Review '$reviewPath'."
|
|
}
|