1

I am using CreateProcess api to start a batch file. The Code works fine on windows 7 but it is failing on Windows 10. Below is the snippet of code:

CString param; //it holds the very long string of command line arguments 
wstring excFile = L"C:\\program files\\BatchFile.bat";
wstring csExcuPath = L"C:\\program files";
wstring exeWithParam = excFile + _T(" ");
exeWithParam = exeWithParam.append(param);
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
TCHAR lpExeWithParam[8191];
_tcscpy_s(lpExeWithParam, exeWithParam.c_str());
BOOL bStatus = CreateProcess(NULL, lpExeWithParam, NULL, NULL, TRUE, CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB, NULL, csExcuPath.c_str(), &si, &pi);

DWORD err;
if (!bStatus)
{
    err = GetLastError();
}

With the above code, it is invoking a batch file which will start an executable with given parameters. This code is not working only Windows 10 in our product. GetLastError is returning error code 122 which code for error "The data area passed to a system call is too small." How to figure out what is causing this error and how it can be resolved?

However, when using the same code in a sample test application is not giving any error and passing. Any clue/hint why is causing it to fail on Windows 10.

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
Vinod
  • 115
  • 11
  • 2
    Roughly said: you cannot create a process with a ".bat" file, it needs to be a ".exe" file. I'm really wondering that this works on Windows 7. You probably want to use `ShellExecute` or `ShellExecuteEx` instead of `CreateProcess` – Jabberwocky Aug 28 '18 at 12:21
  • Why I am using CrreateProcess instead of ShellExecute is that My process is getting started under a job by another process. Which I don't have control. Now I want the child process running as not part of the job. – Vinod Aug 28 '18 at 12:23
  • 2
    It should be possible to CreateProcess on `CMD.exe` passing your batchfile as a parameter eg `"cmd.exe /C path_to_batchfile.bat"` – Richard Critten Aug 28 '18 at 12:25
  • I didn't understand your comment at all, but maybe [this SO article](https://stackoverflow.com/q/25919451/898348) helps. – Jabberwocky Aug 28 '18 at 12:26
  • 2
    If *the exact same code* appears to work when placed in one application, but not when placed in another application, then surely you are either not using the exact same code, or you are changing the environment. The mixture of generic-text mapped types with explicit Unicode types is an alarming signal. At any rate, provide a [mcve], please. – IInspectable Aug 28 '18 at 12:32
  • 1
    It seems very unlikely that anybody would put a .bat file in c:\program files. So the question probably obfuscated the real cause, not giving anybody a chance to see it might be a MAX_PATH induced problem. – Hans Passant Aug 28 '18 at 12:47
  • @Jabberwocky - if file with *.bat* extension as single command line token, windows auto exec `cmd.exe /c *.bat`. so here no error – RbMm Aug 28 '18 at 13:40
  • @RbMm apparently this is not the case on the OP's environnment. – Jabberwocky Aug 28 '18 at 13:45
  • @Jabberwocky - what is not case ? windows first try exec bat file as is and got `STATUS_INVALID_IMAGE_NOT_MZ` after this it check for some well known extensions, like *.bat* (*.cmd*) and if yes - exec exe to which point `ComSpec` environment variable. usually `cmd.exe` – RbMm Aug 28 '18 at 14:00
  • @RbMm the OP is exactly asking why his .bat file isn't being executed on his environnment. – Jabberwocky Aug 28 '18 at 14:02
  • @Jabberwocky exec .bat file like this work on win 10. of course this is not efficient. much better direct use `cmd.exe /c path_to_bat`. but must work. for understand where/why error need debug – RbMm Aug 28 '18 at 14:06
  • @RbMm, I think trying to execute .bat/.cmd files as PE images is a relatively new improvement, i.e. IIRC `CreateProcess` used to immediately rewrite the command line using `ComSpec`, which precluded using these extensions for executable files. Anyway, relative to the total cost of creating a Windows process (including callout to the subsystem process, csrss.exe), this initial `NtCreateUserProcess` call is insignificant. I don't think it's worth rewriting code to use `ComSpec`; I don't think this change would be "much better". – Eryk Sun Aug 28 '18 at 15:53
  • @eryksun - hard say how costly parse command line, additional call to `NtCreateUserProcess` etc. may be and not too much compare all process, but anyway - if exist 100% better solution with pass `lpApplicationName` - why not use it ? – RbMm Aug 28 '18 at 19:20
  • @RbMm, simply the inconvenience of getting the value of `ComSpec` and having to use `lpCommandLine` to pass "cmd /c {path to batch script}" even if no parameters are required, as opposed to letting `CreateProcess` do all of this for you. – Eryk Sun Aug 28 '18 at 19:44
  • @eryksun - this is already meta question. however i very doubt in error code returned `ERROR_INSUFFICIENT_BUFFER`. i not view how this code can be returned at all, if here no output buffer with size – RbMm Aug 28 '18 at 19:50
  • 1
    @RbMm, I won't speculate about the OP's problem. It's too unusual. If it were me, I'd attach a debugger and step through the `CreateProcess` call to get more information about the internal call that fails. – Eryk Sun Aug 28 '18 at 19:57
  • @Vinod does the problem occur only with that exact `BatchFile.bat` file? Could you show the content of that .bat file? What happens if you replace that .bat file with a very basic one? – Jabberwocky Aug 29 '18 at 06:09
  • @Jabberwocky It is happening with any file. Meanwhile i tried many things and found that if I remove create_breakaway_from_job flag then it is starting batch file. I am sure that parent process is started on job with job_object_limit_breakaway_ok option. Do I need to set additional flag/parameter in CreateProcess on winodws 10 to break away from job? – Vinod Aug 29 '18 at 07:02
  • 1
    Need [mcve] including the code that creates JOB object. – zett42 Aug 29 '18 at 07:11
  • @zett42 don't have code which creates the JOB as it's an application given by third party. – Vinod Aug 29 '18 at 09:10
  • 1
    Well, you could try to simulate the behaviour of that third party app by writing a little console program that creates a job object. What happens when you disallow breaking away from the job in that program. Do you also receive `ERROR_INSUFFICIENT_BUFFER`? – zett42 Aug 29 '18 at 12:54
  • 1
    You can also call `QueryInformationJobObject` with `NULL` for parameter `hJob` to query information about the job object your process is associated with. Check if `JOB_OBJECT_LIMIT_BREAKAWAY_OK` flag actually is set. – zett42 Aug 29 '18 at 13:09
  • 1
    Or simply inspect the Job tab for the process in Sysinternals Process Explorer. That said, if you're not allowed to break away from the Job(s), `CreateProcess` should fail with `ERROR_ACCESS_DENIED` (5). – Eryk Sun Aug 29 '18 at 15:06
  • @eryksun Thanks for your help. Checked job tab in process explorer and it is not showing BreakawayOK option for Windows 10 but it is available on Windows 7. But then why it returned 122 error instead of ERROR_ACCESS_DENIED, is confusing. – Vinod Aug 30 '18 at 05:45
  • Attach a debugger such as WinDbg or cdb, and step into the call to see what's specificaly failing. One possibility is that a DLL is injected that hooks `CreateProcess`. I think ConEmu does this. – Eryk Sun Aug 30 '18 at 15:26

