CreateProcess is able to execute batch files, but documentation says the opposite - windows

Consider the following files:
a.bat:
#echo Hello from bat %1
and c.cpp:
#define UNICODE
#include <windows.h>
#include <stdio.h>
void check(TCHAR *cmd, TCHAR *args) {
STARTUPINFO sinf;
PROCESS_INFORMATION pinf;
memset(&sinf, 0, sizeof sinf);
sinf.cb = sizeof(sinf);
CreateProcess(cmd, args, NULL, NULL, FALSE, 0, NULL, NULL, &sinf, &pinf);
WaitForSingleObject(pinf.hProcess, INFINITE);
}
int main() {
TCHAR cmd1[] = L"a";
TCHAR cmd2[] = L"a.bat";
TCHAR cmdargs1[] = L"a argument";
TCHAR cmdargs2[] = L"a.bat argument";
TCHAR args[] = L"argument";
#define run_check(a, b) printf(#a " + " #b "\n"); fflush(stdout); check(a, b)
run_check(cmd1, cmdargs1);
run_check(cmd1, cmdargs2);
run_check(cmd1, args);
run_check(cmd2, cmdargs1);
run_check(cmd2, cmdargs2);
run_check(cmd2, args);
run_check(NULL, cmdargs1);
run_check(NULL, cmdargs2);
printf("Done\n");
return 0;
}
Note that I haven't specified cmd.exe in any of calls to CreateProcess, while MSDN says that I have to do it:
To run a batch file, you must start the command interpreter; set lpApplicationName to cmd.exe and set lpCommandLine to the following arguments: /c plus the name of the batch file.
However, I get the following output:
cmd1 + cmdargs1
cmd1 + cmdargs2
cmd1 + args
cmd2 + cmdargs1
Hello from bat argument
cmd2 + cmdargs2
Hello from bat argument
cmd2 + args
"argument" не является внутренней или внешней
командой, исполняемой программой или пакетным файлом.
NULL + cmdargs1
NULL + cmdargs2
Hello from bat argument
Done
This means that whenever .bat extension is explicitly specified either in lpApplicationName or lpCommandLine, batch file is successfully started. This works wit .cmd too, but not with .vbs. Does anyone know reason behind such behavior and difference between practice and documentation? Is such behavior 'an accident' or it persists among different Windows versions? (I use Windows 7 HP) Is there any point in documentation from which one can infer such behavior?

As a software developer, you shouldn’t rely on undocumented behavior. Even if something works fine when you test it, if the documentation says “you must do it another way”, you generally have to forget your tests, and do as instructed. Otherwise your software can silently break down with the next windows update, including even the minor update.
Now on the batch/vbs files.
To run the CMD/BAT, call GetEnvironmentVariable("ComSpec") to obtain path to cmd.exe, then call CreateProcess specifying command=cmd.exe, arguments /C path_to_CMD_file arg1 arg2.
If the path or arguments contain space, you must include them in quotes. If the path or arguments contain spaces contain ", replace with ^".
To run VBS, call ExpandEnvironmentStrings("%windir%\\System32\\cscript.exe") or wscript.exe, then pass VBS path in the first argument of the .exe file. If the path or an argument contain spaces or quotes, same rules apply to escaping.

Related

How to zip a folder in my Win32 Visual C++ app using a system call

