Search code examples
delphibatch-filecommand-linecmdwinrar

Why Delphi App can't run a *.bat file and make it work?


The contents for the *.bat file:

cd "C:\Program Files\WinRAR"
WinRAR a -r -ep1 "D:\Temp\250815\GiftCard-250815-1to10.zip" "D:\Temp\250815\word"

Basically, this file is telling WinRAR to compress the folder "word" into "GiftCard-250815-1to10.zip".

The delphi line of code:

StartProcess('cmd.exe', '/C ' + 'D:\Temp\GenCards.bat', true, true);

I also tried:

ShellExecute(Handle, 'runas', PChar('C:\Program Files\WinRAR.exe a -r -ep1 "D:\Temp\250815\GiftCard-250815-1to10.zip" "D:\Temp\250815\word"'), nil, nil, SW_SHOWNORMAL);

I also tried:

ShellExecute(Handle, 'runas', 'cmd.exe', PChar('C:\Program Files\WinRAR.exe a -r -ep1 "D:\Temp\250815\GiftCard-250815-1to10.zip" "D:\Temp\250815\word"', nil, SW_SHOWNORMAL);

The fact is, I'm really lost here since, if I go to the folder and double click the *.bat file, it WORKS PERFECTLY!

But, if I execute the bat from my code, it won't work.

* EDIT *

This is StartProcess function:

function TgenerateForm.StartProcess(ExeName: string; CmdLineArgs: string = ''; ShowWindow: boolean = True; WaitForFinish: boolean = False): integer;
var
  StartInfo: TStartupInfo;
  ProcInfo: TProcessInformation;
begin
  //Simple wrapper for the CreateProcess command
  //returns the process id of the started process.
  FillChar(StartInfo,SizeOf(TStartupInfo),#0);
  FillChar(ProcInfo,SizeOf(TProcessInformation),#0);
  StartInfo.cb := SizeOf(TStartupInfo);

  if not(ShowWindow) then begin
    StartInfo.dwFlags := STARTF_USESHOWWINDOW;
    StartInfo.wShowWindow := SW_HIDE;
  end;

  CreateProcess(nil,PChar(ExeName + ' ' + CmdLineArgs),nil,nil,False,
    CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS,nil,nil,StartInfo,
    ProcInfo);

  Result := ProcInfo.dwProcessId;

  if WaitForFinish then begin
    WaitForSingleObject(ProcInfo.hProcess,Infinite);
  end;

  //close process & thread handles
  CloseHandle(ProcInfo.hProcess);
  CloseHandle(ProcInfo.hThread);
end;

Please, do not be unpolite and copy & paste a url. I searched all the stackoverflow and tried all "how-tos ... bat files".


Solution

  • Your implementation should work. It successfully runs a batch file in a test case I put together using your StartProcess() method as written. This suggests there is some other issue affecting the operation of the code.

    A couple of pointers may help.

    From the documentation for CreateProcess():

    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.

    lpApplicationName is the initial parameter to CreateProcess() and is specified in your code as NIL. You are passing the application name in the command line params. This is normally legitimate and in my test of your code does still work. However, it is vulnerable to spaces in long filename paths to the application module. Passing the application module filename and path explicitly in the application module parameter avoids this.

    This is easily achieved by changing your CreateProcess call to:

    CreateProcess(PChar(EXEName), PChar(CmdLineArgs), ....);
    

    Expanding on David's comment, the Unicode version of CreateProcess may modify the contents of the command line parameter, so if you are using a Unicode version of Delphi, or wish to write code that will safely migrate to a Unicode compiler as-is, then you should ensure that you pass a reference to a unique, writable string. To ensure that you get a unique copy, you should first store the args in a local variable:

    var
      cmdline: String;
    
    begin 
      ..
      cmdline := CmdLineArgs;
      UniqueString(cmdline);
    
      ..
    
      CreateProcess(PChar(EXEName), PChar(cmdline), ....);
    
      ..
    end;
    

    Also, I would suggest that a batch file a sufficiently specialised case with such specific requirements that a specific alternate implementation wrapping StartProcess may be advisable, called StartBatch(), for example:

    procedure StartBatch(const aFilename: String, ... );
    begin
      StartProcess('cmd.exe', '/C ' + aFilename, ... );
    end;
    

    On a general point, repeating default parameter values in the implementation of a method is potentially confusing. They are ignored by the compiler and any default parameter values in the declaration take precedence anyway. As a result, if the parameter default values declared in the declaration are changed in the future, the values stated in the implementation then become misleading.