Redirecting Binary Output in PowerShell

Thu, Jan 18, 2007 4-minute read

An interesting problem recently came up in the PowerShell newsgroup, where somebody was trying to use a tool (jpegtopnm.exe) that let you pipe its binary output into a file. This works on Unix (and from cmd.exe,) but unfortunately generated a corrupt image when done in PowerShell.

When PowerShell launches a native application, one of the benefits we provide is allowing you to use PowerShell commands to work with the output. For example:

[C:\temp]
PS:64 > (IpConfig) -match “Gateway”
   Default Gateway . . . . . . . . . : xxx.yy.zz.a
   Default Gateway . . . . . . . . . :

We support this by splitting the output of the program on its newline characters, and pass each line independently down the pipeline. We support programs that use the Unix newline (\n) as well as the Windows newline (\r\n)

If the program outputs binary data, however, there is a risk of corrupting that data when we redirect it into a file. We will first split the program’s output on “\n” or “\r\n”, and generate an array of strings that do not have the newline character. When we send them into the file, we use the Windows newline character as a separator – which effectively converts all 0x0D characters in the program’s output to the sequence of 0x0D,0x0A. Hence, the corrupted file.

You can see that in action here (Format-Hex comes from the PowerShell Community Extensions project:)

From a PPM file generated in DOS:

[C:\temp]
PS:74 > cmd
Microsoft Windows [Version 5.2.3790]
(C) Copyright 1985-2003 Microsoft Corp.

C:\temp>jpegtopnm.exe IMAGE_00002.jpg > pic.ppm
jpegto~1.exe: WRITING PPM FILE

C:\temp>exit

[C:\temp]
PS:11 > gi pic.ppm | format-hex -NumBytesPerLine 10

Address:  0  1  2  3  4  5  6  7  8  9 ASCII
-——- —————————– ———-
00000000 50 36 0A 34 38 30 20 36 34 30 P6.480 640
0000000A 0A 32 35 35 0A 20 23 2C 1F 22 .255. #,."
00000014 2B 22 20 2D 24 20 2E 28 21 31 +" -$ .(!1
0000001E 29 20 31 2A 1C 33 28 1A 31 2A ) 1*.3(.1*

From a PPM file generated in PowerShell:

[C:\temp]
PS:65 > .\jpegtopnm.exe IMAGE_00002.jpg | Out-File -Encoding OEM pic2.ppm
jpegto~1.exe: WRITING PPM FILE

[C:\temp]
PS:66 > gi pic2.ppm | Format-Hex -NumBytesPerLine 10

Address:  0  1  2  3  4  5  6  7  8  9 ASCII
-——- —————————– ———-
00000000 50 36 0D 0A 34 38 30 20 36 34 P6..480 64
0000000A 30 0D 0A 32 35 35 0D 0A 20 23 0..255.. #
00000014 2C 1F 22 2B 22 20 2D 24 20 2E ,."+" -$ .
0000001E 28 21 31 29 20 31 2A 1C 33 28 (!1) 1*.3(
00000028 1A 31 2A 1C 33 2A 1C 33 29 1D .1*.3*.3).
00000032 31 28 1F 32 28 1F 30 25 21 30 1(.2(.0%!0

So the solution here is to not have PowerShell interpret the output stream of the program that generates this binary data:

[C:\temp]
PS:67 > Get-ProcessOutputAsBinary C:\temp\jpegtopnm.exe IMAGE_00002.jpg | Out-File -Encoding OEM pic3.ppm
jpegto~1.exe: WRITING PPM FILE

[C:\temp]
PS:68 > gi pic3.ppm | Format-Hex -NumBytesPerLine 10

Address:  0  1  2  3  4  5  6  7  8  9 ASCII
-——- —————————– ———-
00000000 50 36 0A 34 38 30 20 36 34 30 P6.480 640
0000000A 0A 32 35 35 0A 20 23 2C 1F 22 .255. #,."
00000014 2B 22 20 2D 24 20 2E 28 21 31 +" -$ .(!1
0000001E 29 20 31 2A 1C 33 28 1A 31 2A ) 1*.3(.1*
00000028 1C 33 2A 1C 33 29 1D 31 28 1F .3*.3).1(.
00000032 32 28 1F 30 25 21 30 26 22 30 2(.0%!0&“0

The script that does that is here:

## Get-ProcessOutputAsBinary.ps1
## Get the standard output of a process as binary, rather than
## the interpreted array of strings that PowerShell traditionally
## produces.
param([string] $processname, [string] $arguments)

$processStartInfo = New-Object System.Diagnostics.ProcessStartInfo
$processStartInfo.FileName = $processname
$processStartInfo.WorkingDirectory = (Get-Location).Path
if($arguments) { $processStartInfo.Arguments = $arguments }
$processStartInfo.UseShellExecute = $false
$processStartInfo.RedirectStandardOutput = $true

$process = [System.Diagnostics.Process]::Start($processStartInfo)
$process.WaitForExit()
$process.StandardOutput.ReadToEnd()