3 Answers3

2

You need to execute cmd.exe with the .bat file as a parameter, don't try to execute the .bat directly.

Also, you don't need lpExeWithParam, you can pass exeWithParam directly to CreateProcess().

Try something more like this instead:

CString param; //it holds the very long string of command line arguments
...
wstring excFile = L"C:\\program files\\BatchFile.bat";
wstring csExcuPath = L"C:\\program files";
wstring exeWithParam = L"cmd.exe /c \"" + excFile + L"\" ";
exeWithParam.append(param);

STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi = {};

BOOL bStatus = CreateProcessW(NULL, &exeWithParam[0]/*or exeWithParam.data() in C++17*/, NULL, NULL, TRUE, CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB, NULL, csExcuPath.c_str(), &si, &pi);
if (!bStatus)
{
    DWORD err = GetLastError();
    ...
}
else
{
    ...
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
}
ssbssa
  • 1,261
  • 1
  • 13
  • 21
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • think better call `GetEnvironmentVariableW` for `L"ComSpec"` and pass returned value as first (*lpApplicationName*) argument of `CreateProcessW` instead use 0 here. but anyway this not explain why OP got `ERROR_INSUFFICIENT_BUFFER`, if this is true error – RbMm Aug 28 '18 at 19:17
  • 1
    We do not need to execute cmd.exe manually. This has been the case since NT 3.1. Up to NT 5.x, `CreateProcess` used `"CMD /c"`. In NT 6.0 it switched to using `"%ComSpec% /c"`, with a fallback to `"%SystemRoot%\System32\cmd.exe /c"` when ComSpec isn't defined. IIRC even Windows 9x could run a batch file directly via `"COMMAND.COM /c"`. MSDN is wrong in this case. – Eryk Sun Aug 28 '18 at 20:32
  • 1
    @eryksun: The MSDN documents the supported contract. The implementation is guaranteed to adhere to that contract. If using the API outside its specification (e.g. by passing a batch file) appears to work, then that doesn't invalidate the contract, and the MSDN isn't necessarily wrong. Note in particular, that `CreateProcess` can be called from UWP applications as well, so the implementation may well exhibit different behavior on different platforms. – IInspectable Aug 29 '18 at 06:39
  • @IInspectable, fine, but MSDN is a deeply problematic contract. Bogus claims: `BY_HANDLE_FILE_INFORMATION` says the "identifier (low and high parts) and the volume serial number uniquely identify a file". Flagrantly broken features without ammendment: `FILE_FLAG_POSIX_SEMANTICS` (`CreateFile`) and `FIND_FIRST_EX_CASE_SENSITIVE` (`FindFirstFileEx`) have been broken by default since XP. A console `ReadFile` no longer sets `ERROR_OPERATION_ABORTED` for Ctrl+C (Win 8+). Poorly documented behavior: `NoDefaultCurrentDirectoryInExePath` is documented but not under `CreateProcess` where it matters. – Eryk Sun Aug 29 '18 at 11:42
  • Anyway, it's irrelevant. This is an answer to nothing. Batch files are directly executable via `CreateProcess` in NT 3.1-10.0. I don't know why anyone would mark up an answer the doesn't actually answer the question and sheds absolutely no light on the strange error code for this function, `ERROR_INSUFFICIENT_BUFFER`. This is more like an extended comment on the question with example code. – Eryk Sun Aug 29 '18 at 11:46
  • @ery: I wasn't commenting on the quality of the documentation. I was calling out the concepts. The documentation is the contract. Implementation details are implementation details. If you find, that the implementation doesn't exhibit the contractual guarantees, the MSDN is making it easy for you to leave a comment, asking for correction. – IInspectable Aug 29 '18 at 11:46
  • @IInspectable, comments on MSDN docs for old Windows APIs hit deaf ears from what I've seen. Maybe when the new GitHub based system comes online, it will be spark renewed interest in improving their documentation, to make an actual living contract, rather than a stale document that's riddled with mistakes and erroneous claims that are never fixed. – Eryk Sun Aug 29 '18 at 11:51
