660 lines
17 KiB
PowerShell
660 lines
17 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
|
Import-Module $PSScriptRoot\build.psm1
|
|
$repoRoot = Get-RepositoryRoot
|
|
$script:logFile = join-path $repoRoot.FullName "appveyorlog.log"
|
|
|
|
|
|
<#
|
|
Called by Write-BuildMsg to write to the build log, if it exists.
|
|
#>
|
|
function Write-Log
|
|
{
|
|
param
|
|
(
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $Message
|
|
)
|
|
# write it to the log file, if present.
|
|
if (-not ([string]::IsNullOrEmpty($script:logFile)))
|
|
{
|
|
Add-Content -Path $script:logFile -Value $Message
|
|
}
|
|
}
|
|
|
|
# Sets a build variable
|
|
Function Set-BuildVariable
|
|
{
|
|
param(
|
|
[Parameter(Mandatory=$true)]
|
|
[string]
|
|
$Name,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[string]
|
|
$Value
|
|
)
|
|
|
|
if($env:AppVeyor)
|
|
{
|
|
Set-AppveyorBuildVariable @PSBoundParameters
|
|
}
|
|
else
|
|
{
|
|
Set-Item env:/$name -Value $Value
|
|
}
|
|
}
|
|
|
|
# Emulates running all of AppVeyor but locally
|
|
# should not be used on AppVeyor
|
|
function Invoke-AppVeyorFull
|
|
{
|
|
param(
|
|
[switch] $APPVEYOR_SCHEDULED_BUILD,
|
|
[switch] $CleanRepo
|
|
)
|
|
if($CleanRepo)
|
|
{
|
|
Clear-PSRepo
|
|
}
|
|
|
|
if($env:APPVEYOR)
|
|
{
|
|
throw "This function is to simulate appveyor, but not to be run from appveyor!"
|
|
}
|
|
|
|
if($APPVEYOR_SCHEDULED_BUILD)
|
|
{
|
|
$env:APPVEYOR_SCHEDULED_BUILD = 'True'
|
|
}
|
|
try {
|
|
Invoke-AppVeyorBuild
|
|
Install-OpenSSH
|
|
Install-TestDependencies
|
|
& "$env:ProgramFiles\PowerShell\6.0.0.12\powershell.exe" -Command {Import-Module $($repoRoot.FullName)\contrib\win32\openssh\AppVeyor.psm1;Run-OpenSSHTests -uploadResults}
|
|
Run-OpenSSHTests
|
|
Publish-Artifact
|
|
}
|
|
finally {
|
|
if($APPVEYOR_SCHEDULED_BUILD -and $env:APPVEYOR_SCHEDULED_BUILD)
|
|
{
|
|
Remove-Item env:APPVEYOR_SCHEDULED_BUILD
|
|
}
|
|
}
|
|
}
|
|
|
|
# Implements the AppVeyor 'build_script' step
|
|
function Invoke-AppVeyorBuild
|
|
{
|
|
Start-SSHBuild -Configuration Release -NativeHostArch x64
|
|
Start-SSHBuild -Configuration Debug -NativeHostArch x86
|
|
}
|
|
|
|
<#
|
|
.Synopsis
|
|
This function invokes msiexec.exe to install PSCore on the AppVeyor build machine
|
|
#>
|
|
function Invoke-MSIEXEC
|
|
{
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory=$true)]
|
|
[string] $InstallFile
|
|
)
|
|
|
|
Write-Log -Message "Installing $InstallFile..."
|
|
$arguments = @(
|
|
"/i"
|
|
"`"$InstallFile`""
|
|
"/qn"
|
|
"/norestart"
|
|
)
|
|
$process = Start-Process -FilePath msiexec.exe -ArgumentList $arguments -Wait -PassThru
|
|
if ($process.ExitCode -eq 0){
|
|
Write-Log -Message "$InstallFile has been successfully installed."
|
|
}
|
|
else {
|
|
Write-Log -Message "installer exit code $($process.ExitCode) for file $($InstallFile)"
|
|
}
|
|
|
|
return $process.ExitCode
|
|
}
|
|
|
|
<#
|
|
.Synopsis
|
|
This function installs PSCore MSI on the AppVeyor build machine
|
|
#>
|
|
function Install-PSCoreFromGithub
|
|
{
|
|
$downloadLocation = Download-PSCoreMSI
|
|
|
|
Write-Log -Message "Installing PSCore ..."
|
|
if(-not [string]::IsNullOrEmpty($downloadLocation))
|
|
{
|
|
$processExitCode = Invoke-MSIEXEC -InstallFile $downloadLocation
|
|
Write-Log -Message "Process exitcode: $processExitCode"
|
|
}
|
|
}
|
|
|
|
<#
|
|
.Synopsis
|
|
Retuns MSI location for PSCore for Win10, Windows 8.1 and 2012 R2
|
|
#>
|
|
function Get-PSCoreMSIDownloadURL
|
|
{
|
|
$osversion = [String][Environment]::OSVersion.Version
|
|
|
|
if($osversion.StartsWith("6"))
|
|
{
|
|
if ($($env:PROCESSOR_ARCHITECTURE).Contains('64'))
|
|
{
|
|
return 'https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-alpha.14/PowerShell_6.0.0.14-alpha.14-win81-x64.msi'
|
|
}
|
|
else
|
|
{
|
|
return 'https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-alpha.14/PowerShell_6.0.0.14-alpha.14-win7-x86.msi'
|
|
}
|
|
}
|
|
elseif ($osversion.Contains("10.0"))
|
|
{
|
|
if ($($env:PROCESSOR_ARCHITECTURE).Contains('64'))
|
|
{
|
|
return 'https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-alpha.14/PowerShell_6.0.0.14-alpha.14-win10-x64.msi'
|
|
}
|
|
else
|
|
{
|
|
return 'https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-alpha.14/PowerShell_6.0.0.14-alpha.14-win7-x86.msi'
|
|
}
|
|
}
|
|
}
|
|
|
|
<#
|
|
.Synopsis
|
|
This functions downloads MSI and returns the path where the file is downloaded.
|
|
#>
|
|
function Download-PSCoreMSI
|
|
{
|
|
$url = Get-PSCoreMSIDownloadURL
|
|
if([string]::IsNullOrEmpty($url))
|
|
{
|
|
Write-Log -Message "url is empty"
|
|
return ''
|
|
}
|
|
$parsed = $url.Substring($url.LastIndexOf("/") + 1)
|
|
if(-not (Test-path "$env:SystemDrive\PScore" -PathType Container))
|
|
{
|
|
$null = New-Item -ItemType Directory -Force -Path "$env:SystemDrive\PScore" | out-null
|
|
}
|
|
$downloadLocation = "$env:SystemDrive\PScore\$parsed"
|
|
if(-not (Test-path $downloadLocation -PathType Leaf))
|
|
{
|
|
Invoke-WebRequest -Uri $url -OutFile $downloadLocation -ErrorVariable v
|
|
}
|
|
|
|
if ($v)
|
|
{
|
|
throw "Failed to download PSCore MSI package from $url"
|
|
}
|
|
else
|
|
{
|
|
return $downloadLocation
|
|
}
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
This function installs the tools required by our tests
|
|
1) Pester for running the tests
|
|
2) sysinternals required by the tests on windows.
|
|
#>
|
|
function Install-TestDependencies
|
|
{
|
|
[CmdletBinding()]
|
|
param ()
|
|
|
|
$isModuleAvailable = Get-Module 'Pester' -ListAvailable
|
|
if (-not ($isModuleAvailable))
|
|
{
|
|
Write-Log -Message "Installing Pester..."
|
|
choco install Pester -y --force --limitoutput
|
|
}
|
|
|
|
if ( -not (Test-Path "$env:ProgramData\chocolatey\lib\sysinternals\tools" ) ) {
|
|
Write-Log -Message "sysinternals not present. Installing sysinternals."
|
|
choco install sysinternals -y --force --limitoutput
|
|
}
|
|
Install-PSCoreFromGithub
|
|
}
|
|
<#
|
|
.Synopsis
|
|
Deploy all required files to a location and install the binaries
|
|
#>
|
|
function Install-OpenSSH
|
|
{
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[string] $OpenSSHDir = "$env:SystemDrive\OpenSSH",
|
|
|
|
[ValidateSet('Debug', 'Release', '')]
|
|
[string]$Configuration = "",
|
|
|
|
[ValidateSet('x86', 'x64', '')]
|
|
[string]$NativeHostArch = ""
|
|
)
|
|
|
|
Build-Win32OpenSSHPackage @PSBoundParameters
|
|
|
|
Push-Location $OpenSSHDir
|
|
&( "$OpenSSHDir\install-sshd.ps1")
|
|
.\ssh-keygen.exe -A
|
|
Start-Service ssh-agent
|
|
&( "$OpenSSHDir\install-sshlsa.ps1")
|
|
|
|
Set-Service sshd -StartupType Automatic
|
|
Set-Service ssh-agent -StartupType Automatic
|
|
Start-Service sshd
|
|
|
|
Pop-Location
|
|
}
|
|
|
|
<#
|
|
.Synopsis
|
|
uninstalled sshd and sshla
|
|
#>
|
|
function UnInstall-OpenSSH
|
|
{
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[string] $OpenSSHDir = "$env:SystemDrive\OpenSSH"
|
|
)
|
|
|
|
Push-Location $OpenSSHDir
|
|
|
|
Stop-Service sshd
|
|
&( "$OpenSSHDir\uninstall-sshd.ps1")
|
|
&( "$OpenSSHDir\uninstall-sshlsa.ps1")
|
|
Pop-Location
|
|
}
|
|
|
|
<#
|
|
.Synopsis
|
|
Deploy all required files to build a package and create zip file.
|
|
#>
|
|
function Build-Win32OpenSSHPackage
|
|
{
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[string] $OpenSSHDir = "$env:SystemDrive\OpenSSH",
|
|
|
|
[ValidateSet('Debug', 'Release', '')]
|
|
[string]$Configuration = "",
|
|
|
|
[ValidateSet('x86', 'x64', '')]
|
|
[string]$NativeHostArch = ""
|
|
)
|
|
|
|
if (-not (Test-Path -Path $OpenSSHDir -PathType Container))
|
|
{
|
|
$null = New-Item -Path $OpenSSHDir -ItemType Directory -Force -ErrorAction Stop
|
|
}
|
|
|
|
[string] $platform = $env:PROCESSOR_ARCHITECTURE
|
|
if(-not [String]::IsNullOrEmpty($NativeHostArch))
|
|
{
|
|
$folderName = $NativeHostArch
|
|
if($NativeHostArch -ieq 'x86')
|
|
{
|
|
$folderName = "Win32"
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if($platform -ieq "AMD64")
|
|
{
|
|
$folderName = "x64"
|
|
}
|
|
else
|
|
{
|
|
$folderName = "Win32"
|
|
}
|
|
}
|
|
|
|
if([String]::IsNullOrEmpty($Configuration))
|
|
{
|
|
if( $folderName -ieq "Win32" )
|
|
{
|
|
$RealConfiguration = "Debug"
|
|
}
|
|
else
|
|
{
|
|
$RealConfiguration = "Release"
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$RealConfiguration = $Configuration
|
|
}
|
|
|
|
|
|
[System.IO.DirectoryInfo] $repositoryRoot = Get-RepositoryRoot
|
|
$sourceDir = Join-Path $repositoryRoot.FullName -ChildPath "bin\$folderName\$RealConfiguration"
|
|
Copy-Item -Path "$sourceDir\*" -Destination $OpenSSHDir -Include *.exe,*.dll -Exclude *unittest*.* -Force -ErrorAction Stop
|
|
$sourceDir = Join-Path $repositoryRoot.FullName -ChildPath "contrib\win32\openssh"
|
|
Copy-Item -Path "$sourceDir\*" -Destination $OpenSSHDir -Include *.ps1,sshd_config -Exclude AnalyzeCodeDiff.ps1 -Force -ErrorAction Stop
|
|
|
|
$packageName = "rktools.2003"
|
|
$rktoolsPath = "${env:ProgramFiles(x86)}\Windows Resource Kits\Tools\ntrights.exe"
|
|
if (-not (Test-Path -Path $rktoolsPath))
|
|
{
|
|
Write-Log -Message "$packageName not present. Installing $packageName."
|
|
choco install $packageName -y --force
|
|
}
|
|
|
|
Copy-Item -Path $rktoolsPath -Destination $OpenSSHDir -Force -ErrorAction Stop
|
|
|
|
$packageFolder = $env:SystemDrive
|
|
if ($env:APPVEYOR_BUILD_FOLDER)
|
|
{
|
|
$packageFolder = $env:APPVEYOR_BUILD_FOLDER
|
|
}
|
|
|
|
$package = "$packageFolder\Win32OpenSSH$RealConfiguration$folderName.zip"
|
|
$allPackage = "$packageFolder\Win32OpenSSH*.zip"
|
|
if (Test-Path $allPackage)
|
|
{
|
|
Remove-Item -Path $allPackage -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
Add-Type -assemblyname System.IO.Compression.FileSystem
|
|
[System.IO.Compression.ZipFile]::CreateFromDirectory($OpenSSHDir, $package)
|
|
}
|
|
|
|
<#
|
|
.Synopsis
|
|
After build and test run completes, upload all artifacts from the build machine.
|
|
#>
|
|
function Deploy-OpenSSHTests
|
|
{
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[string] $OpenSSHTestDir = "$env:SystemDrive\OpenSSH",
|
|
|
|
[ValidateSet('Debug', 'Release', '')]
|
|
[string]$Configuration = "",
|
|
|
|
[ValidateSet('x86', 'x64', '')]
|
|
[string]$NativeHostArch = ""
|
|
)
|
|
|
|
if (-not (Test-Path -Path $OpenSSHTestDir -PathType Container))
|
|
{
|
|
$null = New-Item -Path $OpenSSHTestDir -ItemType Directory -Force -ErrorAction Stop
|
|
}
|
|
|
|
[string] $platform = $env:PROCESSOR_ARCHITECTURE
|
|
if(-not [String]::IsNullOrEmpty($NativeHostArch))
|
|
{
|
|
$folderName = $NativeHostArch
|
|
if($NativeHostArch -eq 'x86')
|
|
{
|
|
$folderName = "Win32"
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if($platform -ieq "AMD64")
|
|
{
|
|
$folderName = "x64"
|
|
}
|
|
else
|
|
{
|
|
$folderName = "Win32"
|
|
}
|
|
}
|
|
|
|
if([String]::IsNullOrEmpty($Configuration))
|
|
{
|
|
if( $folderName -ieq "Win32" )
|
|
{
|
|
$RealConfiguration = "Debug"
|
|
}
|
|
else
|
|
{
|
|
$RealConfiguration = "Release"
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$RealConfiguration = $Configuration
|
|
}
|
|
|
|
|
|
[System.IO.DirectoryInfo] $repositoryRoot = Get-RepositoryRoot
|
|
|
|
$sourceDir = Join-Path $repositoryRoot.FullName -ChildPath "regress\pesterTests"
|
|
Copy-Item -Path "$sourceDir\*" -Destination $OpenSSHTestDir -Include *.ps1,*.psm1 -Force -ErrorAction Stop
|
|
|
|
$sourceDir = Join-Path $repositoryRoot.FullName -ChildPath "bin\$folderName\$RealConfiguration"
|
|
Copy-Item -Path "$sourceDir\*" -Destination $OpenSSHTestDir -Exclude ssh-agent.exe, sshd.exe -Force -ErrorAction Stop
|
|
}
|
|
|
|
|
|
<#
|
|
.Synopsis
|
|
Adds a build log to the list of published artifacts.
|
|
.Description
|
|
If a build log exists, it is renamed to reflect the associated CLR runtime then added to the list of
|
|
artifacts to publish. If it doesn't exist, a warning is written and the file is skipped.
|
|
The rename is needed since publishing overwrites the artifact if it already exists.
|
|
.Parameter artifacts
|
|
An array list to add the fully qualified build log path
|
|
.Parameter buildLog
|
|
The build log file produced by the build.
|
|
#>
|
|
function Add-BuildLog
|
|
{
|
|
param
|
|
(
|
|
[ValidateNotNull()]
|
|
[System.Collections.ArrayList] $artifacts,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $buildLog
|
|
)
|
|
|
|
if (Test-Path -Path $buildLog)
|
|
{
|
|
$null = $artifacts.Add($buildLog)
|
|
}
|
|
else
|
|
{
|
|
Write-Warning "Skip publishing build log. $buildLog does not exist"
|
|
}
|
|
}
|
|
|
|
<#
|
|
.Synopsis
|
|
Publishes package build artifacts.
|
|
.Parameter artifacts
|
|
An array list to add the fully qualified build log path
|
|
.Parameter packageFile
|
|
Path to the package
|
|
#>
|
|
function Add-Artifact
|
|
{
|
|
param
|
|
(
|
|
[ValidateNotNull()]
|
|
[System.Collections.ArrayList] $artifacts,
|
|
[string] $FileToAdd = "$env:SystemDrive\Win32OpenSSH*.zip"
|
|
)
|
|
|
|
$files = Get-ChildItem -Path $FileToAdd -ErrorAction Ignore
|
|
if ($files -ne $null)
|
|
{
|
|
$files | % {
|
|
$null = $artifacts.Add($_.FullName)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Write-Warning "Skip publishing package artifacts. $FileToAdd does not exist"
|
|
}
|
|
}
|
|
|
|
<#
|
|
.Synopsis
|
|
After build and test run completes, upload all artifacts from the build machine.
|
|
#>
|
|
function Publish-Artifact
|
|
{
|
|
Write-Output "Publishing project artifacts"
|
|
[System.Collections.ArrayList] $artifacts = [System.Collections.ArrayList]::new()
|
|
|
|
$packageFolder = $env:SystemDrive
|
|
if ($env:APPVEYOR_BUILD_FOLDER)
|
|
{
|
|
$packageFolder = $env:APPVEYOR_BUILD_FOLDER
|
|
}
|
|
|
|
Add-Artifact -artifacts $artifacts -FileToAdd "$packageFolder\Win32OpenSSH*.zip"
|
|
Add-Artifact -artifacts $artifacts -FileToAdd "$env:SystemDrive\OpenSSH\UnitTestResults.txt"
|
|
Add-Artifact -artifacts $artifacts -FileToAdd "$script:logFile"
|
|
|
|
# Get the build.log file for each build configuration
|
|
Add-BuildLog -artifacts $artifacts -buildLog (Get-BuildLogFile -root $repoRoot.FullName)
|
|
|
|
foreach ($artifact in $artifacts)
|
|
{
|
|
Write-Output "Publishing $artifact as Appveyor artifact"
|
|
# NOTE: attempt to publish subsequent artifacts even if the current one fails
|
|
Push-AppveyorArtifact $artifact -ErrorAction "Continue"
|
|
}
|
|
}
|
|
|
|
<#
|
|
.Synopsis
|
|
Run OpenSSH pester tests.
|
|
#>
|
|
function Run-OpenSSHPesterTest
|
|
{
|
|
param($testRoot, $outputXml)
|
|
|
|
# Discover all CI tests and run them.
|
|
Push-Location $testRoot
|
|
Write-Log -Message "Running OpenSSH Pester tests..."
|
|
$testFolders = Get-ChildItem *.tests.ps1 -Recurse | ForEach-Object{ Split-Path $_.FullName} | Sort-Object -Unique
|
|
|
|
Invoke-Pester $testFolders -OutputFormat NUnitXml -OutputFile $outputXml -Tag 'CI'
|
|
Pop-Location
|
|
}
|
|
|
|
<#
|
|
.Synopsis
|
|
Run unit tests.
|
|
#>
|
|
function Run-OpenSSHUnitTest
|
|
{
|
|
param($testRoot, $unitTestOutputFile)
|
|
|
|
# Discover all CI tests and run them.
|
|
Push-Location $testRoot
|
|
Write-Log -Message "Running OpenSSH unit tests..."
|
|
if (Test-Path $unitTestOutputFile)
|
|
{
|
|
Remove-Item -Path $unitTestOutputFile -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
$unitTestFiles = Get-ChildItem -Path "$testRoot\unittest*.exe"
|
|
$testFailed = $false
|
|
if ($unitTestFiles -ne $null)
|
|
{
|
|
$unitTestFiles | % {
|
|
Write-Log -Message "Running OpenSSH unit $($_.FullName)..."
|
|
& $_.FullName >> $unitTestOutputFile
|
|
$errorCode = $LASTEXITCODE
|
|
if ($errorCode -ne 0)
|
|
{
|
|
$testFailed = $true
|
|
Write-Log -Message "$($_.FullName) test failed for OpenSSH.`nExitCode: $error"
|
|
}
|
|
}
|
|
|
|
if($testFailed)
|
|
{
|
|
throw "SSH unit tests failed"
|
|
}
|
|
}
|
|
|
|
Pop-Location
|
|
}
|
|
|
|
<#
|
|
.Synopsis
|
|
Runs the tests for this repo
|
|
|
|
.Parameter testResultsFile
|
|
The name of the xml file to write pester results.
|
|
The default value is '.\testResults.xml'
|
|
|
|
.Parameter uploadResults
|
|
Uploads the tests results.
|
|
|
|
.Example
|
|
.\RunTests.ps1
|
|
Runs the tests and creates the default 'testResults.xml'
|
|
|
|
.Example
|
|
.\RunTests.ps1 -uploadResults
|
|
Runs the tests and creates teh default 'testResults.xml' and uploads it to appveyor.
|
|
|
|
#>
|
|
function Run-OpenSSHTests
|
|
{
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[string] $testResultsFile = "$env:SystemDrive\OpenSSH\TestResults.xml",
|
|
[string] $unitTestResultsFile = "$env:SystemDrive\OpenSSH\UnitTestResults.txt",
|
|
[string] $testInstallFolder = "$env:SystemDrive\OpenSSH"
|
|
)
|
|
|
|
Deploy-OpenSSHTests -OpenSSHTestDir $testInstallFolder
|
|
|
|
# Run all pester tests.
|
|
Run-OpenSSHPesterTest -testRoot $testInstallFolder -outputXml $testResultsFile
|
|
|
|
$xml = [xml](Get-Content -raw $testResultsFile)
|
|
if ([int]$xml.'test-results'.failures -gt 0)
|
|
{
|
|
throw "$($xml.'test-results'.failures) tests in regress\pesterTests failed"
|
|
}
|
|
|
|
# Writing out warning when the $Error.Count is non-zero. Tests Should clean $Error after success.
|
|
if ($Error.Count -gt 0)
|
|
{
|
|
$Error| Out-File "$env:SystemDrive\OpenSSH\TestError.txt" -Append
|
|
}
|
|
|
|
Run-OpenSSHUnitTest -testRoot $testInstallFolder -unitTestOutputFile $unitTestResultsFile
|
|
}
|
|
|
|
function Upload-OpenSSHTestResults
|
|
{
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[string] $testResultsFile = "$env:SystemDrive\OpenSSH\TestResults.xml"
|
|
)
|
|
|
|
if ($env:APPVEYOR_JOB_ID)
|
|
{
|
|
(New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile))
|
|
}
|
|
}
|