@@ -225,8 +225,8 @@ function cache_path($app, $version, $url) { "$cachedir\$app#$version#$($url -rep
225225
226226# apps
227227function sanitary_path ($path ) { return [regex ]::replace($path , " [/\\?:*<>|]" , " " ) }
228- function installed ($app , $global ) {
229- if (-not $PSBoundParameters .ContainsKey ( ' global' ) ) {
228+ function installed ($app , [ Nullable [ bool ]] $global ) {
229+ if ($null -eq $ global ) {
230230 return (installed $app $false ) -or (installed $app $true )
231231 }
232232 # Dependencies of the format "bucket/dependency" install in a directory of form
@@ -507,34 +507,63 @@ function Invoke-ExternalCommand {
507507 }
508508 $Process = New-Object System.Diagnostics.Process
509509 $Process.StartInfo.FileName = $FilePath
510- $Process.StartInfo.Arguments = ($ArgumentList | Select-Object - Unique) -join ' '
511510 $Process.StartInfo.UseShellExecute = $false
512511 if ($LogPath ) {
513- if ($FilePath -match ' (^|\W) msiexec($|\W) ' ) {
514- $Process .StartInfo.Arguments += " /lwe `" $LogPath `" "
512+ if ($FilePath -match ' ^ msiexec(.exe)?$ ' ) {
513+ $ArgumentList += " /lwe `" $LogPath `" "
515514 } else {
515+ $redirectToLogFile = $true
516516 $Process.StartInfo.RedirectStandardOutput = $true
517517 $Process.StartInfo.RedirectStandardError = $true
518518 }
519519 }
520520 if ($RunAs ) {
521521 $Process.StartInfo.UseShellExecute = $true
522522 $Process.StartInfo.Verb = ' RunAs'
523+ } else {
524+ $Process.StartInfo.CreateNoWindow = $true
525+ }
526+ if ($FilePath -match ' ^((cmd|cscript|wscript|msiexec)(\.exe)?|.*\.(bat|cmd|js|vbs|wsf))$' ) {
527+ $Process.StartInfo.Arguments = $ArgumentList -join ' '
528+ } elseif ($Process.StartInfo.ArgumentList.Add ) {
529+ # ArgumentList is supported in PowerShell 6.1 and later (built on .NET Core 2.1+)
530+ # ref-1: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.argumentlist?view=net-6.0
531+ # ref-2: https://docs.microsoft.com/en-us/powershell/scripting/whats-new/differences-from-windows-powershell?view=powershell-7.2#net-framework-vs-net-core
532+ $ArgumentList | ForEach-Object { $Process.StartInfo.ArgumentList.Add ($_ ) }
533+ } else {
534+ # escape arguments manually in lower versions, refer to https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85)
535+ $escapedArgs = $ArgumentList | ForEach-Object {
536+ # escape N consecutive backslash(es), which are followed by a double quote, to 2N consecutive ones
537+ $s = $_ -replace ' (\\+)"' , ' $1$1"'
538+ # escape N consecutive backslash(es), which are at the end of the string, to 2N consecutive ones
539+ $s = $s -replace ' (\\+)$' , ' $1$1'
540+ # escape double quotes
541+ $s = $s -replace ' "' , ' \"'
542+ # quote the argument
543+ " `" $s `" "
544+ }
545+ $Process.StartInfo.Arguments = $escapedArgs -join ' '
523546 }
524547 try {
525- $Process.Start () | Out-Null
548+ [ void ] $Process.Start ()
526549 } catch {
527550 if ($Activity ) {
528551 Write-Host " error." - ForegroundColor DarkRed
529552 }
530553 error $_.Exception.Message
531554 return $false
532555 }
533- if ($LogPath -and ($FilePath -notmatch ' (^|\W)msiexec($|\W)' )) {
534- Out-UTF8File - FilePath $LogPath - Append - InputObject $Process.StandardOutput.ReadToEnd ()
535- Out-UTF8File - FilePath $LogPath - Append - InputObject $Process.StandardError.ReadToEnd ()
556+ if ($redirectToLogFile ) {
557+ # we do this to remove a deadlock potential
558+ # ref: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.standardoutput?view=netframework-4.5#remarks
559+ $stdoutTask = $Process.StandardOutput.ReadToEndAsync ()
560+ $stderrTask = $Process.StandardError.ReadToEndAsync ()
536561 }
537562 $Process.WaitForExit ()
563+ if ($redirectToLogFile ) {
564+ Out-UTF8File - FilePath $LogPath - Append - InputObject $stdoutTask.Result
565+ Out-UTF8File - FilePath $LogPath - Append - InputObject $stderrTask.Result
566+ }
538567 if ($Process.ExitCode -ne 0 ) {
539568 if ($ContinueExitCodes -and ($ContinueExitCodes.ContainsKey ($Process.ExitCode ))) {
540569 if ($Activity ) {
@@ -604,12 +633,12 @@ function movedir($from, $to) {
604633 $proc.StartInfo.RedirectStandardError = $true
605634 $proc.StartInfo.UseShellExecute = $false
606635 $proc.StartInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle ]::Hidden
607- $proc.Start ()
608- $out = $proc.StandardOutput.ReadToEnd ()
636+ [ void ] $proc.Start ()
637+ $stdoutTask = $proc.StandardOutput.ReadToEndAsync ()
609638 $proc.WaitForExit ()
610639
611640 if ($proc.ExitCode -ge 8 ) {
612- debug $out
641+ debug $stdoutTask .Result
613642 throw " Could not find '$ ( fname $from ) '! (error $ ( $proc.ExitCode ) )"
614643 }
615644
0 commit comments