0

Error 122 equates to ERROR_INSUFFICIENT_BUFFER and I think the clue here is "it holds the very long string of command line arguments".

Just how long is it? The limit may be lower on Windows 10 - I recommend you experiment (binary chop).

Also, the documentation for CreateProcess states that you must launch cmd.exe explicitly to run a batch file, so I guess you should do what it says.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • windows allow command line be up to `0x7ffe` length ( with terminated 0 - this will be `0x7fff * 2=0xfffe` byte size - the maximum size for `UNICODE_STRING`). when we use length more - we got another error `ERROR_FILENAME_EXCED_RANGE` (*The filename or extension is too long.*) - so this not explain error `ERROR_INSUFFICIENT_BUFFER` (this usually we get when output buffer for some query too small, but not for too large input buffer). of course the best query `"ComSpec"` and use it path as application name, OP code also must work, despite not efficient. only debugging his code can locate error – RbMm Aug 28 '18 at 15:06
  • [What is the command line length limit?](https://blogs.msdn.microsoft.com/oldnewthing/20031210-00/?p=41553) – Remy Lebeau Aug 28 '18 at 15:21
  • @remy Thanks. Shoulda known Raymond would have something to say about this. Anybody have a better idea of what's happening here? Too many command line arguements?? Whatever it might be OP, experiment, experiment, experiment. Maybe put the buffer on the heap instead of the stack (grasping at straws here!) – Paul Sanders Aug 28 '18 at 16:01
  • *"Anybody have a better idea of what's happening here?"* - Maybe the environment block exceeds its limit ([What is the maximum length of an environment variable?](https://blogs.msdn.microsoft.com/oldnewthing/20100203-00/?p=15083)). Could be a run under a user account that, when combined with the system's environment, simply exceeds the limit. – IInspectable Aug 28 '18 at 16:16
  • @IInspectable - error `ERROR_INSUFFICIENT_BUFFER` say about too small output buffer. this is absolute different from too big input buffer. because this i doubt that exactly this error returned at all. and absolute sure that task here not in environment block size. at all here used default from process environment. – RbMm Aug 28 '18 at 19:13
  • @rbm: When copying data into a buffer, and the input data is too large, then that necessitates that the output buffer is too small. It's the same error, really. Presumably, `CreateProcess` copies input from various sources into fixed-sized buffers, with the environment and application name/command line storing data, the client can affect. I'm not saying, that this happens, but at least the error code were the plausible result, if user-provided data exceeded the limits of internal fixed-size buffers. – IInspectable Aug 29 '18 at 06:52
  • @IInspectable - in this case will be another error. and in code snippet no any big input data - cmdline `< 8191`. env is default. only debugging (step internal to createprocess) can show src of this error, if op not confuse error code at all - say this is not actual code and GetLastError is called too late – RbMm Aug 29 '18 at 07:04
  • @rbm: We don't know, what the command line is. Apparently, it *is* long: `CString param; //it holds the very long string of command line arguments`. – IInspectable Aug 29 '18 at 07:08
  • @IInspectable - yes, but i based on `TCHAR lpExeWithParam[8191];` used for cmdline. is it is longer - we have buffer overflow already before call CreateProcess with UB. however how i say - in this case (too large input buffer) we will have **another** error. in case too long cmdline - `ERROR_FILENAME_EXCED_RANGE`. no sense guess. need debug – RbMm Aug 29 '18 at 07:11
  • 2
    [_tcscpy_s](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/strcpy-s-wcscpy-s-mbscpy-s?view=vs-2017) will not overflow the destination buffer. – IInspectable Aug 29 '18 at 07:18
-2

I think to run a batch file you must set lpApplicationName to cmd.exe and set lpCommandLine to the following arguments: /c plus the name of the batch file

  • 1
    That won't work. Cmd.exe doesn't interpret the first argument as part of the command line (a common convention). When you pass `/c BatchFile.bat` as *lpCommandLine*, it will drop the `/c` part, assuming that that is the application name. Regardless of that, this proposed answer doesn't attempt to identify, what the issue is. – IInspectable Aug 28 '18 at 17:23