Use Exec-Process

The `&` operator in Powershell does not easily allow arguments to be
conditionally emitted into a command. This is because instead of
inserting string contents when using variable replacement it will insert
a quoted string instead. Using `Exec-Process` instead as it does proper
substitution
上级 b4593303
......@@ -27,36 +27,70 @@ function Exec-Block([scriptblock]$cmd) {
}
}
function Exec-CommandCore([string]$command, [string]$commandArgs, [switch]$useConsole = $true) {
# This will exec a process using the console and return it's exit code. This will not
# throw when the process fails.
function Exec-Process([string]$command, [string]$commandArgs) {
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = $command
$startInfo.Arguments = $commandArgs
$startInfo.UseShellExecute = $false
$startInfo.WorkingDirectory = Get-Location
if (-not $useConsole) {
$startInfo.RedirectStandardOutput = $true
$startInfo.CreateNoWindow = $true
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
$process.Start() | Out-Null
$finished = $false
try {
while (-not $process.WaitForExit(100)) {
# Non-blocking loop done to allow ctr-c interrupts
}
$finished = $true
$process.ExitCode
}
finally {
# If we didn't finish then an error occured or the user hit ctrl-c. Either
# way kill the process
if (-not $finished) {
$process.Kill()
}
}
}
function Exec-CommandCore([string]$command, [string]$commandArgs, [switch]$useConsole = $true) {
if ($useConsole) {
$exitCode = Exec-Process $command $commandArgs
if ($exitCode -ne 0) {
throw "Command failed to execute with exit code $($process.ExitCode): $command $commandArgs"
}
return
}
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = $command
$startInfo.Arguments = $commandArgs
$startInfo.UseShellExecute = $false
$startInfo.WorkingDirectory = Get-Location
$startInfo.RedirectStandardOutput = $true
$startInfo.CreateNoWindow = $true
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
$process.Start() | Out-Null
$finished = $false
try {
if (-not $useConsole) {
# The OutputDataReceived event doesn't fire as events are sent by the
# process in powershell. Possibly due to subtlties of how Powershell
# manages the thread pool that I'm not aware of. Using blocking
# reading here as an alternative which is fine since this blocks
# on completion already.
$out = $process.StandardOutput
while (-not $out.EndOfStream) {
$line = $out.ReadLine()
Write-Output $line
}
# The OutputDataReceived event doesn't fire as events are sent by the
# process in powershell. Possibly due to subtlties of how Powershell
# manages the thread pool that I'm not aware of. Using blocking
# reading here as an alternative which is fine since this blocks
# on completion already.
$out = $process.StandardOutput
while (-not $out.EndOfStream) {
$line = $out.ReadLine()
Write-Output $line
}
while (-not $process.WaitForExit(100)) {
......@@ -65,7 +99,7 @@ function Exec-CommandCore([string]$command, [string]$commandArgs, [switch]$useCo
$finished = $true
if ($process.ExitCode -ne 0) {
throw "Command failed to execute: $command $commandArgs"
throw "Command failed to execute with exit code $($process.ExitCode): $command $commandArgs"
}
}
finally {
......
......@@ -3,116 +3,55 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Source,
[string]$source,
[Parameter(Mandatory = $true)]
[string]$Target,
[string]$target,
[Parameter(Mandatory = $true)]
[int]$ParallelCount,
[int]$parallelCount,
[Parameter(Mandatory = $false)]
[string]$File,
[string]$file,
[Parameter(Mandatory = $false)]
[string[]]$Exclude)
# This script translates the output from robocopy into UTF8. Node has limited
# built-in support for encodings.
#
# Robocopy uses the system default code page. The system default code page varies
# depending on the locale configuration. On an en-US box, the system default code
# page is Windows-1252.
#
# Note, on a typical en-US box, testing with the '' character is a good way to
# determine whether data is passed correctly between processes. This is because
# the '' character has a different code point across each of the common encodings
# on a typical en-US box, i.e.
# 1) the default console-output code page (IBM437)
# 2) the system default code page (i.e. CP_ACP) (Windows-1252)
# 3) UTF8
[string[]]$exclude)
$ErrorActionPreference = 'Stop'
Set-StrictMode -version 2.0
# Redefine the wrapper over STDOUT to use UTF8. Node expects UTF8 by default.
$stdout = [System.Console]::OpenStandardOutput()
$utf8 = New-Object System.Text.UTF8Encoding($false) # do not emit BOM
$writer = New-Object System.IO.StreamWriter($stdout, $utf8)
[System.Console]::SetOut($writer)
# All subsequent output must be written using [System.Console]::WriteLine(). In
# PowerShell 4, Write-Host and Out-Default do not consider the updated stream writer.
try {
. (Join-Path $PSScriptRoot "build-utils.ps1")
if (!$File) {
$File = "*";
}
# Print the ##command. The /MT parameter is only supported on 2008 R2 and higher.
if ($ParallelCount -gt 1) {
[System.Console]::WriteLine("##[command]robocopy.exe /E /COPY:DA /NP /R:3 /MT:$ParallelCount `"$Source`" `"$Target`" `"$File`"")
}
else {
[System.Console]::WriteLine("##[command]robocopy.exe /E /COPY:DA /NP /R:3 `"$Source`" `"$Target`" `"$File`"")
}
# The $OutputEncoding variable instructs PowerShell how to interpret the output
# from the external command.
$OutputEncoding = [System.Text.Encoding]::Default
if (!$File) {
$File = "*";
}
$ExcludeArg = ""
if (($null -ne $Exclude) -and ($Exclude.Length -gt 0)) {
$ExcludeArg = "/XD "
foreach ($e in $Exclude) {
$ExcludeArg += "$e "
$commandLine = "/E /COPY:DA /NP /R:3 "
if ($parallelCount -gt 1) {
$commandLine += "/MT:$parallelCount "
}
}
# Usage :: ROBOCOPY source destination [file [file]...] [options]
# source :: Source Directory (drive:\path or \\server\share\path).
# destination :: Destination Dir (drive:\path or \\server\share\path).
# file :: File(s) to copy (names/wildcards: default is "*.*").
# /E :: copy subdirectories, including Empty ones.
# /COPY:copyflag[s] :: what to COPY for files (default is /COPY:DAT).
# (copyflags : D=Data, A=Attributes, T=Timestamps).
# (S=Security=NTFS ACLs, O=Owner info, U=aUditing info).
# /NP :: No Progress - don't display percentage copied.
# /MT[:n] :: Do multi-threaded copies with n threads (default 8).
# n must be at least 1 and not greater than 128.
# This option is incompatible with the /IPG and /EFSRAW options.
# Redirect output using /LOG option for better performance.
# /R:n :: number of Retries on failed copies: default 1 million.
#
# Note, the output from robocopy needs to be iterated over. Otherwise PowerShell.exe
# will launch the external command in such a way that it inherits the streams.
#
# Note, the /MT parameter is only supported on 2008 R2 and higher.
if ($ParallelCount -gt 1) {
& robocopy.exe /E /COPY:DA /NP /R:3 /MT:$ParallelCount $Source $Target $File $ExcludeArg 2>&1 |
ForEach-Object {
if ($_ -is [System.Management.Automation.ErrorRecord]) {
[System.Console]::WriteLine($_.Exception.Message)
}
else {
[System.Console]::WriteLine($_)
$commandLine += "$source $target $file "
if (($null -ne $exclude) -and ($exclude.Length -gt 0)) {
$commandLine += "/XD "
foreach ($e in $exclude) {
$commandLine += "$e "
}
}
}
else {
& robocopy.exe /E /COPY:DA /NP /R:3 $Source $Target $File $ExcludeArg 2>&1 |
ForEach-Object {
if ($_ -is [System.Management.Automation.ErrorRecord]) {
[System.Console]::WriteLine($_.Exception.Message)
}
else {
[System.Console]::WriteLine($_)
}
Write-Host "robocopy $commandLine"
$exitCode = Exec-Process "robocopy" $commandLine
if ($exitCode -gt 8) {
Write-Host "robocopy failed $exitCode"
}
}
[System.Console]::WriteLine("##[debug]robocopy exit code '$LASTEXITCODE'")
[System.Console]::Out.Flush()
if ($LASTEXITCODE -ge 8) {
exit $LASTEXITCODE
exit 0
}
catch {
Write-Host $_
Write-Host $_.Exception
Write-Host $_.ScriptStackTrace
exit 1
}
exit 0
......@@ -113,3 +113,16 @@ Invoke-Expression "& $command $args"
Exec-Command $command $args
```
## Comarisons with null
Whenever comparing with `$null` always make sure to put `$null` on the left hand side of the
operator. For non-collection types this doesn't really affect behavior. For collection types though
having a collection on the left hand side changes the meaning of `-ne` and `-eq`. Instead of checking
for `$null` it will instead compare collection contents.
``` powershell
# DO NOT
if ($e -ne $null) { ... }
# DO
if ($null -ne $e) { ... }
```
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册