-1

So I'm trying to do a archive using delphi and ShellExecuteEx my code is :

 Result := False;
  DecodeDate(now,y,m,d);
  NumeFisier := dir+'\Export_'+IntToStr(y)+'.'+IntToStr(m)+'.'+IntToStr(d)+'.zip';
  FillChar(exInfo, SizeOf(exInfo), 0);
  with exInfo do
   begin
    cbSize := SizeOf(exInfo);
    fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
    Wnd := GetActiveWindow();
    exInfo.lpVerb := nil;
    exInfo.lpFile  := PAnsiChar('C:\Windows\System32\cmd.exe');
   exInfo.lpParameters := PAnsiChar('C:\Program Files\7-Zip\7z.exe ' +'a ' + NumeFisier + ' ' + dir);
    nShow := SW_SHOWNORMAL;
   end;
   if ShellExecuteEx(@exInfo) then
    Ph := exInfo.hProcess
    else
     begin
     ShowMessage(SysErrorMessage(GetLastError));
     Result := true;
     exit;
    end;
   while WaitForSingleObject(exInfo.hProcess, 50) <> WAIT_OBJECT_0 do
     Application.ProcessMessages;
   CloseHandle(Ph);

  Result := true;

For some reason this only opens the Command Prompt and doesn't execute the archiving. How can I make it execute the 7z.exe file.

I tried with ShellExecute and it works great, but I have to check then the process is finished, so I'm stuck with ShellExecuteEx

CiucaS
  • 2,010
  • 5
  • 36
  • 63

1 Answers1

3

There's no need to involve cmd.exe. That's the command interpreter. You want to execute a different executable so do that directly.

You don't want to use ShellExecuteEx since that has far more generality than you need. All that ShellExecuteEx is doing here is calling CreateProcess. You should do that directly and avoid the middle man. What's more, calling CreateProcess allows you to hide the console window easily. Pass CREATE_NO_WINDOW to achieve that.

Finally, there are better ways to wait than your code. Using MsgWaitForMultipleObjects allows you to avoid polling. And putting this code into a thread would allow you to avoid calls to Application.ProcessMessages.

procedure WaitUntilSignaled(Handle: THandle; ProcessMessages: Boolean);
var
  retval: DWORD;
begin
  if ProcessMessages then begin
    Application.ProcessMessages;//in case there are messages already in the queue
    while True do begin
      retval := MsgWaitForMultipleObjects(1, Handle, False, INFINITE, QS_ALLEVENTS);
      case retval of
      WAIT_OBJECT_0,WAIT_ABANDONED_0:
        break;
      WAIT_OBJECT_0+1:
        Application.ProcessMessages;
      WAIT_FAILED:
        RaiseLastOSError;
      end;
    end;
  end else begin
    Win32Check(WaitForSingleObject(Handle, INFINITE)<>WAIT_FAILED);
  end;
end;

procedure ExecuteProcess(
  const ExecutablePath: string;
  const Arguments: string;
  const CurrentDirectory: string;
  const Wait: Boolean;
  const CreationFlags: DWORD
);
var
  si: TStartupInfo;
  pi: TProcessInformation;
  MyCurrentDirectory: PChar;
begin
  ZeroMemory(@si, SizeOf(si));
  si.cb := SizeOf(si);

  if CurrentDirectory <> '' then begin
    MyCurrentDirectory := PChar(CurrentDirectory);
  end else begin
    MyCurrentDirectory := nil;
  end;

  Win32Check(CreateProcess(
    nil,
    PChar('"' + ExecutablePath + '" ' + Arguments),
    nil,
    nil,
    False,
    CreationFlags,
    nil,
    MyCurrentDirectory,
    si,
    pi
  ));
  try
    if Wait then begin
      WaitUntilSignaled(pi.hProcess, True);
    end;
  finally
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
  end;
end;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I've tried : NumeFisier := ChangeFileExt('test.Bak', '.7z'); if FileExists ( NumeFisier ) then DeleteFile(NumeFisier); ExecuteProcess('"C:\Program Files\7-Zip\7za.exe" ' , ' a ' + NumeFisier + ' ' + 'D:\TestBK\*.bak' ,'',True,0); and I get the error "Invalid handler" – CiucaS Jan 07 '15 at 12:48
  • That's a little hard to understand. You use the code in the answer verbatim I presume? – David Heffernan Jan 07 '15 at 13:23
  • The error is in "WaitUntilSignaled" at RaiseLastOSError; – CiucaS Jan 07 '15 at 13:25
  • 1
    You aren't using the code in my answer. If you were then you'd get invalid parameter in the call to `CreateProcess`. The problem is that you are double quoting the file name. Should be: `ExecuteProcess('C:\Program Files\7-Zip\7za.exe', 'a ' + NumeFisier + ' ' + 'D:\TestBK*.bak', '', True, 0)` – David Heffernan Jan 07 '15 at 13:38
  • Thank you, I've used "" as in Command Promt I've got error because of the space between Program and Files – CiucaS Jan 07 '15 at 13:48
  • If you would use the code in my answer then you would not have that problem. The code in my answer already adds quotes inside `ExecuteProcess`. – David Heffernan Jan 07 '15 at 13:49
  • I've used that code, but didn't see the Quotes, so that's why I've got the error :). – CiucaS Jan 07 '15 at 13:51
  • as a last question if in WaitUntilSignaled I change the Application.ProcessMessages; with ServiceThread.ProcessRequests; will it work for a service? – CiucaS Jan 07 '15 at 13:53
  • Perhaps. Not really my area of expertise. I say that a better approach would be to run the code in a thread and get it to signal upon completion. – David Heffernan Jan 07 '15 at 14:09