Fixed passing common test modules path to unelevated Powershell (#4313)

- Fixed the way common test modules are passed to elevated and unelevated powershell. Earlier, only elevated powershell got those through inheritance as a child process. Now we add them to the startup of the process.
- Fixed error reported by PSScriptAnalyzer about ? / Where-Object
- Converted all the parameters passed to powershell.exe to be a base64 encoded string to avoid complications with quotes.
- Removed code which was updated $env:PSModulePath as we do it in startup args for powershell process instead. 
- Added a way to disable -Quiet for Pester.
- Opencover.console.exe gets confused when the base64 encoded parameter is given with '&' invoke.
Writing to a ps1 file and invoking the script works around the issue.
This also makes it similar to how unelevated tests are invoked.
This commit is contained in:
Aditya Patwardhan 2017-08-04 09:16:17 -07:00 committed by Dongbo Wang
parent fc77c7981a
commit 5cd0e85d12
2 changed files with 72 additions and 40 deletions

View file

@ -1,7 +1,8 @@
param(
[Parameter(Mandatory = $true, Position = 0)] $coverallsToken,
[Parameter(Mandatory = $true, Position = 1)] $codecovToken,
[Parameter(Position = 2)] $azureLogDrive = "L:\"
[Parameter(Position = 2)] $azureLogDrive = "L:\",
[switch] $SuppressQuiet
)
# Read the XML and create a dictionary for FileUID -> file full path.
@ -168,7 +169,7 @@ try
Install-OpenCover -TargetDirectory $openCoverTargetDirectory -force
Write-LogPassThru -Message "OpenCover installed."
Write-LogPassThru -Message "TestDirectory : $testPath"
Write-LogPassThru -Message "TestPath : $testPath"
Write-LogPassThru -Message "openCoverPath : $openCoverTargetDirectory\OpenCover"
Write-LogPassThru -Message "psbinpath : $psBinPath"
Write-LogPassThru -Message "elevatedLog : $elevatedLogs"
@ -176,7 +177,7 @@ try
Write-LogPassThru -Message "TestToolsPath : $testToolsPath"
$openCoverParams = @{outputlog = $outputLog;
TestDirectory = $testPath;
TestPath = $testPath;
OpenCoverPath = "$openCoverTargetDirectory\OpenCover";
PowerShellExeDirectory = "$psBinPath";
PesterLogElevated = $elevatedLogs;
@ -184,6 +185,11 @@ try
TestToolsModulesPath = "$testToolsPath\Modules";
}
if($SuppressQuiet)
{
$openCoverParams.Add('SuppressQuiet', $true)
}
$openCoverParams | Out-String | Write-LogPassThru
Write-LogPassThru -Message "Starting test run."

View file

@ -413,21 +413,22 @@ function Install-OpenCover
.Description
Invoke-OpenCover runs tests under OpenCover by executing tests on PowerShell.exe located at $PowerShellExeDirectory.
.EXAMPLE
Invoke-OpenCover -TestDirectory $pwd/test/powershell -PowerShellExeDirectory $pwd/src/powershell-win-core/bin/CodeCoverage/netcoreapp1.0/win10-x64
Invoke-OpenCover -TestPath $pwd/test/powershell -PowerShellExeDirectory $pwd/src/powershell-win-core/bin/CodeCoverage/netcoreapp1.0/win10-x64
#>
function Invoke-OpenCover
{
[CmdletBinding(SupportsShouldProcess=$true)]
param (
[parameter()]$OutputLog = "$home/Documents/OpenCover.xml",
[parameter()]$TestDirectory = "${script:psRepoPath}/test/powershell",
[parameter()]$TestPath = "${script:psRepoPath}/test/powershell",
[parameter()]$OpenCoverPath = "$home/OpenCover",
[parameter()]$PowerShellExeDirectory = "${script:psRepoPath}/src/powershell-win-core/bin/CodeCoverage/netcoreapp2.0/win10-x64/publish",
[parameter()]$PesterLogElevated = "$pwd/TestResultsElevated.xml",
[parameter()]$PesterLogUnelevated = "$pwd/TestResultsUnelevated.xml",
[parameter()]$PesterLogFormat = "NUnitXml",
[parameter()]$TestToolsModulesPath = "${script:psRepoPath}/test/tools/Modules",
[switch]$CIOnly
[switch]$CIOnly,
[switch]$SuppressQuiet
)
# check for elevation
@ -462,8 +463,10 @@ function Invoke-OpenCover
# create the arguments for OpenCover
$startupArgs = 'Set-ExecutionPolicy Bypass -Force -Scope Process;'
$targetArgs = "-c","${startupArgs}", "Invoke-Pester","${TestDirectory}"
$updatedEnvPath = "${PowerShellExeDirectory}\Modules;$TestToolsModulesPath"
$startupArgs = "Set-ExecutionPolicy Bypass -Force -Scope Process; `$env:PSModulePath = '${updatedEnvPath}';"
$targetArgs = "${startupArgs}", "Invoke-Pester","${TestPath}","-OutputFormat $PesterLogFormat"
if ( $CIOnly )
{
@ -476,46 +479,40 @@ function Invoke-OpenCover
$targetArgsUnelevated = $targetArgs + @("-excludeTag @('RequireAdminOnWindows')")
}
$targetArgsElevated += @("-OutputFile $PesterLogElevated", "-OutputFormat $PesterLogFormat", "-Quiet")
$targetArgsUnelevated += @("-OutputFile $PesterLogUnelevated", "-OutputFormat $PesterLogFormat", "-Quiet")
$targetArgsElevated += @("-OutputFile $PesterLogElevated")
$targetArgsUnelevated += @("-OutputFile $PesterLogUnelevated")
$targetArgStringElevated = $targetArgsElevated -join " "
$targetArgStringUnelevated = $targetArgsUnelevated -join " "
# the order seems to be important. Always keep -targetargs as the last parameter.
$openCoverArgsElevated = "-target:$target","-register:user","-output:${outputLog}","-nodefaultfilters","-oldstyle","-hideskipped:all","-mergeoutput", "-filter:`"+[*]* -[Microsoft.PowerShell.PSReadLine]*`"", "-targetargs:`"$targetArgStringElevated`""
$openCoverArgsUnelevated = "-target:$target","-register:user","-output:${outputLog}","-nodefaultfilters","-oldstyle","-hideskipped:all", "-mergeoutput", "-filter:`"+[*]* -[Microsoft.PowerShell.PSReadLine]*`"", "-targetargs:`"$targetArgStringUnelevated`""
$openCoverArgsUnelevatedString = $openCoverArgsUnelevated -join " "
if(-not $SuppressQuiet)
{
$targetArgsElevated += @("-Quiet")
$targetArgsUnelevated += @("-Quiet")
}
if ( $PSCmdlet.ShouldProcess("$OpenCoverBin $openCoverArgsUnelevated") )
$cmdlineElevated = CreateOpenCoverCmdline -target $target -outputLog $OutputLog -targetArgs $targetArgsElevated
$cmdlineUnelevated = CreateOpenCoverCmdline -target $target -outputLog $OutputLog -targetArgs $targetArgsUnelevated
if ( $PSCmdlet.ShouldProcess("$OpenCoverBin $cmdlineUnelevated") )
{
try
{
# check to be sure that the module path is present
# this isn't done earlier because there's no need to change env:PSModulePath unless we're going to really run tests
$saveModPath = $env:PSModulePath
$env:PSModulePath = "${PowerShellExeDirectory}\Modules;$TestToolsModulesPath"
$modulePathParts = $env:PSModulePath -split ';'
foreach($part in $modulePathParts)
{
if ( ! (test-path $part) )
{
throw "${part} does not exist"
}
}
# invoke OpenCover elevated
& $OpenCoverBin $openCoverArgsElevated
# Write the command line to a file and then invoke file.
# '&' invoke caused issues with cmdline parameters for opencover.console.exe
$elevatedFile = "$env:temp\elevated.ps1"
"$OpenCoverBin $cmdlineElevated" | Out-File -FilePath $elevatedFile -force
powershell.exe -file $elevatedFile
# invoke OpenCover unelevated and poll for completion
"$openCoverBin $openCoverArgsUnelevatedString" | Out-File -FilePath "$env:temp\unelevated.ps1" -Force
runas.exe /trustlevel:0x20000 "powershell.exe -file $env:temp\unelevated.ps1"
# wait for process to start
Start-Sleep -Seconds 5
$unelevatedFile = "$env:temp\unelevated.ps1"
"$openCoverBin $cmdlineUnelevated" | Out-File -FilePath $unelevatedFile -Force
runas.exe /trustlevel:0x20000 "powershell.exe -file $unelevatedFile"
# poll for process exit every 60 seconds
# timeout of 6 hours
# Runs currently take about 2.5 - 3 hours, we picked 6 hours to be substantially larger.
$timeOut = ([datetime]::Now).AddHours(6)
$openCoverExited = $false
while([datetime]::Now -lt $timeOut)
{
Start-Sleep -Seconds 60
@ -524,15 +521,44 @@ function Invoke-OpenCover
if(-not $openCoverProcess)
{
#run must have completed.
$openCoverExited = $true
break
}
}
if(-not $openCoverExited)
{
throw "Opencover has not exited in 6 hours"
}
}
finally
{
# set it back
$env:PSModulePath = $saveModPath
Remove-Item "$env:temp\unelevated.ps1" -force -ErrorAction SilentlyContinue
Remove-Item $elevatedFile -force -ErrorAction SilentlyContinue
Remove-Item $unelevatedFile -force -ErrorAction SilentlyContinue
}
}
}
function CreateOpenCoverCmdline($target, $outputLog, $targetArgs)
{
$targetArgString = $targetArgs -join " "
$bytes = [System.Text.Encoding]::Unicode.GetBytes($targetArgString)
$base64targetArgs = [convert]::ToBase64String($bytes)
# the order seems to be important. Always keep -targetargs as the last parameter.
$cmdline = "-target:$target",
"-register:user",
"-output:${outputLog}",
"-nodefaultfilters",
"-oldstyle",
"-hideskipped:all",
"-mergeoutput",
"-filter:`"+[*]* -[Microsoft.PowerShell.PSReadLine]*`"",
"-targetargs:`"-EncodedCommand $base64targetArgs`""
$cmdlineAsString = $cmdline -join " "
return $cmdlineAsString
}