Welcome PowerShell User! This recipe is just one of the hundreds of useful resources contained in the PowerShell Cookbook.
If you own the book already, login here to get free, online, searchable access to the entire book's content.
If not, the Windows PowerShell Cookbook is available at Amazon, or any of your other favourite book retailers. If you want to see what the PowerShell Cookbook has to offer, enjoy this free 90 page e-book sample: "The Windows PowerShell Interactive Shell".
You want to run programs that transfer complex binary data between themselves.
Use the Invoke-BinaryProcess
script to invoke the program, as shown in Example 2-9. If it’s the source of binary data, use the -RedirectOutput
parameter. If it consumes binary data, use the -RedirectInput
parameter.
##############################################################################
##
## Invoke-BinaryProcess
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
<#
.SYNOPSIS
Invokes a process that emits or consumes binary data.
.EXAMPLE
PS > Invoke-BinaryProcess binaryProcess.exe -RedirectOutput -ArgumentList "-emit" |
Invoke-BinaryProcess binaryProcess.exe -RedirectInput -ArgumentList "-consume"
#>
param
(
## The name of the process to invoke
[string]
$ProcessName
,
## Specifies that input to the process should be treated as
## binary
[
Alias
(
"Input"
)]
[switch]
$RedirectInput
,
## Specifies that the output of the process should be treated
## as binary
[
Alias
(
"Output"
)]
[switch]
$RedirectOutput
,
## Specifies the arguments for the process
[string]
$ArgumentList
)
Set-StrictMode
-Version
3
## Prepare to invoke the process
$processStartInfo
=
New-Object
System
.
Diagnostics
.
ProcessStartInfo
$processStartInfo
.
FileName
=
(
Get-Command
$processname
).
Definition
$processStartInfo
.
WorkingDirectory
=
(
Get-Location
).
Path
if
(
$argumentList
)
{
$processStartInfo
.
Arguments
=
$argumentList
}
$processStartInfo
.
UseShellExecute
=
$false
## Always redirect the input and output of the process.
## Sometimes we will capture it as binary, other times we will
## just treat it as strings.
$processStartInfo
.
RedirectStandardOutput
=
$true
$processStartInfo
.
RedirectStandardInput
=
$true
$process
=
[System.Diagnostics.Process]
::
Start
(
$processStartInfo
)
## If we've been asked to redirect the input, treat it as bytes.
## Otherwise, write any input to the process as strings.
if
(
$redirectInput
)
{
$inputBytes
=
@(
$input
)
$process
.
StandardInput
.
BaseStream
.
Write
(
$inputBytes
,
0
,
$inputBytes
.
Count
)
$process
.
StandardInput
.
Close
()
}
else
{
$input
|
%
{
$process
.
StandardInput
.
WriteLine
(
$_
)
}
$process
.
StandardInput
.
Close
()
}
## If we've been asked to redirect the output, treat it as bytes.
## Otherwise, read any input from the process as strings.
if
(
$redirectOutput
)
{
$byteRead
=
-
1
do
{
$byteRead
=
$process
.
StandardOutput
.
BaseStream
.
ReadByte
()
if
(
$byteRead
-ge
0
)
{
$byteRead
}
}
while
(
$byteRead
-ge
0
)
}
else
{
$process
.
StandardOutput
.
ReadToEnd
()
}
When PowerShell launches a native application, one of the benefits it provides is allowing you to use PowerShell commands to work with the output. For example:
PS > (ipconfig)[7] Link-local IPv6 Address . . . . . : fe80::20f9:871:8365:f368%8 PS > (ipconfig)[8] IPv4 Address. . . . . . . . . . . : 10.211.55.3
PowerShell enables this by splitting the output of the program on its newline characters, and then passing each line independently down the pipeline. This includes programs that use the Unix newline (\n
) as well as the Windows newline (\r\n
).
If the program outputs binary data, however, that reinterpretation can corrupt data as it gets redirected to another process or file. For example, some programs communicate between themselves through complicated binary data structures that cannot be modified along the way. This is common in some image editing utilities and other non-PowerShell tools designed for pipelined data manipulation.
We can see this through an example BinaryProcess.exe application that either emits binary data or consumes it. Here is the C# source code to the BinaryProcess.exe application:
using
System
;
using
System.IO
;
public
class
BinaryProcess
{
public
static
void
Main
(
string
[]
args
)
{
if
(
args
[
0
]
==
"-consume"
)
{
using
(
Stream
inputStream
=
Console
.
OpenStandardInput
())
{
for
(
byte
counter
=
0
;
counter
<
255
;
counter
++)
{
byte
received
=
(
byte
)
inputStream
.
ReadByte
();
if
(
received
!=
counter
)
{
Console
.
WriteLine
(
"Got an invalid byte: {0}, expected {1}."
,
received
,
counter
);
return
;
}
else
{
Console
.
WriteLine
(
"Properly received byte: {0}."
,
received
,
counter
);
}
}
}
}
if
(
args
[
0
]
==
"-emit"
)
{
using
(
Stream
outputStream
=
Console
.
OpenStandardOutput
())
{
for
(
byte
counter
=
0
;
counter
<
255
;
counter
++)
{
outputStream
.
WriteByte
(
counter
);
}
}
}
}
}
When we run it with the -emit
parameter, PowerShell breaks the output into three objects:
PS > $output = .\binaryprocess.exe -emit PS > $output.Count 3
We would expect this output to contain the numbers 0 through 254, but we see that it does not:
PS > $output | ForEach-Object { "------------"; $_.ToCharArray() | ForEach-Object { [int] $_ } } ------------ 0 1 2 3 4 5 6 7 8 9 ------------ 11 12 ------------ 14 15 16 17 18 19 20 21 22 (...) 255 214 220 162 163 165 8359 402 225
At number 10, PowerShell interprets that byte as the end of the line, and uses that to split the output into a new element. It does the same for number 13. Things appear to get even stranger when we get to the higher numbers and PowerShell starts to interpret combinations of bytes as Unicode characters from another language.
The Solution resolves this behavior by managing the output of the binary process directly. If you supply the
-RedirectInput
parameter, the script assumes an incoming stream of binary data and passes it to the program directly. If you supply the -RedirectOutput
parameter, the script assumes that the output is binary data, and likewise reads it from the process directly.
Recipe 1.2, “Run Programs, Scripts, and Existing Tools”