I am trying to zip a folder in my Win32 Visual C++ app using a system call. On my computer, I can do this by the cmd command:
PowerShell -Command "Compress-Archive -Path C:\Users\ttyler\Desktop\Software\Folder2\RUN -DestinationPath C:\Users\ttyler\Desktop\Software\Folder2\RUN"
Where "Try" is a folder.
I am trying to do this on my app using a System call, but in my app, I have the "Path" and the "DestinationPath" stored in variables (because the destination path is being pulled in by the user). I'm not sure I'm using the variables correctly in my system call because this is giving me the error: "'+': cannot add two pointers" and "expression must have integral or unscoped enum type".
TCHAR path = L"C:\Users\ttyler\Desktop\Software\Folder2\RUN";
TCHAR DestinationPath = L"C:\Users\ttyler\Desktop\Software\Folder2\RUN.zip";
system("Compress-Archive -Path" + path[0] + "-DestinationPath" + DestinationPath[0]);
What am I doing wrong when using the variables in my system call?
Your code won't work for many reasons:
you are trying to assign wide string literals to single-char variables. You need to use arrays or pointers instead, eg:
TCHAR path[] = L"C:\\Users\\ttyler\\Desktop\\Software\\Folder2\\RUN";
TCHAR DestinationPath[] = L"C:\\Users\\ttyler\\Desktop\\Software\\Folder2\\RUN.zip";
LPCTSTR path = L"C:\\Users\\ttyler\\Desktop\\Software\\Folder2\\RUN";
LPCTSTR DestinationPath = L"C:\\Users\\ttyler\\Desktop\\Software\\Folder2\\RUN.zip";
you cannot concatenate string literals with array/single-chars the way you are trying to do. You will have to allocate new arrays and copy/format the various substrings into it, eg:
WCHAR path[] = L"C:\\Users\\ttyler\\Desktop\\Software\\Folder2\\RUN";
WCHAR DestinationPath[] = L"C:\\Users\\ttyler\\Desktop\\Software\\Folder2\\RUN.zip";
WCHAR cmd[(MAX_PATH*2)+50] = {};
swprintf(cmd, L"Compress-Archive -Path \"%s\" -DestinationPath \"%s\"", path, DestinationPath);
// use cmd as needed...
Or, just use std::wstring instead, eg:
std::wstring path = L"C:\\Users\\ttyler\\Desktop\\Software\\Folder2\\RUN";
std::wstring DestinationPath = L"C:\\Users\\ttyler\\Desktop\\Software\\Folder2\\RUN.zip";
std::wstring cmd = L"Compress-Archive -Path \"" + path + L"\" -DestinationPath \"" + DestinationPath + L"\"";
// use cmd as needed...
system() takes a const char* pointer as input. But you are using TCHAR strings, which will only work correctly for your wide string literals if TCHAR is WCHAR. Which means you can't pass your concatenated string to system() anyway. You will have to use _wsystem() instead, eg:
_wsystem(cmd); // when using WCHAR[]/LPWSTR
_wsystem(cmd.c_str()); // when using std::wstring
system()/_wsystem() runs an instance of cmd.exe /C <command-line>, where <command-line> is the specified input string. But PowerShell.exe is its own app with its own command-line parameters. PowerShell.exe commands ARE NOT valid cmd.exe commands, ie you are trying to execute this:
cmd.exe /C Compress-Archive -Path <path> -DestinationPath <dest>
Which is not a valid command, as cmd.exe does not know what Compress-Archive is. You would need to execute this instead:
cmd.exe /C PowerShell -Command "Compress-Archive -Path \"<path>\" -DestinationPath \"<dest>\""
WCHAR cmd[(MAX_PATH*2)+70] = {};
swprintf(cmd, L"PowerShell -Command \"Compress-Archive -Path \\\"%s\\\" -DestinationPath \\\"%s\\\"", path, DestinationPath);
std::wstring cmd = L"PowerShell -Command \"Compress-Archive -Path \\\"" + path + L"\\\" -DestinationPath \\\"" + DestinationPath + L"\\\"";
But, you really should use CreateProcess() instead to run PowerShell.exe directly, don't use cmd.exe at all, eg:
STARTUPINFO si = {sizeof(STARTUPINFO), 0};
PROCESS_INFORMATION pi = {};
if (CreateProcess(NULL, cmd/*cmd.data()*/, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
Otherwise, put your PowerShell commands into a .bat script, eg:
#echo off
PowerShell -Command "Compress-Archive -Path \"%0\" -DestinationPath \"%1\""
And then you can use system()/_wsystem() to run that script, eg:
WCHAR cmd[(MAX_PATH*2)+25] = {};
swprintf(cmd, L"myscript.bat \"%s\" \"%s\"", path, DestinationPath);
_wsystem(cmd);
std::wstring cmd = L"myscript.bat \"" + path + L"\" \"" + DestinationPath + L"\"";
_wsystem(cmd.c_str());

JScript: identifying whether double quotes are passed to a WSH script

There are situations when it is important to identify whether double quotes are passed as arguments to a WSH script. For example because they should be passed to another executable to be run.
The standard parsing functions/objects:
objArgs = WScript.Arguments;
for (i = 0; i < objArgs.length; i++)
{
WScript.Echo(objArgs(i));
}
do not differentiate between:
cscript foo.js "bar"
and
cscript foo.js bar
Is it possible with some other approach?
Note: I also tried to sort of escape them with several combinations like:
cscript foo.js '"bar"'
It seems that they are simply stripped away.
Following #Ekkehard.Horner suggestions:
Solution
// parseArgs.js
// Parsing jscript script arguments verbatim
var Shell = new ActiveXObject("WScript.Shell"),
wmi = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2"),
guid = (new ActiveXObject("Scriptlet.TypeLib")).GUID.substring(0,38),
windir=Shell.ExpandEnvironmentStrings("%WinDir%"),
winver="\"" + windir + "\\System32\\winver.exe\" " + guid,
pcol, pid, cmd;
// Run winver.exe hidden and get this script ID as its ParentProcessId
winver=winver.replace(/\\/g, "\\\\");
Shell.Run("winver " + guid, 0);
pcol = new Enumerator (wmi.ExecQuery(
"SELECT * From Win32_Process WHERE CommandLine='"+ winver + "'",
"WQL", 32));
for (; !pcol.atEnd(); pcol.moveNext()){
var prc = pcol.item();
pid=prc.ParentProcessId;
prc.Terminate;
}
// Get the command line for the found PID
pcol = new Enumerator (wmi.ExecQuery(
"SELECT * From Win32_Process WHERE ProcessID="+ pid,
"WQL", 32));
for (; !pcol.atEnd(); pcol.moveNext()){
var prc = pcol.item();
cmd =prc.CommandLine;
}
WScript.Echo(cmd);
// Parse command line for arguments
var ags,
parseCmd=function(cmd){// WMI trims initial spaces
var p = new Object(),
re =/^"/.test(cmd) ? /"[^"]+" */ : /\S+\s*/;
p.nxt=re.test(cmd) ? cmd.match(re)[0] : ""; // extract next token
p.rst=cmd.replace(re, "") ; // remainder
return(p);
}
// Strip c/wscript path
ags=parseCmd(cmd).rst
//WScript.Echo(ags);
// Remove WSH "//xxx" options
ags=ags.replace(/\/\/\w+ +/g, "")
//WScript.Echo(ags);
// Strip script name and get arguments
ags=parseCmd(ags).rst
WScript.Echo(ags);
// Loop args and store as an array
var i=1, aags=[];
while(ags != ""){
var p =parseCmd(ags);
ags=p.rst;
aags.push(p.nxt.replace(/ +$/, ""));
WScript.Echo(i, p.nxt);
i++;
}
WScript.Echo(aags);
Test
Running parseArgs.js gives:
> cscript //nologo parseArgs.js "hello" world
cscript //nologo parseArgs.js "hello" world
"hello" world
1 "hello"
2 world
"hello",world
The line:
> parseArgs.js "hello" world
gives similar results.
Comments
Do we need such a convoluted script? Short answer: no. Long: depends.
In general, assuming you know the name of your script when it is run, you could query WMI for it.
Anyway, when you deploy your script, you do not normally have control on the deploy directory. So, if there is another script running under the same name, you can't know for sure which one is yours.
Another not so edge case is when there are two or more instances of your script running.
The strategy here is to run some dummy standard Windows executable (winver.exe) hidden, passing to it a GUID. In this way, it is safe to identify winver.exe command line by the unique GUID and consequently your script as the parent of winver.exe.
winver.exe does not require arguments, but does not protest if you pass some to it.

Implementing my shell using c, Change the directory

Implementing my own shell
I did not post the whole code to save your time,
briefly i used exec() to execute the command lines
my problem is clarified in a comment line below
thanks for any help
int main()
{
int pid[2];
char inbuf[10];
printf("\n\nSimple Shell using C\n");
char hostname[1024];
hostname[1023] = '\0';
gethostname(hostname, 1023);
char resolved_path[100];
realpath("./", resolved_path);
printf("Maram #%s :<%s>$ ", hostname,resolved_path); //MY PROBLEM: For cd command ex: cd Desktop/Folder.. does not go in this directory and the resolved path does not change
while(1){
printf("[my shell] :");
gets(inbuf);
if(fork()){
wait();
}else{
pip(inbuf, 0, 1);
}
}
return 0;
}
/// ALSO, how can I print a command not found if an entered command line is not correct?
Nowhere in this program do you call chdir() -- which is the only way to change your current process's directory (barring functional equivalents such as fchdir(); which still retain the fundamental restriction that they impact only the current process and not its parents).
Using an exec-family call to launch an external program which changes its own directory has no effect -- that program's directory is separate from your shell's. The shell must change its own directory without forking if you want to have any effect.

Prevent console window from being created in custom node.js build

I'm creating a custom build of node.js that should not show a console window to the user.
I've tried changing the linker config in the gyp file to 2 (which should set the linker flag /SUBSYSTEM:WINDOWS), but I still get a console window when I run the resulting node.exe binary.
How can I prevent the console window from appearing?
Edit: Further investigation reveals that the linker config in node.gyp is not taking effect. The generated node.vcxproj still has <Link><SubSystem>Console</SubSystem></Link> (which is very strange to me, since adding 'UACUIAccess': 'true' in the same part of node.gyp did take effect), so the built binary is incorrectly linked.
Solution 1
Save this one line of text as file invisible.vbs:
CreateObject(“Wscript.Shell”).Run “”"” & WScript.Arguments(0) & “”"”, 0, False
To run any program or batch file invisibly, use it like this:
wscript.exe “C:\Wherever\invisible.vbs” “C:\Some Other Place\MyBatchFile.bat”
To also be able to pass-on/relay a list of arguments use only two double quotes
CreateObject(“Wscript.Shell”).Run “” & WScript.Arguments(0) & “”, 0, False
eg: Invisible.vbs “Kill.vbs ME.exe”
Solution 2
Use a command line tool to silently launch a process : Quiet.
Solution 3
Roll your own C++ Win32 App:
PROCESS_INFORMATION procInfo = {0};
STARTUPINFOstartupInfo = {0};
SECURITY_ATTRIBUTESsaAttr = {0};
HANDLEhStdIn = GetStdHandle(STD_INPUT_HANDLE);
HANDLEhStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLEhStdErr = GetStdHandle(STD_ERROR_HANDLE);
// build up security attributes
saAttr.nLength = sizeof(saAttr);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// set file handles for process to be created
startupInfo.cb = sizeof(startupInfo);
startupInfo.dwFlags = STARTF_USESTDHANDLES;
startupInfo.hStdInput = hStdIn;
startupInfo.hStdOutput = hStdOut;
startupInfo.hStdError = hStdErr;
// build command line: format is [cmd.exe /c "%batchScript%" %batchArgs%]
if (-1 == _snprintf_s(cmd, sizeof(cmd),"cmd.exe /c \"%s\" %s", batchScript, batchArgs))
errorExit("_snprintf_s(\"cmd.exe /c \"%%s\" %%s\"), \"%s\", \"%s\") failed.", batchScript, batchArgs);
rc = CreateProcess(NULL, cmd, NULL, &saAttr, TRUE, CREATE_NO_WINDOW, NULL, tempPath, &startupInfo, &procInfo);
You have to change the SubSystem field value in node.exe PE optional header. The current value is 3 which is defined as Windows Console. If you change it to 2 (which is defined as Windows GUI) there would be no console window. In order to patch the executable file, you have to use utilities to change Optional Header of PE.
One example of such a tool is PE tools.
Click on Optinal Header and then change the Subsystem from 3 to 2.
That`s all.
Remember that with this change you can only run js files. You can not use interactive mode.
It appears that you must:
Comment out the 'SubSystem': 1 line in common.gypi. (Changing it to 2 causes the build to fail in mksnapshot.)
Change SubSystem to 2 in node.gyp
Also add 'EntryPointSymbol': 'wmainCRTStartup' to node.gyp.
This builds a node.exe that does not create a console window.

Running another program in Windows bat file and not create child process

I have subversion server with a post-commit hook to do something.
I want the checkin finish soon, not wait the hook script.
But by design, the Subversion post-commit hook script will run until all child process exit, so using somthing like:
start another_prog...
in the hook bat file has no use.
So I want to know how to run another program in Windows bat file which not create child process or let the child process detach from the parent.
Synchronous. The second notepad won't launch until you close the first.
notepad.exe c:\temp\a.txt
notepad.exe c:\temp\b.txt
Asynchronous: The second notepad will launch even if you haven't closed the first.
start notepad.exe c:\temp\a.txt
start notepad.exe c:\temp\b.txt
More info about the start command:
http://www.robvanderwoude.com/ntstart.php
EDIT: The following comment was made elsewhere by #zhongshu, the original poster. I'm only copying it here:
start cmd /c doesn't work because SVN
post-commit hook will wait for the
hook and the child process created by
the hook exit. It's the design of SVN.
I have found a solution, Please refer:
http://svn.haxx.se/users/archive-2008-11/0301.shtml
Assuming that he knows what he's talking about, I'm wrong and...undeserving.
I found a method to resolve my question, compile the following c code and named the exe output as runjob.exe, and then in the hook bat file, use " runjob another_prog " , now it's ok.
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
int _tmain()
{
char * pCmd = ::GetCommandLine();
// skip the executable
if (*pCmd++ == L'"')
{
while (*pCmd++ != L'"');
}
else
{
while (*pCmd != NULL && *pCmd != L' ')
++pCmd;
}
while (*pCmd == L' ')
pCmd++;
STARTUPINFO si;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
ZeroMemory( &pi, sizeof(pi) );
// Start the child process.
BOOL result = CreateProcess
(
NULL, // No module name (use command line)
pCmd, // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set bInheritHandles to FALSE
DETACHED_PROCESS, // Detach process
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (returned)
);
if (result) return 0;
char msg[2048];
FormatMessage
(
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
::GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT),
msg, sizeof(msg),
NULL
);
fputs(msg, stderr);
_flushall();
return -1;
}
What you can do is create a Scheduled Task that executes the batch script or other executable that runs for a long time. Set it to run once, in the past and don't set it to delete the task when no more runs are scheduled. Then in your Subversion hook script, put the following line in:
schtasks /run /tn NameOfYourTaskHere
I confirmed with a test by having my scheduled task run Notepad++ and the Notepad++ executable showed up as a child of svchost.exe, not the cmd.exe window that I executed the schtasks command from.
Use:
start cmd /c "your command"
Cheers.
Try cmd /c "your command"
Could you use the windows task scheduler command line interface "schtasks /run" to start a job that runs the "another_prog"? You'd have to create the job ahead of time. There also used to be a "SOON" program with the Windows (NT) Resource Kit that would create dynamic entries for the "AT" command scheduler to run a job in a few minutes that would not require setting up a job ahead of time, it can still be found with a little searching.
You can create a hybrid batch/JScript file (i.e. a batch file able to run embedded JScript) where the JScript part will run another_prog in detached mode with shell.Run(prog, 0, false):
#if (#X)==(#Y) #end /* JScript comment
rem put any batch code that runs before another_prog here
#cscript //E:JScript //nologo "%~f0" "another_prog" 0 false
rem put any batch code that runs after another_prog here
exit /b %errorlevel%
#if (#X)==(#Y) #end JScript comment */
var ARGS = WScript.Arguments;
var shell = new ActiveXObject("WScript.Shell");
shell.Run(ARGS.Item(0),ARGS.Item(1),ARGS.Item(2));`

Resources