-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
syscall: exec_windows.go: arguments should not be escaped to work with msiexec #15566
Comments
How many arguments do you want msiexec to get? Because you are passing one argument - args (args is a single string). If you want to pass many arguments, than you should pass them one by one:
and so on. I think waht you want is:
Not tested. I don't even know how msiexec works. Alex |
You are correct, I should have used that in the example. That is the way I originally tested and same error. |
Actually, let me clarify because it is a different error, but same reasoning. When I run:
I get this error: "Could not access network location "C:\testfolder". And then: exit status 1603 I need quotes around the TARGETDIR value because there could be a space in the path. This is the command that ultimately I'm trying to run:
|
msiexec is, probably, confused about having " character in the file name. You confused it.
The exec.Command passes arguments to the program as is. It will deal with spaces, quotes, double quotes and everything else. That is what syscall.EscapeArg does. You job is to provide all parameters exactly as your child process expects them. Leave all the quoting to do for syscall.EscapeArg.
Like I suggested above, you want:
syscall.EscapeArg will insert whatever escape characters to make sure that space in Alex |
When run msiexec on cmd.exe, some of double-quotes will be removed. dummy.c #include <windows.h>
#include <stdio.h>
int
main(int argc, wchar_t* argv[]) {
int i, narg = 0;
LPWSTR* args = CommandLineToArgvW(GetCommandLineW(), &narg);
for (i = 0; i < narg; i++) printf("%ws\n", args[i]);
LocalFree(args);
return 0;
}
But when run dummy.exe from go, those are not removed. package main
import (
"os"
"os/exec"
)
func main() {
cmd := exec.Command(`dummy`, "/a", "package.msi", `TARGETDIR="C:\testfolder"`)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
}
|
When I run this command, it works properly:
When I run this command, I get the MSIEXEC WIndows Installer Help and then exit status 1639:
|
I actually tried running your program, and I agree that exec.Command escaping rules don't work with msiexec. It is second program (that I know of) that behaves this way - first one is cmd.exe (https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/). The Go exec.Command escaping will work if child program reads its arguments with CommandLineToArgv. And most programs doo. But cmd.exe and msiexec.exe do not. So you have to use Cmd.SysProcAttr.CmdLine to build command line by hand. And you do that already. I don't see what else you could do. Alex |
This is to be expected. Arguments are passed as is - quotes and all. If you don't want quotes, don't pass them inside parameters. Alex |
@alexbrainman This is my expected. Just explaining go's behavior. :) |
Ah very interesting! I didn't know cmd and msiexec behaved differently. It looks like other languages have this problem as well. Luckily, we can specify the arguments in a raw format directly using Cmd.SysProcAttr.CmdLine. Do you think a comment added to exec.Command would be sufficient enough? Just for reference, it looks like arguments will be parsed differently depending on the compiler of the program you are passing to: http://daviddeley.com/autohotkey/parameters/parameters.htm#WIN. |
Do you propose to say that it is a mess? :-) Also #15588 might be of interest to you. Alex |
Haha yeah, that should do it! Or something like: The Cmd's Args are escaped and may not work properly with some programs. You can skip escaping them by manually assigning the Args to Cmd.SysProcAttr.CmdLine after prefixing with a space. And I checked out 15588 - CommandLineToArgv() looks like a good way to test commands to ensure they will be split correctly when read by a C/C++ programs - thanks. And for more reference, I ran tests with different syntax in command prompt and the results are below. It's a shame you can't put quotes around the entire property like this because this wouldn't be an issue, but it's a msiexec problem, not a Go problem: "TARGETDIR=C:\testfolder"
|
I am bit concerned about adding a lot of confusing mambo jambo because of two programs (cmd.exe and msiexec.exe). For most users we don't even need to say word "escape", and everything works perfectly. Maybe we should specifically mention cmd.exe and msexec.exe as being different. And say that for some argument strings Cmd.Args will not work, and they should build command line by hand and pass it via Cmd.SysProcAttr.CmdLine. What do you think? Another alternative would be to create new function similar to syscall.EscapeArg, but specifically for cmd.exe. And ignore msexec.exe altogether. Alex |
I agree on preventing it from becoming confusing. Can we do both options? Since manually setting Cmd.SysProcAttr.CmdLine is an option, adding a comment about it would be helpful. And for a more official way, I like the idea of adding an alternative syscall.EscapeArg for cmd. We could then run (hopefully) msiexec and other applications by passing them to cmd like this:
|
I don't see how having special function that handles cmd.exe arguments will help with msiexec.exe. The idea is that this function allows you not to worry about quotes and spaces and similar. If you are prepared to take care of quotes and space and others in your code, then Cmd.SysProcAttr.CmdLine is all you need. |
I am going to take care of quotes and spaces in my code, I'm just trying to think about what makes the most sense for other users that will experience the same problem. A comment seems like it makes the most sense right now since I don't have any good use cases for a separate function for cmd.exe. |
Sounds good to me. Feel free to send the change. Alex |
CL https://golang.org/cl/24730 mentions this issue. |
CL https://golang.org/cl/30947 mentions this issue. |
I just sent CL 30947, which does the right quoting for cmd.exe at least. There is a test that now passes and did not before. The actual rules seem easy enough that we should provide them. The hard question is how to know when to turn on the additional quoting. The CL looks for cmd or cmd.exe or %COMSPEC%, which is a bit of a hack. I would like to have something in the SysProcAttr that lets you opt in or out explicitly, but I would also like it if we could do the right thing by default in more cases than we do now. Is that possible? Detecting explicit cmd is easy, if a bit hacky, but then in #17149 we have an ordinary batch file being executed and that has the same issue. We could also apply the rules for programs ending in ".bat". Is that enough? Do all batch files still end in ".bat"? |
Nah, they also use .cmd. |
I guess the bigger question is: is there a reliable way to detect batch files? Must they end in ".bat" or ".cmd"? And if something does end in ".bat" or ".cmd" is it guaranteed to be a batch file, or at least to be reinvoked using cmd /c ? |
I'm not sure trying to detect this is the correct way. I suggest to add (somewhere) a |
exec.Command() can't handle spaces, even when quoted. Command has to be modified using SysProcAttr. See also: golang/go#15566 Plus: Minor refactoring - reordering of functions, more consistent capitalization, sorting of imports and whatnot.
Considering that probably half of the billions of child processes spawned each day on Windows have something to do with cmd and spaces in paths are nothing unusual since at least the year 2000, I find this curiously optimistic. And odd that the member CmdLine isn't even listed here: https://golang.org/pkg/syscall/#SysProcAttr I probably won't use Go any more myself but still wanted to note that this is obviously a very common use case (simply check the stars on nvm-windows if you don't know what nvm is), so it would be for your own benefit to document this instead of dismiss it. |
exec.Command() can't handle spaces, even when quoted. Command has to be modified using SysProcAttr. See also: golang/go#15566 Plus: Minor refactoring - reordering of functions, more consistent capitalization, sorting of imports and whatnot.
@magical someone still needs to write the code. I have very little free time now days. If you want to contribute fix to building command line that executes cmd.exe, you are welcome to - https://golang.org/doc/contribute.html I will be happy to help you in any way I can. Alex |
This breaks calling Unity installer from go too, as it quotes the arguments breaking the installer. The process is started with |
Problem: When I try to run a command in Go on Windows using exec.Command() and then exec.Run(), the arguments are escaped using this logic: https://github.com/golang/go/blob/master/src/syscall/exec_windows.go#L26. That logic escapes the quotes around TARGETDIR="%v" which need to be there. I currently am assigning to Cmd.SysProcAttr manually to get around the escaping.
go version
)?Go 1.6.2
go env
)?Windows 10 Pro x64
I expect to see the MSI say: Completed the ... Setup Wizard.
I see the Windows Installer help Windows and when I click it, I get: exit status 1639 which is:
ERROR_INVALID_COMMAND_LINE 1639 Invalid command line argument. Consult the Windows Installer SDK for detailed command line help.
The text was updated successfully, but these errors were encountered: