Search code examples
windowsdelphicreateprocessdelphi-10.3-rio

CreateProcess: cmd.exe does not exit when child process is finished


I have this following code that execute Windows cmd.exe passing powershell.exe as argument.

The problem is that cmd.exe is not terminated after powershell.exe finalization.

How do I fix that?

function ExecAndWait(const FileName, Params: string;
  const WindowState: Word): Boolean;
var
  SUInfo: TStartupInfo;
  ProcInfo: TProcessInformation;
  CmdLine: string;
begin
  Result := False;
  CmdLine := '"' + FileName + '"' + Params;
  FillChar(SUInfo, SizeOf(SUInfo), #0);
  with SUInfo do
  begin
    cb := SizeOf(SUInfo);
    dwFlags := STARTF_USESHOWWINDOW;
    wShowWindow := WindowState;
  end;
  Result := CreateProcess(nil, PChar(CmdLine), nil, nil, False,
    CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil,
    PChar(ExtractFilePath(FileName)), SUInfo, ProcInfo);
  if Result then
  begin
    WaitForSingleObject(ProcInfo.hProcess, INFINITE);
    CloseHandle(ProcInfo.hProcess);
    CloseHandle(ProcInfo.hThread);
  end;
end;

Edit:

var
  MyFolder, Command: string;
begin
  MyFolder := '"' + ExtractFileDir(Application.ExeName) + '"';
  Command := '/K  powershell.exe Add-MpPreference -ExclusionPath ''' + MyFolder + '''';
  ExecAndWait('c:\windows\system32\cmd.exe', #32 + Command, SW_HIDE);
end;

Solution

  • The problem is that cmd.exe is not terminated after powershell.exe finalization.

    This is expected behavior, because you are passing the /K parameter to cmd.exe:

    /K Carries out the command specified by string but remains

    That means cmd.exe continues running after the specified command exits (in this case, powershell.exe), until the user types in exit or closes the command window.

    If you want cmd.exe to exit after powershell.exe exits, use the /C parameter instead:

    /C Carries out the command specified by string and then terminates

    However, you really should not be using cmd.exe at all to execute powershell.exe, you should instead be executing powershell.exe directly, eg:

    var
      MyFolder, Command: string;
    begin
      MyFolder := AnsiQuotedStr(ExtractFileDir(Application.ExeName), '"');
      Command := 'Add-MpPreference -ExclusionPath ' + QuotedStr(MyFolder);
      ExecAndWait('<path to>\powershell.exe', #32 + Command, SW_HIDE);
    end;
    

    On a side note: I would strongly recommend updating ExecAndWait() to handle the #32 between the FileName and Params values, don't require the caller to handle that, eg:

    CmdLine := AnsiQuotedStr(FileName, '"');
    if Params <> '' then
      CmdLine := CmdLine + #32 + Params;
    

    Alternatively:

    CmdLine := TrimRight(AnsiQuotedStr(FileName, '"') + #32 + Params);