Start-ContainerProcess missing Args[] paramers
artisticcheese opened this issue · 7 comments
Steps to reproduce the issue:
- Start-ContainerProcess -ContainerIdOrName first -Command "powershell (dir c:)"
What actually happened?:
Error being thrown
container 7c4de1fc1a26875e307a094f98d5c510750b30a0c29a013c79f6001a454c3f35 encountered an error during CreateProcess: failure in a Windows system call: The system cannot find the file specified. (0x2) extra info: {"ApplicationName":"","CommandLine":"\"powershell (dir c:\\)\"","User":"","WorkingDirectory":"C:\\","Environment":{},"EmulateConsole":false,"CreateStdInPipe":true,"CreateStdOutPipe":true,"CreateStdErrPipe":true,"ConsoleSize":[0,0]}
What did you expect to happen?:
Same as output of
docker exec 7c "powershell.exe" "(dir c:\)"
That is listing of directory on C drive
Additional information:
@jhowardmsft - I believe this is a known issue but can you confirm?
Oh. I misread. I believe the issue is that you cant pass the params to docker like that. The REST API is a bit different from the CLI in that it does some fancy parsing. You really need to execute:
Start-ContainerProcess -ContainerIdOrName first -Command @("powershell", "(dir c:)")
@swernli - Can you confirm that you always have to separate args in PS execution?
So -Command parameter always expects string[2] where first element is executable and second one is parameter?
@jterry75 @artisticcheese This one is a bit weird, so bear with me. The short explanation is that docker expects the command parameter in their JSON to be the array of tokenized strings for the command you want to execute, so it can be an array of any length. What you are doing above works, since the items in the parens can be considered one token. Another command that has several tokens, such as "cmd /c echo test" could be represented as "cmd","/c echo test"
or "cmd","/c","echo","test"
and I believe either would work. Alternatively, when using the cmdlet, you could forgo the explicit -Command
and just make sure your command is the last parameter provided, and then just type it normally on the commandline (with proper escapes for any powershell characters like "-"). For example:
> Run-ContainerImage microsoft/nanoserver powershell echo test
This method has some big conveniences and some annoying quirks, and understanding them requires the longer explanation...
Cmdlets that include the Command parameter have it decorated with ValueFromRemainingArguments = true
as you can see at https://github.com/Microsoft/Docker-PowerShell/blob/master/src/Docker.PowerShell/Cmdlets/StartContainerProcess.cs#L20. What this means is that if the -Command
parameter name is not explicitly provided, then powershell will just populate the command from whatever elements are left at the end of the cmdlet invocation after processing all other parameters. This is as close as we could get to the behavior of the docker CLI, which relies on the tokenizing of the shell it executes in for translating input from the user into a string array. If we had made the Command parameter a single string instead of string[]
then we would have to tokenize it ourselves, and that would be a pretty complex bit of code. Unfortunately, ValueFromRemainingArguments uses powershell’s cmdline logic, which means extra escape characters may have to used to differentiate between elements you want powershell to evaluate and elements that should be passed along as-is to the parameter. Your use of parenthesis above is a good example, in fact. If I try to use the remaining arguments trick for that, I get an error:
PS > Run-ContainerImage microsoft/nanoserver -Isolation HyperV -RemoveAutomatically powershell (dir c:\)
Invoke-ContainerImage : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Command'.
Specified method is not supported.
At line:1 char:1
+ Run-ContainerImage microsoft/nanoserver -Isolation HyperV -RemoveAuto ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Invoke-ContainerImage], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Docker.PowerShell.Cmdlets.InvokeContainerImage
This is because powershell, when it sees the (dir c:\)
, actually evaluates it and tries to insert the resulting array of objects as an item into the Command string array. What you really want is powershell to ignore the parenthesis and just pass them through as tokens instead of evaluating them, so you have to escape them using a backtick:
PS C:\Windows\system32> Run-ContainerImage microsoft/nanoserver -Isolation HyperV -RemoveAutomatically powershell `(dir c:\`)
This will produce the correct result. The same would have to be done with the "-" and "$" characters to prevent the host powershell window from trying to interpret them.
It's not pretty, but it's the closest we could get to something that would allow callers to write container commands without having to manually tokenize the command in a string array themselves.
Damn, I hoped going to powershell route on managing lifecycle of container instead of relying on ugly "docker" commands will prevent what you just described above happening. But seems this UNIX born theater followed to powershell wrapper as well.
Yeah, it's definitely annoying. If there were some generic tokenize function in .NET somewhere, we might have been able to use that and simplify this, but we couldn't find anything. And tokenizing input is an annoying problem, especially when you factor in how many special characters may or may not change the behavior and the fact that which characters are considered special changes between shells and platforms. So both docker CLI and cmdlet take the same approach: punt the problem to the shell itself, and let the user figure out how to say what they mean.
I've run into the same problem when executing cmd commands from within powershell and vice versa; in general, interop between multiple shell paradigms is not well solved, and containers bring that to the forefront due to the desire to specify a command within another command.