Home > database >  How to show output of xcopy in powershell script while it's running
How to show output of xcopy in powershell script while it's running

Time:10-26

I have this powershell scrip where I want to copy files from our production server to our test server, and only have it copy files which are newer or changed. The script itself has many more functions but this copying is one of those. I'm using the following XCopy command for that:

  ... some other scriptingcode
  try {
      xCopy $source $destination /f /e /r /k /y /d
  }
  catch {
      Write-Host "issue: $PSItem" -ForegroundColor Yellow -BackgroundColor Red
  }
  ... some more other scriptingcode

When I run the XCopy command directly from the powershell prompt it shows it's progress, the files being copied, while it's running, but when I run it from the script itself, it doesn't output anything. And just adding -Verbose at the end just gives me a script error.

I've tried the following:

  $output = $(xCopy $source $destination /f /e /r /k /y /d) -join "`r\`n"
  Write-Host $output

But that only gives me the output after it's already been copied, and isn't a problem when no, 1 or 2 files are copied, but not really helpfull when you have a 100 or 1.000 files to copy. FYI, the -join "'r'n" is because the output is an array when more than one line is output, so I had to join the lines into one string which could be shown by Write-Host.

I have tried to google for a solution, but I guess my google skills aren't good enough to get me any results to this problem. So, is there a way to have XCopy output while it's doing it, just like directly from the powershell commandline?

CodePudding user response:

For xcopy, you can both output to the host and capture the output with a foreach:

try {
  $output = xcopy $src $dst /f /e /r /k /y /d | 
    Foreach {
      # write line to console
      $_ | Write-Host -ForegroundColor Yellow -BackgroundColor Red
    
      # add line to $output
      $_
    }
}

Note that xcopy is not a powershell command, so it's not able to do some things like use the default -verbose or -ErrorAction flags. It also does not throw terminating errors, so Try/Catch will not work unless you set your $ErrorActionPreference correctly.

CodePudding user response:

You shouldn't see a problem with your script, because by default PowerShell passes stdout and stderr output from external programs such as xcopy.exe directly through to the host (console), without interfering with the output or its timing - irrespective of whether you execute the program interactively or from a script.

Similarly, if you send stdout (success) output through the pipeline , PowerShell streams it, i.e. sends each output line as it is being received (conceivably, a given program may itself buffer lines before emitting them when not printing to the console, but that doesn't seem to be the case for xcopy.exe).

The simplest way to print an external program's output to the console in streaming fashion while also capturing its output is to use the Tee-Object cmdlet:

# Prints the output to the console as it is being received
# while also capturing it in variable $output
xcopy $source $destination /f /e /r /k /y /d | Tee-Object -Variable output

Afterwards, $output contains the stdout output as an array of lines (or a single string, if there happened to be just one output line).

To also capture stderr output, append redirection 2>&1 to the external-program call; stderr lines are captured as System.Management.Automation.ErrorRecord instances, as if they were PowerShell errors; to convert all array elements to strings afterwards, use $output = [string[]] $output.

Character-encoding note: When PowerShell is involved in handling external program output (in the pipeline, capturing in a variable, saving to a file), the output is invariably decoded into .NET strings first, based on the encoding stored in [Console]::OutputEncoding, which may situationally have to be modified to match the specific encoding used by a given external program - see this answer for more information.

If you want to perform transformations or apply additional formatting to the lines being received from the external program, consider Cpt.Whale's helpful solution based on ForEach-Object.

CodePudding user response:

This is a side-effect of using the sub-expression operator $() here (note you would get the same effect using the group-expression operator () as well). The reason this happens is because $() and () function like parentheses in mathematics, and order of operations (technically order of precedence in computing) dictates that inner expressions be worked out before outer expressions. So anything inside the parentheses must complete first before it can be processed by the outer code.

In your case here, the "outer code" is what displays information to the console, hence, why the inner command has to complete before it gets displayed.


If you don't need to evaluate $output later on, the simplest and most performant approach is to pipe your command output directly to Out-Host instead (you probably also want to redirect error output as well):

# 2>&1 will redirect the error stream (external command STDERR gets written here)
# to the success stream (external command STDOUT gets written here)
# The success stream is what gets passed down the pipeline
xCopy $source $destination /f /e /r /k /y /d 2>&1 | Out-Host

However, if you need to assign $output to a variable at the same time for further processing, see @Cpt.Whale's solution involving the clever use of ForEach-Object. There are some edge caveats to their approach but does allow you to display the output and either stage the output in another variable for later processing or process the lines of output as they are read in. @mklement0's answer also shows how to use Tee-Object to simultaneously output to a variable and the success stream.

  • Related