简体   繁体   中英

How do I use PowerShell to split a DOS-style command line into its parameters

I have a Powershell script that (as one of its options) reads a user-defined pre-execution command from a file and needs to execute it. The user-defined pre-execution command is expected to be an ordinary DOS-style command. I can split the command by spaces and then feed it to the PowerShell "&" to get it executed:

$preExecutionCommand = "dir D:\Test"
$preExecutionArgs = $preExecutionCommand -split '\s+'
$preExecutionCmd = $preExecutionArgs[0]
$preExecutionNumArgs = $preExecutionArgs.Length - 1
if ($preExecutionNumArgs -gt 0) {
  $preExecutionArgs = $preExecutionArgs[1..$preExecutionNumArgs]
  & $preExecutionCmd $preExecutionArgs
} else {
  & $preExecutionCmd
}

But if the user-defined command string has spaces that need to go in the arguments, or the path to the command has spaces, then I need to be much smarter at parsing the user-defined string.

To the naked eye it is obvious that the following string has a command at the front followed by 2 parameters:

"C:\Program Files\Tool\program1" 25 "the quick brown fox"

Has anyone already got a function that will parse strings like this and give back an array or list of the DOS-style command and each of the parameters?

There is a very simple solution for that. You can misuse the Powershell paramater parsing mechanism for it:

> $paramString = '1 blah "bluh" "ding dong" """foo"""'
> $paramArray = iex "echo $paramString"
> $paramArray 
1
blah
bluh
ding dong
"foo"

I have put together the following that seems to do what you require.

$parsercode = @"
using System;
using System.Linq;
using System.Collections.Generic;
public static class CommandLineParser
{
    public static List<String> Parse(string commandLine)
    {
       var result = commandLine.Split('"')
           .Select((element, index) => index % 2 == 0  // If even index
                  ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                    : new string[] { String.Format("\"{0}\"", element) })  // Keep the entire item   
                         .SelectMany(element => element).ToList();

        return result;
    }
}
"@


Add-Type -TypeDefinition $parsercode -Language CSharp

$commands = Get-Content .\commands.txt

$commands | % {
    $tokens = [CommandLineParser]::Parse($_)
    $command = $tokens[0]
    $arguments = $tokens[1..($tokens.Count-1)]
    echo ("Command:{0}, ArgCount:{1}, Arguments:{2}" -f $command, $arguments.Count, ([string]::Join(" ", $arguments)))
    Start-Process -FilePath ($command) -ArgumentList $arguments
}

I have used some c# code posted by @Cédric Bignon in 2013 which shows a very nice C# Linq solution to your parser problem to create a parser method in [CommandLineParser]::Parse. That is then used to parse the command and Arguments to send to Start-Process.

Try it out and see if it does what you want.

In the end I am using CommandLineToArgvW() to parse the command line. With this I can pass double quotes literally into parameters when needed, as well as have spaces in double-quoted parameters. eg:

dir "abc def" 23 """z"""

becomes a directory command with 3 parameters:

abc def
23
"z"

The code is:

function Split-CommandLine
{
    <#
    .Synopsis
        Parse command-line arguments using Win32 API CommandLineToArgvW function.

    .Link
        https://github.com/beatcracker/Powershell-Misc/blob/master/Split-CommandLine.ps1
        http://edgylogic.com/blog/powershell-and-external-commands-done-right/

    .Description
        This is the Cmdlet version of the code from the article http://edgylogic.com/blog/powershell-and-external-commands-done-right.
        It can parse command-line arguments using Win32 API function CommandLineToArgvW . 

    .Parameter CommandLine
        A string representing the command-line to parse. If not specified, the command-line of the current PowerShell host is used.
    #>
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]$CommandLine
    )

    Begin
    {
        $Kernel32Definition = @'
            [DllImport("kernel32")]
            public static extern IntPtr LocalFree(IntPtr hMem);
'@
        $Kernel32 = Add-Type -MemberDefinition $Kernel32Definition -Name 'Kernel32' -Namespace 'Win32' -PassThru

        $Shell32Definition = @'
            [DllImport("shell32.dll", SetLastError = true)]
            public static extern IntPtr CommandLineToArgvW(
                [MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine,
                out int pNumArgs);
'@
        $Shell32 = Add-Type -MemberDefinition $Shell32Definition -Name 'Shell32' -Namespace 'Win32' -PassThru
    }

    Process
    {
        $ParsedArgCount = 0
        $ParsedArgsPtr = $Shell32::CommandLineToArgvW($CommandLine, [ref]$ParsedArgCount)

        Try
        {
            $ParsedArgs = @();

            0..$ParsedArgCount | ForEach-Object {
                $ParsedArgs += [System.Runtime.InteropServices.Marshal]::PtrToStringUni(
                [System.Runtime.InteropServices.Marshal]::ReadIntPtr($ParsedArgsPtr, $_ * [IntPtr]::Size)
                )
            }
        }
        Finally
        {
            $Kernel32::LocalFree($ParsedArgsPtr) | Out-Null
        }

        $ret = @()

        # -lt to skip the last item, which is a NULL ptr
        for ($i = 0; $i -lt $ParsedArgCount; $i += 1) {
            $ret += $ParsedArgs[$i]
        }

        return $ret
    }
}

$executionCommand = Get-Content .\commands.txt
$executionArgs = Split-CommandLine $executionCommand
$executionCmd = $executionArgs[0]
$executionNumArgs = $executionArgs.Length - 1
if ($executionNumArgs -gt 0) {
    $executionArgs = $executionArgs[1..$executionNumArgs]
    echo $executionCmd $executionArgs
    & $executionCmd $executionArgs
} else {
    echo $executionCmd
    & $executionCmd
}
function ParseCommandLine($commandLine)
{
  return Invoke-Expression ".{`$args} $commandLine"
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM