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

View file

@ -413,21 +413,22 @@ function Install-OpenCover
.Description .Description
Invoke-OpenCover runs tests under OpenCover by executing tests on PowerShell.exe located at $PowerShellExeDirectory. Invoke-OpenCover runs tests under OpenCover by executing tests on PowerShell.exe located at $PowerShellExeDirectory.
.EXAMPLE .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 function Invoke-OpenCover
{ {
[CmdletBinding(SupportsShouldProcess=$true)] [CmdletBinding(SupportsShouldProcess=$true)]
param ( param (
[parameter()]$OutputLog = "$home/Documents/OpenCover.xml", [parameter()]$OutputLog = "$home/Documents/OpenCover.xml",
[parameter()]$TestDirectory = "${script:psRepoPath}/test/powershell", [parameter()]$TestPath = "${script:psRepoPath}/test/powershell",
[parameter()]$OpenCoverPath = "$home/OpenCover", [parameter()]$OpenCoverPath = "$home/OpenCover",
[parameter()]$PowerShellExeDirectory = "${script:psRepoPath}/src/powershell-win-core/bin/CodeCoverage/netcoreapp2.0/win10-x64/publish", [parameter()]$PowerShellExeDirectory = "${script:psRepoPath}/src/powershell-win-core/bin/CodeCoverage/netcoreapp2.0/win10-x64/publish",
[parameter()]$PesterLogElevated = "$pwd/TestResultsElevated.xml", [parameter()]$PesterLogElevated = "$pwd/TestResultsElevated.xml",
[parameter()]$PesterLogUnelevated = "$pwd/TestResultsUnelevated.xml", [parameter()]$PesterLogUnelevated = "$pwd/TestResultsUnelevated.xml",
[parameter()]$PesterLogFormat = "NUnitXml", [parameter()]$PesterLogFormat = "NUnitXml",
[parameter()]$TestToolsModulesPath = "${script:psRepoPath}/test/tools/Modules", [parameter()]$TestToolsModulesPath = "${script:psRepoPath}/test/tools/Modules",
[switch]$CIOnly [switch]$CIOnly,
[switch]$SuppressQuiet
) )
# check for elevation # check for elevation
@ -462,8 +463,10 @@ function Invoke-OpenCover
# create the arguments for OpenCover # create the arguments for OpenCover
$startupArgs = 'Set-ExecutionPolicy Bypass -Force -Scope Process;' $updatedEnvPath = "${PowerShellExeDirectory}\Modules;$TestToolsModulesPath"
$targetArgs = "-c","${startupArgs}", "Invoke-Pester","${TestDirectory}"
$startupArgs = "Set-ExecutionPolicy Bypass -Force -Scope Process; `$env:PSModulePath = '${updatedEnvPath}';"
$targetArgs = "${startupArgs}", "Invoke-Pester","${TestPath}","-OutputFormat $PesterLogFormat"
if ( $CIOnly ) if ( $CIOnly )
{ {
@ -476,46 +479,40 @@ function Invoke-OpenCover
$targetArgsUnelevated = $targetArgs + @("-excludeTag @('RequireAdminOnWindows')") $targetArgsUnelevated = $targetArgs + @("-excludeTag @('RequireAdminOnWindows')")
} }
$targetArgsElevated += @("-OutputFile $PesterLogElevated", "-OutputFormat $PesterLogFormat", "-Quiet") $targetArgsElevated += @("-OutputFile $PesterLogElevated")
$targetArgsUnelevated += @("-OutputFile $PesterLogUnelevated", "-OutputFormat $PesterLogFormat", "-Quiet") $targetArgsUnelevated += @("-OutputFile $PesterLogUnelevated")
$targetArgStringElevated = $targetArgsElevated -join " " if(-not $SuppressQuiet)
$targetArgStringUnelevated = $targetArgsUnelevated -join " " {
# the order seems to be important. Always keep -targetargs as the last parameter. $targetArgsElevated += @("-Quiet")
$openCoverArgsElevated = "-target:$target","-register:user","-output:${outputLog}","-nodefaultfilters","-oldstyle","-hideskipped:all","-mergeoutput", "-filter:`"+[*]* -[Microsoft.PowerShell.PSReadLine]*`"", "-targetargs:`"$targetArgStringElevated`"" $targetArgsUnelevated += @("-Quiet")
$openCoverArgsUnelevated = "-target:$target","-register:user","-output:${outputLog}","-nodefaultfilters","-oldstyle","-hideskipped:all", "-mergeoutput", "-filter:`"+[*]* -[Microsoft.PowerShell.PSReadLine]*`"", "-targetargs:`"$targetArgStringUnelevated`"" }
$openCoverArgsUnelevatedString = $openCoverArgsUnelevated -join " "
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 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 # 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 # invoke OpenCover unelevated and poll for completion
"$openCoverBin $openCoverArgsUnelevatedString" | Out-File -FilePath "$env:temp\unelevated.ps1" -Force $unelevatedFile = "$env:temp\unelevated.ps1"
runas.exe /trustlevel:0x20000 "powershell.exe -file $env:temp\unelevated.ps1" "$openCoverBin $cmdlineUnelevated" | Out-File -FilePath $unelevatedFile -Force
# wait for process to start runas.exe /trustlevel:0x20000 "powershell.exe -file $unelevatedFile"
Start-Sleep -Seconds 5
# poll for process exit every 60 seconds # poll for process exit every 60 seconds
# timeout of 6 hours # 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) $timeOut = ([datetime]::Now).AddHours(6)
$openCoverExited = $false
while([datetime]::Now -lt $timeOut) while([datetime]::Now -lt $timeOut)
{ {
Start-Sleep -Seconds 60 Start-Sleep -Seconds 60
@ -524,15 +521,44 @@ function Invoke-OpenCover
if(-not $openCoverProcess) if(-not $openCoverProcess)
{ {
#run must have completed. #run must have completed.
$openCoverExited = $true
break break
} }
} }
if(-not $openCoverExited)
{
throw "Opencover has not exited in 6 hours"
}
} }
finally finally
{ {
# set it back Remove-Item $elevatedFile -force -ErrorAction SilentlyContinue
$env:PSModulePath = $saveModPath Remove-Item $unelevatedFile -force -ErrorAction SilentlyContinue
Remove-Item "$env:temp\unelevated.ps1" -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
}