I'm new to QT and have been spending a lot of time on this issue now.
Starting a batch file like below on Windows 10 has no problem but fails at Windows 7 and Windows XP.
Qprocess process;
QString data = "test.bat";
int success = 0;
success=process.execute(data);
The above snippet works fine on Windows 10.
On Windows 7 and XP it works if the data is modified as below:
"cmd.exe /c start test.bat"
Why does this difference exist for different Windows version ?
Help much appreciated!!
The question cannot be really answered as the used code depends on the execution environment defined outside of the application by the parent process on starting the application and there is nothing posted how the application is started on Windows XP, Windows 7 and Windows 10.
test.bat is referenced with no path which means it must be found in the current directory set for the application coded in C++ using Qt5 by the parent process on execution of the Windows library function CreateProcess which is used also by Qt5 class QProcess on Windows.
A batch file is not an executable. It is a script file which needs a script interpreter which is for batch files since Windows NT4 the Windows command processor cmd.exe. So the Windows command processor must be executed to process the batch file which is most likely stored in the directory of the C++ coded application using Qt5.
It is highly recommended to write the C++ code with using Qt5 being independent on the current working directory of the application and the environment variables PATH and PATHEXT by using the following commented code.
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QProcess>
int main(int argc, char **argv)
{
QCoreApplication qDemoApp(argc,argv);
// Get the fully qualified file name of the Windows command processor.
QString qsCmdFileName = qEnvironmentVariable("ComSpec");
// The environment variable ComSpec is usually defined on Windows
// as system environment variable, but check if really existing.
if(qsCmdFileName.isEmpty())
{
// Get the full path to the Windows directory not ending with the
// directory separator using the environment variable SystemRoot.
qsCmdFileName = qEnvironmentVariable("SystemRoot");
// It is very unlikely that this environment variable is not defined
// as it is always defined by the Windows shell, i.e. explorer.exe
// running as Windows shell. But it is nevertheless possible that
// the environment variable is not defined in the list of the
// environment variables of the current process. In this case use
// directory Windows on system drive as directory and hope this
// directory exists as otherwise the execution of cmd.exe fails.
if(qsCmdFileName.isEmpty())
{
qsCmdFileName = QDir::toNativeSeparators(QDir::rootPath()) + QStringLiteral("Windows");
}
qsCmdFileName.push_back(QStringLiteral("\\System32\\cmd.exe"));
}
// The process must be executed with the following command line on
// C:\Windows\System32\cmd.exe is the fully qualified file name of
// the Windows command processor and the application directory has
// the unusual path C:\Temp\Development & Test(!) containing the
// batch file Test.bat which is explicitly set as working directory.
// C:\Windows\System32\cmd.exe /D /E:ON /V:OFF /C Test.bat
QStringList lqsArgumentsList(QStringLiteral("/D"));
lqsArgumentsList.append(QStringLiteral("/E:ON"));
lqsArgumentsList.append(QStringLiteral("/V:OFF"));
lqsArgumentsList.append(QStringLiteral("/C"));
lqsArgumentsList.append(QStringLiteral("Test.bat"));
QProcess qWindowsCommandProcess;
qWindowsCommandProcess.setWorkingDirectory(QCoreApplication::applicationDirPath());
qWindowsCommandProcess.start(qsCmdFileName,lqsArgumentsList,QIODevice::NotOpen);
if(!qWindowsCommandProcess.waitForFinished(-1))
{
QChar qSpace(' ');
QString qsCommandLine = qsCmdFileName + qSpace;
qsCommandLine.push_back(lqsArgumentsList.join(qSpace));
qInfo().noquote().nospace() << "ERROR: Failed to execute\n" << qsCommandLine << "\nin the directory:\n\""
<< QDir::toNativeSeparators(QCoreApplication::applicationDirPath()) << QChar('\"');
return 1;
}
return 0;
}
The Windows command processor arguments /E:ON and /V:OFF can be removed if the batch file Test.bat contains at top
#echo off
setlocal EnableExtensions DisableDelayedExpansion
and has as last line
endlocal
to define the execution environment completely without depending on which environment is defined on starting cmd.exe respectively on Windows command processor defaults which Windows users could customize although usually not done by them as most batch files would not work anymore as expected by their authors on really doing that. Run in a command prompt window cmd /? for the usage help and read the Microsoft documentation about cmd for more details.
Good coded applications do not depend on execution environment properties defined outside of the application as much as possible. The code above depends on the environment variable ComSpec and if not defined on environment variable SystemRoot. The usage of these two environment variables could be also avoided, but that would require the usage of the native Windows library function GetSystemDirectory instead of Qt5 functions.
For testing this code create in application directory the batch file Test.bat with the single command line #echo Executed %0 at %TIME% in "%CD%">"%~n0.txt". Then create another batch file AppTest.bat with the following command lines with environment variable AppPath defined with correct path to the directory containing your executable and Test.bat and AppName defined correct with file name plus file extension of your application:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "AppPath=C:\Full Path\To Application"
set "AppName=AppName.exe"
rem Set a directory different to application directory as current directory.
cd /D "%TEMP%"
call :TestRun "Use environment variable ComSpec defined with: %ComSpec%"
set "ComSpec=C:\Temp\cmd.exe"
call :TestRun "Use environment variable ComSpec defined with: %ComSpec%"
set "ComSpec="
call :TestRun "Use environment variable SystemRoot defined with: %SystemRoot%"
set "SystemRoot=C:\Temp"
call :TestRun "Use environment variable SystemRoot defined with: %SystemRoot%"
set "SystemRoot="
call :TestRun "Use environment variable SystemDrive defined with: %SystemDrive%"
set "SystemDrive=Z:"
call :TestRun "Use environment variable SystemDrive defined with: %SystemDrive%"
set "SystemDrive="
call :TestRun "Use environment variable SystemDrive not defined at all."
goto EndBatch
:TestRun
echo %~1
"%AppPath%\%AppName%"
if not errorlevel 1 if exist "%AppPath%\Test.txt" (
type "%AppPath%\Test.txt"
del "%AppPath%\Test.txt"
)
echo/
goto :EOF
:EndBatch
pause
endlocal
QDir::rootPath() makes use of the Windows system environment variable SystemDrive on being compiled without preprocessor macro Q_OS_WINRT defined.
The batch file AppTest.bat can be executed from within a command prompt window or with a double click on it in Windows File Explorer.
Related
In unix command line you can do this
tempvar=ABC mybinary
apparently you can do the same in Windows batch, but in case you need multiple assignments to pass, export in unix would do the trick. How could you pass multiple variables to mybynary in a Windows Batch script?
1. Information about Windows commands and Windows command processor syntax
There can be executed help in a command prompt to get output an incomplete list of Windows commands with a brief description. Any Windows command can be executed with /? as argument to get output the usage help for the command. The execution of cmd /? results in output of the usage help of Windows command processor cmd.exe which is the Windows equivalent for man sh (or man bash, man dash, man ksh, man zsh, etc.) in a Linux terminal window.
For most Windows commands, but not all of them, it is also possible to run help with the command name as argument. help set and set /? output both the usage help of the command to define environment variables.
There is additionally:
the Microsoft documentation about the Windows commands
the A-Z index of Windows CMD commands by SS64
the Windows CMD Shell How-to guides and examples by SS64
SS64 offers also comprehensive documentations for other scripting languages on Windows, Linux and Mac.
There are two types of Windows commands:
the internal commands of cmd.exe like echo, copy, set
the so called external commands like chcp, find, robocopy
The external Windows commands are executables in directory %SystemRoot%\System32 with file extension .com like chcp.com (rare) or file extension .exe like find.exe, robocopy.exe and where.exe (most external Windows commands).
There are on 64-bit Windows two system directories:
%SystemRoot%\System32 contains the 64-bit executables and dynamic linked libraries.
%SystemRoot%\SysWOW64 contains the 32-bit executables and dynamic linked libraries.
The Windows commands usually exist in both system directories as 32-bit and 64-bit executable on 64-bit Windows. But there are some executables existing only as 64-bit executables.
That is important to know if a 32-bit application starts on 64-bit Windows the Windows command processor cmd.exe to process a batch file because of the File System Redirector starts in this case 32-bit %SystemRoot%\SysWOW64\cmd.exe and all commands found via the environment variables PATHEXT and PATH in folder %SystemRoot%\System32 being by default the first folder path in system environment variable PATH are in real searched and executed from directory %SystemRoot%\SysWOW64 in the 32-bit execution environment on 64-bit Windows.
In a command prompt window are entered by a user a command usually with just its file name and so the Windows command processor has to search for a suitable file to execute. For details about this process read: What is the reason for "X is not recognized as an internal or external command, operable program or batch file"?
In a batch file can be used also only the file name of an external Windows command, but it is advisable to specify them with full qualified file names for the following reasons:
The batch file an external command specified like %SystemRoot%\System32\find.exe instead of just find are more fail safe and secure as the batch file does not depend on the environment variables PATHEXT and PATH defined outside of the batch file which are quite often modified by programs and users, especially the environment variable PATH.
The batch file is executed faster as the Windows command processor has not to search in file system for the executables.
The external Windows command where can be used to find out if a Windows command is an internal command or an external command. The execution of where set in a command prompt window results in output of an error message (hopefully) while where find results in output of file qualified file name of find (hopefully found first in %SystemRoot%\System32 with file extension .exe).
2. Define one or more environment variables for an executable
The command to define environment variables on Windows is SET. So this command must be used to define an environment variable which an executable evaluates on execution. An environment variable like tempvar can be defined as follows:
set tempvar=ABC
set "tempvar=ABC"
The second syntax is highly recommended, but requires enabled command extensions which is the case by default, but which does not mean that command extensions are always enabled. Please read my answer on: Why is no string output with 'echo %var%' after using 'set var = text' on command line? There is explained in full details why set "variable=value" is the recommended syntax.
The environment variable(s) evaluated by an executable can be defined on separate lines before execution of the executable or on same command line.
Definition of the environment variable and execution of the executable on separate command lines:
set "tempvar=ABC"
mybinary
Definition of the environment variable and execution of the executable on one command line:
set "tempvar=ABC" & mybinary
Single line with multiple commands using Windows batch file explains the operator & used in the single command line variant.
A batch file writer should avoid replacing one of the predefined Windows environment variables, except a predefined environment variable should be intentionally redefined in a batch script.
3. Define one or more environment variables just for an executable
Environment variables defined inside a batch file for an executable are still defined after the executable terminated itself. Therefore the environment variables are readable for any other executable started next by cmd.exe in same batch file or even after batch file processing finished depending on how cmd.exe was started for processing the batch file and the execution environment defined by the batch file itself.
The Windows command processor uses the Windows kernel library function CreateProcess to run an executable like any other Windows application capable running an executable. cmd.exe uses always NULL for the CreateProcess function parameter lpEnvironment which results in CreateProcess making a copy of the current environment variables list of cmd.exe for the executable started next.
The commands SETLOCAL and ENDLOCAL can be used in a batch file to define the environment variables evaluated by an executable just for the executable as the code below demonstrates.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem The two command lines above define completely the execution environment for the
rem batch file and so the batch file does not depend anymore on settings outside of
rem the batch file.
rem Define MyVariable1 and undefine MyVariable2
set "MyVariable1=Demo"
set "MyVariable2="
rem Verify that with an output of all environment variables of
rem which name starts with the string MyVariable with their values.
echo/
echo My variables defined in batch script at top:
echo/
set MyVariable
setlocal
set "MyVariable1=Other value for execuable"
set "MyVariable2=Defined also for executable"
echo/
echo My variables defined for mybinary:
echo/
set MyVariable
if exist mybinary mybinary
endlocal
echo/
echo My variables defined after execution of mybinary:
echo/
set MyVariable
endlocal
echo/
echo My variables as defined outside of the batch file:
echo/
set MyVariable
echo/
pause
I recommend to read this answer in addition to execution of setlocal /? and endlocal /? in a command prompt window to get full knowledge on what is done by these two commands in background.
4. Referencing files in batch file directory with full path
The Windows command processor cmd.exe can be executed for processing a batch file from within any directory which means the current working directory can be any directory. For that reason it is advisable to reference other files in the directory of the batch file with full path and with file extension instead of just the file name.
The usage of just mybinary results in searching in current working directory for a file with name mybinary having one of the file extensions as listed in environment variable PATHEXT if the environment variable NoDefaultCurrentDirectoryInExePath is not defined. In other words the successful execution of mybinary depends on execution environment settings defined outside of the batch file which is not good.
So it is advisable to use "%~dp0mybinary.exe" in the batch file to reference the executable with full qualified file name whereby %~dp0 references the drive and path (can be also a UNC path) of argument 0 which is the full folder path of the batch file always ending with a backslash and therefore concatenated without an additional backslash with file name mybinary.exe.
The usage of "%~dp0mybinary.exe" has following advantages regarding to execution of this executable:
It does not matter anymore how the environment variable PATHEXT is defined.
It does not matter anymore if the environment variable NoDefaultCurrentDirectoryInExePath is defined or not.
It does not matter anymore what is the current working directory on execution of the batch file as long as the executable is good coded and does not expect that the directory containing the executable is the working directory to access other files in that directory.
For completeness regarding usage of %~dp0 see also:
What is the reason for batch file path referenced with %~dp0 sometimes changes on changing directory?
I'm setting up an external hard drive with portable apps, and there are some CLI tools that I'd like to easily access.
I've done it before but I forgot how; basically, I have a file which sets the PATH variable for all the required CLI programs, and then allows me to use the command prompt normally.
How do I do this without having to run the file from another command prompt, but by double clicking the Batch file?
If your command line tools are in a folder named CLiTools next to the batch-file, then you could have 2 lines as the batch-file content.
#set "path=%path%;%~dp0CLiTools"
#cmd
You can double click the batch-file, it will open cmd with the modified setting of %path% and you can enter commands as you would normally do. If you have i.e. xyz.exe in the CLiTools folder, then you can type xyz at the current command prompt and will be recognized as a command.
The changed environment applies to the current child cmd session that inherited the environment.
%~dp0 is the drive and path of argument 0 which is the drive and path to the batch-file in this case.
What paths are searched by start? Is this path list queryable via WMI, the registry, WSH shell folder constants, .NET methods, or any other Windows API?
In a cmd console, the start command is sort of magic. Say your default web browser is Firefox. You also have Chrome and Opera installed, but they are neither set as default, nor associated with any file types, nor in your %PATH%. Considering the following example:
start "" opera https://stackoverflow.com
... why does that work?
The reason this matters is that, for all the start command's strengths, it is still perilous to retrieve an application's PID or window handle when that application was launched with start. In a cmd environment I'd prefer to use wmic process call create in a for /F loop to capture the PID. So is there a way I can teach wmic process call create to launch "opera" or "iexplore" or "outlook" or "winword" or other applications as start can without fully qualifying the drive:\\path\\to\\executable?
I thought I was on to a solution by scanning HKLM\Software\Clients recursively for shell\open\command, then adding each referenced location temporarily to %PATH%:
rem // temp add installed clients to %PATH% to make "call :display" behave like "start"
setlocal enabledelayedexpansion
for %%a in (HKLM HKCU) do for /f "tokens=2*" %%I in (
'REG QUERY %%a\Software\Clients /s /f command /k /ve ^| find "(Default)"'
) do if "%%~I"=="REG_EXPAND_SZ" (
if /i "%%~xJ"==".exe" (
if "!PATH:%%~J\..=!"=="!PATH!" path !PATH!;%%~J\..
) else for %%# in (%%J) do if /i "%%~x#"==".exe" (
if "!PATH:%%~#\..=!"=="!PATH!" path !PATH!;%%~#\..
)
) else (
if /i "%%~xJ"==".exe" (
if "!PATH:%%~dpJ=!"=="!PATH!" path !PATH!;%%~dpJ
) else (
for %%# in (%%J) do if /i "%%~x#"==".exe" (
if "!PATH:%%~dp#=!"=="!PATH!" path !PATH!;%%~dp#
)
)
)
endlocal & path %PATH%
That works reasonably well, but it still falls short of what start can access. For example:
start "" wordpad
works, whereas
wmic process call create "wordpad.exe"
fails.
I remember a discussion about this very topic either on DosTips.com or here on SO but I cannot find it. Hopefully somebody does so we can mark this question as a duplicate.
We know that the batch files will search the current directory and then the path variable to find an executable. The START command also searches the following Registry path
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
So you have a few options to find the executable.
You can use the WHERE command which searches the current directory and the directories in the PATH variable.
where notepad.exe
You can also just search the path variable with a FOR command.
for %%I in (notepad.exe) do echo %%~$PATH:I
And of course you can search the registry path as I mentioned above by doing a REG QUERY.
Edit2
This may be of interest regards to what you are doing (as opposed to why something works the way it does).
From a previous answer
Can I create shorthand names for use inside cmd?
See Doskey /?.
You could do this doskey cddesk=cd "%userprofile%\desktop"
Then type cddesk.
By putting all your macros in a batch file you can autoload. Of course
you could do this with batchfiles too.
From Technical Reference to the Windows 2000 Registry, Microsoft,
2000.
AutoRun HKCU\Software\Microsoft\Command Processor
Data type Range Default value
REG_SZ list of commands There is no default value for this entry.
Description
Contains commands which are executed each time you start Cmd.exe.
Also see this batchfile https://pastebin.com/A2qLw8r3 that list
special names.
In a command prompt start shell:desktop
Or in Start - Run (Winkey + R) just shell:desktop
Edit
I'll just add some caveats here.
This is about opening executables using Start command. A massive amount of rules apply to non executable files.
Not using Start CMD pre-processes the command line and sends a FQFN to CreateProcess - so it's CMD that searches the path not CreateProcess.
https://learn.microsoft.com/en-us/windows/desktop/shell/launch is the documentation for ShellExecute. Everything ends up calling CreateProcess https://learn.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessw. Plus CMD preprocessing is detailed here https://books.google.com.au/books/about/Windows_NT_Shell_Scripting.html?id=jrdQAAAAMAAJ&redir_esc=y. Also start /? provides documentation for all three ways to start files. A summary is here Command to run a .bat file
Some fun facts. CMD if it can't recognise a file extension will attempt to execute it. ShellExecute will content sniff unknown files https://learn.microsoft.com/en-us/windows/desktop/com/filetype-key.
quote from rojo: Thanks for the link dump. I'm struggling to understand how ShellExecute can save me from having to fully qualify path names though. That was my primary question. "To use ShellExecute or ShellExecuteEx, your application must specify the file or folder object that is to be acted on, and a verb that specifies the operation." It's the "specify the file or folder object" part that I'm asking about.
It uses the 6 steps under CreateProcess and if that turns up nothing it uses App Paths. If a verb is not specified the default verb is used (usually Open). See my answer here How do i automate the simulation of right clicking a file and selecting the first option using vbscript.
So it an intersection of 4 technologies. Compatibility with MSDos, changes made by IBM for OS/2 and updated by Microsoft for Windows 2000, the standard windows way of starting programs CreateProcess, and with Windows 95 we got shell functions which are based on OLE
quote from rojo: Snap! You're right! In PowerShell I was able to create a System.Diagnostics.Process object, set $ps.StartInfo.UseShellExecute = $true, and then start "wordpad" without any file extension or fully qualified path. Setting the .UseShellExecute property to $false caused the execution to fail. How interesting!
I'm writing a CMD script to execute a Java program. The program requires at least one argument so I created an if else block to check that argument one %1 is not blank. When I run the script without providing argument one I get the expected you must provide an argument to run. But when I do provide the argument the program does not execute. Additionally if I don't have #echo off set, the entire script prints out as text in the window when I do provide the proper argument.
Here's the full script:
set APP_HOME=C:\Temp\Hammer
rem Set APP_HOME to the place where you installed Hammer
if ["%1"] == [""] (
echo you must provide a python script to run
) else (
java -cp %APP_HOME%\lib\jython.jar;%APP_HOME%\lib\hammer.jar;%CLASSPATH% bridenstine.hammer.main.Main %1 %2
)
I think this is a problem with the script and not the program itself because when I run the line that's in the else block by itself without the script,
java -cp C:\Temp\Hammer\lib\jython.jar;C:\Temp\Hammer\lib\hammer.jar;%CLASSPATH% bridenstine.hammer.main.Main argument1
The program executes normally. I've been looking at example scripts and cross referencing this site for CMD files and what I have seems to be valid. Do I have a syntax error?
Update:
I'm running the script like so,
cd C:\Temp\Hammer
bin\ProgramScript.cmd argument1
Update 2:
Someone pointed out that when they run this script they get an error message saying Java is not recognized as an internal or external command (the expected message when Java is not set on their PATH) But they made a good point that this means the script is in fact getting inside the else block. I then pointed out the following,
After I run the script with a valid argument it prints out the entire script as text on the command prompt. I am then able to mark the line from inside the else statement (that was printed), copy it, paste it, and it runs the program fine. So the Java command seems to be valid. But you make a good point that the script is obviously getting inside the else block...something is still going wrong here and it doesn't seem to be the program.
Note:
If relevant I am using Windows 8.1 and I am using the standard command prompt, not one that has administrative privileges (the results remained the same regardless of using a command prompt with administrative privileges).
I suggest to use
set APP_HOME=C:\Temp\Hammer
rem Set APP_HOME to the place where you installed Hammer.
if "%~1"=="" (
echo You must provide a Python script to run.
pause
) else (
java.exe -cp "%APP_HOME%\lib\jython.jar;%APP_HOME%\lib\hammer.jar;%CLASSPATH%" bridenstine.hammer.main.Main %*
)
It is always better to enclose an argument string in double quotes if it contains environment variables like CLASSPATH which might have a string value containing 1 or more spaces.
%* is expanded by all arguments passed to the batch file as argument, i.e. %1 %2 ...
It is best to always specify an application like java with full path and file extension as otherwise Windows has to search for a file with a file extension as defined in environment variable PATHEXT in current working directory and all directories defined in environment variable PATH. At least the file extension should be in the batch file if the program files directory of the application varies.
I can only offer a suggestion; I've not tried this.
I would try escaping each ; with a caret ^ thus:
java -cp %APP_HOME%\lib\jython.jar^;%APP_HOME%\lib\hammer.jar^;%CLASSPATH% bridenstine.hammer.main.Main %1 %2
(But I'll admit it's clutching at straws...)
You are missing you she-bang at the top of the script
#!/bin/bash
I have a C# program that calculates a date. I want to set an environment variable datayyyymmdd to be read outside the program to be expaned into a filename that I need to look for with the Dos code.
Can someone please help me with c# code sample or any link .
Thanks
This is not a trivial task as each process has its own environment variables table, especially if the environment variable should not be set permanently for all running processes.
The solution is quite simple.
In your C# application create a batch file with the line:
set YourVar=datayyyymmdd
The C# application could create this batch file with name "SetYourVar.bat" in directory for temporary files. The path of the temporary files directory can be get within the C# application from environment variable TEMP.
Your batch file contains the lines:
#echo off
"Full\Name\Of\C#\ConsoleApplication.exe" "parameters for this application"
if exist "%TEMP%\SetYourVar.bat" (
call "%TEMP%\SetYourVar.bat"
del "%TEMP%\SetYourVar.bat" >nul
)
"Full\Name\Of\Other\DOS\Application.exe"
So instead of trying to set the environment variables to use in other applications directly within the C# console application, write the lines to set them into a batch file called next by the batch file which started the C# console application and next deletes this temporary batch file just used to set environment variables.
Modify your C# program so it just display in the screen the value of the variable instead of set the variable itself. Then, execute the .exe from the Batch file this way:
for /F "delims=" %%a in ('abc.exe "any parameter"') do set datayyyymmdd=%%a