How do I Launch OpenFiles.exe from Delphi XE2 ShellExecute? - delphi

I need to launch MS Window's OpenFiles.exe from a Delphi XE2 application to export currently opened files to a text file. The normal cmd.exe syntax is something like:
Openfiles.exe /query /s 127.0.0.1 /nh >c:\OpenFilesExport.txt
Using the following code returns a successful exit code but the export file is not generated:
var
exInfo: TShellExecuteInfo;
exitcode: DWORD;
begin
FillChar(exInfo, Sizeof(exInfo), 0);
with exInfo do
begin
cbSize := Sizeof(exInfo);
fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
lpVerb := 'open';
lpFile := Pchar('Openfiles.exe');
lpParameters := PChar('/query /s 127.0.0.1 /nh >c:\OpenFilesOutput.txt');
nShow := SW_SHOWNORMAL
end;
if ShellExecuteEx(#exInfo) then
begin
while GetExitCodeProcess(exInfo.hProcess, exitcode)
and (exitcode = STILL_ACTIVE) do
Application.ProcessMessages();
CloseHandle(exInfo.hProcess);
end
else
ShowMessage(SysErrorMessage(GetLastError));
I've also tried putting the cmd.exe syntax in a bat file and launching that from shellexecute and it DOES generate the file but there is no content. Running the same bat file from explorer generates the file as expected.
How can I launch Openfiles.exe successfully from ShellExecute?

Your problem is the redirect, >, which only makes sense if you have a command interpreter. And in your code you do not. You have two options:
Call ShellExecuteEx passing a command interpreter to do the work.
Use CreateProcess to execute the other process, but pass a handle to a file as the stdout handle for the new process.
For the command interpreter option you would have a command line like this:
cmd /c Openfiles.exe /query /s 127.0.0.1 /nh >c:\OpenFilesExport.txt
The code might be like so:
FillChar(exInfo, Sizeof(exInfo), 0);
with exInfo do
begin
cbSize := Sizeof(exInfo);
fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
lpFile := 'cmd.exe';
lpParameters := '/c Openfiles.exe /query /s 127.0.0.1 /nh >c:\OpenFilesExport.txt';
nShow := SW_SHOWNORMAL;
end;
For the CreateProcess option you'll need to create the file with a call to CreateFile, and pass that handle as stdout of the new process. You'll need to make sure that the file handle is inheritable. And finally you'll need to wait on the process so that you can close the file handle.
Regarding your current code, your wait is not very pleasant. It's a busy wait that needlessly consumes CPU. You should use a blocking wait on the process handle.

Related

Why does my third-party FTP program hang when started from my service?

I have the following function to create a process and wait for its execution:
procedure TdmTransfer.ExecNewProcess(ProgramName, aParams: string);
var
StartInfo: TStartupInfo;
ProcInfo: TProcessInformation;
CreateOK: Boolean;
begin
FillChar(StartInfo, SizeOf(TStartupInfo), #0);
FillChar(ProcInfo, SizeOf(TProcessInformation), #0);
StartInfo.cb := SizeOf(TStartupInfo);
CreateOK := CreateProcess(nil,
PChar(ProgramName + ' ' + aParams),
nil,
nil,
False,
CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS,
nil,
nil,
StartInfo,
ProcInfo);
if CreateOK then
WaitForSingleObject(ProcInfo.hProcess, INFINITE);
CloseHandle(ProcInfo.hProcess);
end;
I'm executing psftp.exe -b "download.cmd" -pw "password" user#domain.com which runs fine if I call it from a desktop application.
When I run the code from within a service application the process gets created, but then psftp.exe doesn't finish and my service waits indefinitely.
The file "download.cmd" looks like this:
lcd download
cd directory
mget ./*.XML
quit
What can I do to make it to run inside my service application?

CreateProcess : escape spaces in the application path

We need to execute ffmpeg in a command window in my delphi application.
We found the solution to protect the path with the function "ExtractShortPathName".
But on some computers we can't get the 8.3 path (HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\NtfsDisable8dot3NameCreation is 2) and we want to find another way to escape the spaces.
Here is the code :
sParameters := '"C:\Users\[...]\input.wav" -r 12.03 -f image2 -i "C:\Users\[...]\delphilm%d.png" -vf "scale=1024:704" -ab 96k -r 24 -b 2000k -pass 1 -vcodec libx264 -fpre "C:\[...]\libx264-normal.ffpreset" "C:\Users\[...]\export.mp4"';
sCommand := 'C:\Program Files\My application\utils\bin\ffmpeg.exe';
Handle := CreateProcess(nil, PChar('cmd.exe /C '+ProtectPath(sCommand)+' '+sParameters),nil, nil, True, 0, nil, nil, SI, PI);
With the ProtectPath function :
function ProtectPath(sCommand:Widestring):Widestring;
begin
Result := sCommand;
// get the 8.3 path
Result := ExtractShortPathName(sCommand);
// if 8.3 path is not accessible
if(Pos(' ', Result)>0)then begin
//Result := '"'+sCommand+'"'; --> do not work
//Result := StrReplace(sCommand, ' ','" "'); --> do not work
//Result := StrReplace(sCommand, ' ','^ '); --> do not work
//Result := StrReplace(sCommand, ' ','\ '); --> do not work
//Result := StrReplace(sCommand, ' ','\\ '); --> do not work
//Result := StrReplace(sCommand, ' ','/ '); --> do not work
//Result := StrReplace(sCommand, ' ','// '); --> do not work
end;
end;
Any ideas ?
You do not need to retrieve the 8.3 filename. All you have to do is wrap a long path with a single pair of quotation marks if it contains any space characters in it (like you are already doing with some of your FFMPEG parameters). Then, get rid of cmd.exe altogether and just call ffmpeg.exe directly instead.
sCommand := '"C:\Program Files\My application\utils\bin\ffmpeg.exe"';
sParameters := '"C:\Users\[...]\input.wav" -r 12.03 -f image2 -i "C:\Users\[...]\delphilm%d.png" -vf "scale=1024:704" -ab 96k -r 24 -b 2000k -pass 1 -vcodec libx264 -fpre "C:\[...]\libx264-normal.ffpreset" "C:\Users\[...]\export.mp4"';
Handle := CreateProcess(nil, PChar(sCommand + ' ' + sParameters), nil, nil, True, 0, nil, nil, SI, PI);
If you want to do the quoting dynamically, use (Ansi)QuotedStr() for that, eg:
function ProtectParam(sParam: String): String;
begin
if LastDelimiter(' "', sParam) <> 0 then
Result := QuotedStr(sParam)
else
Result := sParam;
end;
FFMPEG := 'C:\Program Files\My application\utils\bin\ffmpeg.exe';
InputFile := 'C:\Users\[...]\input.wav';
PngFile := 'C:\Users\[...]\delphilm%d.png';
PresetFile := 'C:\[...]\libx264-normal.ffpreset';
ExportFile := 'C:\Users\[...]\export.mp4';
sCommand := ProtectParam(FFMPEG) + ' ' + ProtectParam(InputFile) + ' -r 12.03 -f image2 -i ' + ProtectParam(PngFile) + ' -vf "scale=1024:704" -ab 96k -r 24 -b 2000k -pass 1 -vcodec libx264 -fpre ' + ProtectParam(PresetFile) + ' ' + ProtectParam(ExportFile);
Handle := CreateProcess(nil, PChar(sCommand), nil, nil, True, 0, nil, nil, SI, PI);
I don't see any real reason to use cmd.exe here. It's just adding an extra layer of complexity that burns you. You are asking cmd.exe to call CreateProcess to start ffmpeg, so why not do it directly?
That said, a cheap and cheerful way to side-step the problem is to make use of the working directory. Pass 'C:\Program Files\My application\utils\bin' for the working directory of the new process, and then PChar('cmd.exe /C ffmpeg.exe '+sParameters) is all you need.

Mysqldump via ShellExecute not working

I am trying to backup my mysql database from delphi by using ShellExecute to run mysqldump.
This is how I am using mysqldump:
MySqlDump.exe -u root -p[password] [databasename] > TheOutputFile.Sql
This works from command prompt.
I call this command line using ShellExecute:
ShellExecute(
0,
nil,
'cmd.exe',
'C:\SmartRetail\Test System\Periodic database backup\MySqlDump.exe -u root -p123 smartretailprogramdata > TheOutputFile2.Sql',
nil,
SW_SHOW
);
This does not work, instead it opens command prompt.
What am i doing wrong?
It opens the command interpreter because that's what cmd.exe is. You need to specify /C to tell the cmd process to close after it has interpreted your commands.
On top of that you need to take care of the working directory. Unless the .sql file is in the working directory, the dump process will not find it. And the working directory is inherited from the parent process since you did not specify it. You may need to specify the working directory.
Finally, ShellExecute is really the wrong solution here. You are only using it to get the stdout redirection. But that should really be done explicitly. Use CreateProcess instead.
Of curse you can :
var
BatFile : TStringList;
str: string;
const
EXEC = 'MySqlDump.exe -u root -p[password] [databasename] > TheOutputFile.Sql' ;
begin
BatFile := TStringList.Create;
BatFile.Text := EXEC ;
str := ExtractFilePath(Application.ExeName) + 's.bat';
BatFile.SaveToFile(str);
BatFile.Free;
ShellExecuteW(Handle, 'open', PWideChar(str), nil, nil, SW_HIDE);
Sleep(200);
ShowMessage('Done.');
DeleteFile(str);
end;

How to use Delphi to execute a .bat file properly

I am running a .bat file from delphi(2010).
procedure TForm1.Button2Click(Sender: TObject);
var sCmd: String;
Begin
sCmd := Pwidechar('b4a_c2dm.bat' +' ' +'send ' + Trim(Edit1.Text)+' ' + Trim(edit2.Text ));
ShellExecute(0, 'open', 'b4a_c2dm.bat', PChar(sCmd), nil, SW_SHOWMAXIMIZED);
end;
This opens the cmd.exe and passes the correct string in the cmd.exe , BUT
Some how the line in the .bat file (java -cp b4a_c2dm.jar anywheresoftware.b4a.c2dm.C2DM %*) is showing up in the cmd.exe window and not letting the .bat file do its job.
Can someone help me with this.
In order to execute a batch file, the program to be called is 'cmd' and its parameter should be the name of the batch file.
Regarding your program,
ShellExecute (application.handle, 'open', 'cmd', PChar(sCmd), nil, SW_MAXIMIZE)

Send Parameter To CMD

How can i send parameters to CMD? for example send a path and start it from that path? How can i execute CMD commands?
Thanks
To start cmd.exe and immediately execute a command, use the /K flag:
procedure TForm1.FormCreate(Sender: TObject);
begin
ShellExecute(Handle, nil, 'cmd.exe', '/K cd C:\WINDOWS', nil, SW_SHOWNORMAL);
end;
To run a command in cmd.exe and then immediately close the console window, use the /C flag:
procedure TForm1.FormCreate(Sender: TObject);
begin
ShellExecute(Handle, nil, 'cmd.exe', '/C del myfile.txt', nil, SW_SHOWNORMAL);
end;
You can also use the Process class - see an example below
AProcess := TProcess.Create(nil); // Create process
AProcess.Executable := 'cmd'; // Executable to run
AProcess.Parameters.Add('/T:B0'); // Set background colour
AProcess.Parameters.Add('/K'); // Keep open
AProcess.Parameters.Add('title'); // A title for cmd
AProcess.Parameters.Add('My Console'); // Title
AProcess.Parameters.Add('&&'); // Start a new command line
AProcess.Parameters.Add('cd'); // Change directory
AProcess.Parameters.Add('D:\X\'); // Path to Folder
{Set environment variable}
AProcess.Parameters.Add('&&'); // Start a new command line
AProcess.Parameters.Add('HOME='+MYSQL_DIR); // Set env example
AProcess.Parameters.Add('&&'); // Start a new command line
AProcess.Parameters.Add('mysql.exe'); // run mysql.exe
AProcess.Parameters.Add('--host=' + VAR_HOST); // Parameter server
AProcess.Parameters.Add('--port=' + VAR_PORT); // Parameter mysql server port
AProcess.Execute; // execute detatched process command window remains visible
AProcess.Free; // free memory

Resources