Command Line Interface (CLI) tools can be very useful for interacting with certain applications. However, some CLIs do not let a user pass in parameters which makes it difficult to automate. Instead, they lock a user into an interactive session and force the user to enter commands. Fortunately, some programming languages allow for a redirection of the standard input, output, and error of an running process. A developer co-worker of mine has been very successful doing this in Java, which got me thinking… And turns out, using the .NET libraries, we can implement this functionality for any CLI in PowerShell.
Two .NET assemblies are needed for this task:
– ProcessStartInfo – This object will store the metadata for our process, such as the executable path and arguments.
– Process – This object will correspond to the running process on our system.
First, let’s create a new ProcessStartInfo, assign an executable, and assign some arguments. In this example I am using SQLCMD as our executable (yes, I know SQLCMD accepts parameters, this is just an example). The arguments here are identical to what you would pass the executable if you were executing from a shell. I’m passing my server (localhost\sql2017), a database (master), and setting the flag to use integrated authentication.
$ProcInfo = New-Object System.Diagnostics.ProcessStartInfo $ProcInfo.FileName = 'C:\Program Files (x86)\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn\SQLCMD.EXE' $ProcInfo.Arguments = '-S localhost\SQL2017 -d master -E'
Some additional members need to be assigned to allow our process to redirect input. Luckily, these are all booleans and fairly straight forward.
# These two are required to redirect any of the streams $ProcInfo.UseShellExecute = $false $ProcInfo.ErrorDialog = $false # Allow redirection $ProcInfo.RedirectStandardError = $true $ProcInfo.RedirectStandardInput = $true $ProcInfo.RedirectStandardOutput = $true
With our ProcessStartInfo object instantiated we can now move onto instantiate our Process object. We will create the object and then assign StartInfo to our newly created ProcessStartInfo.
$Process = New-Object System.Diagnostics.Process $Process.StartInfo = $ProcInfo
Next, we will start our process and access its streams. Start returns a boolean which can be logically used for error handling. For clarity, I’ve assigned the streams to new variables but this is not neccessary.
$Process.Start() $STDIn = $Process.StandardInput $STDOut = $Process.StandardOutput $STDError = $Process.StandardError
With our streams re-directed, we can now use the standard input stream programmatically. In this example, I am hard-coding strings but in production this could be a script file or even another process.
$STDIn.WriteLine('SELECT @@SERVERNAME') $STDIn.WriteLine('GO') # If you don't call "quit", SQLCMD will not return $STDIn.WriteLine('quit')
Finally, we can capture all the output from our process using the ReadToEnd method.
There’s several other methods available to make your script more flexible. For example, if your process runs for a long time, you can loop until it completes using the HasExited boolean. Or if your process has multiple points where you need to read from standard out, the ReadLine method is available. All of these are common in more traditional programming languages and it’s incredibly powerful to have them available to us in PowerShell.
Here’s the entire script.
# Specify our executable and parameters. $ProcInfo = New-Object System.Diagnostics.ProcessStartInfo $ProcInfo.FileName = 'C:\Program Files (x86)\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn\SQLCMD.EXE' $ProcInfo.Arguments = '-S localhost\sql2017 -d master -E' # Configure $ProcInfo.UseShellExecute = $false $ProcInfo.ErrorDialog = $false $ProcInfo.RedirectStandardError = $true $ProcInfo.RedirectStandardInput = $true $ProcInfo.RedirectStandardOutput = $true # Create a process $Process = New-Object System.Diagnostics.Process $Process.StartInfo = $ProcInfo # Start the process $Process.Start() # Redirect input, output, and error streams $STDIn = $Process.StandardInput $STDOut = $Process.StandardOutput $STDError = $Process.StandardError # Write some strings to stdin $STDIn.WriteLine('SELECT @@SERVERNAME') $STDIn.WriteLine('GO') $STDIn.WriteLine('quit') # Read all the output Write-Host $STDOut.ReadToEnd()