Sunday, July 24, 2011

Run-Executable

OK, so one of the promises of PowerShell is that not only does it do all this cool new stuff, but all your favorite DOS commands are aliases of the new PowerShell commands, but that you can also run your old executables just like you used to….

Well not so fast….

Running a straight executable such as MyExe.exe, works just fine as long as you’re in the exe’s folder or it resides in a folder in the PATH environment variable.

Try passing to start passing it command line arguments and things start to get squirly.

This is due to how PowerShell handles command line arguments.  PowerShell has three types of arguments:

  • Positional – these arguments are expect to be in a specific position such as:
    • Run-Command “Param1Value”
  • Named – these arguments are proceeded by the parameter name and can be in any position as follows (note that I’m passing a string literal to Param2 and a variable value to Param1:
    • Run-Command –Param2 “Param2Value” –Param1 $Param1Value
  • Switch Parameters – These parameters are either bolean parameters that are either present or not such as:
    • Run-Command –SwitchedOn

PowerShell tries to treat executable paramters the same way.  This works great if your executable uses the same format, but if you’re trying to run MsiExec you’ve got a problem.

MsiExec /i MyInstall.msi
Will work fine, but try:
$MyCustomInstallFolder = “D:\Custom Program Files”
MsiExec /I MyInstall.msi TARGETDIR=$MyCustomInstallFolder

Not so much.  the problem is is that what is actually getting passed is:

MsiExec /i MyInstall.msi “TARGETDIR=D:\Custom Program Files”
MsiExec ends up ignoring the TARGETDIR parameter because PowerShell didn’t recognize TARGETDIR as a named parameter and treated it as a positional parameter, surrounding it with quotes because the expanded string contained spaces.

After fighting with this for quite some time (and doing a fair amount of Googling), I ended up writing the following function that utilizes the .NET Process object to execute an executable, passing a string variable for the entire command line.

Function Start-Process
{
    Param([string]$Executable,
    [string]$Arguments,
    [string]$WorkingFolder,
    [int]$Timeout = 240000,
    [switch]$ShowStandardOutput)
   
    Write-Host ("Starting Process, {0}" -F $Executable);
    Write-Host ("Command Line Args:  {0}" -F $Arguments);
    Write-Host ("Working Folder:  " -F $WorkingFolder);
    Write-Host ("Timeout:  {0}" -F $TimeOut);
    Write-Host ("Show Std Out:  {0}" -F $ShowStandardOutput);
   
    $ProcessInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo;
    $ProcessInfo.FileName = $Executable;
    $ProcessInfo.WorkingDirectory = $WorkingFolder
    $ProcessInfo.UseShellExecute = $false;
    if ($ShowStandardOutput) {
        $ProcessInfo.RedirectStandardOutput = $true;
    }
    else {
        $ProcessInfo.RedirectStandardOutput = $false;
    }
    $ProcessInfo.RedirectStandardError = $false;
    $ProcessInfo.Arguments = $Arguments;
    $ProcessInfo.CreateNoWindow = $false;
    $ProcessInfo.ErrorDialog = $false;
   
    $Process = New-Object -TypeName System.Diagnostics.Process;
    $Process.StartInfo = $ProcessInfo;
    $Process.EnableRaisingEvents = $false;
   
    $Process.Start();
    if ($ShowSTandardOutput) {
        $Output = $Process.StandardOutput.ReadToEnd();
        $Output;
    }
    if (-not $Process.WaitForExit($Timeout)) {
        $Process.Kill;
        $ProcessInfo;
        throw "Start-Process - Process timed out";
    }
   
    if ($Process.ExitCode -ne 0) {
        $ProcessInfo;
        throw "Start-Process - Process failed with exit code - " + $Process.ExitCode
    };
}

Since then I’ve read that you can actually run Cmd.exe, passing the entire command line (executable and arguments) as a string, thus doing something similar to what I’m doing with the Process object.

No comments:

Post a Comment