39

Is it possible to get an output of an Exec'ed executable?

I want to show the user an info query page, but show the default value of MAC address in the input box. Is there any other way to achieve this?

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992

2 Answers2

45

Yes, use redirection of the standard output to a file:

[Code]

function NextButtonClick(CurPage: Integer): Boolean;
var
  TmpFileName, ExecStdout: string;
  ResultCode: integer;
begin
  if CurPage = wpWelcome then begin
    TmpFileName := ExpandConstant('{tmp}') + '\ipconfig_results.txt';
    Exec('cmd.exe', '/C ipconfig /ALL > "' + TmpFileName + '"', '', SW_HIDE,
      ewWaitUntilTerminated, ResultCode);
    if LoadStringFromFile(TmpFileName, ExecStdout) then begin
      MsgBox(ExecStdout, mbInformation, MB_OK);
      { do something with contents of file... }
    end;
    DeleteFile(TmpFileName);
  end;
  Result := True;
end;

Note that there may be more than one network adapter, and consequently several MAC addresses to choose from.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
mghie
  • 32,028
  • 6
  • 87
  • 129
  • 5
    Note that rather than hard-coding "cmd.exe" it's better practice to use `ExpandConstant('{cmd}')`. (Of course, it's better still to use proper APIs instead of trying to capture the output of console commands, as the latter may change without notice, since it's intended for humans.) – Miral Jul 14 '12 at 04:25
  • 5
    For clarification: you need to run your program through the command prompt to get redirection. I initially looked at this answer and was confused why this wasn't working for me, the reason was because I didn't realise redirection is a function of the command prompt rather than windows, so you need to Exec on cmd.exe /c – phillip voyle Jan 20 '15 at 03:19
  • 3
    for unicode installation, must use: `var ExecStdout: AnsiString;` – papo Apr 16 '17 at 04:47
  • You can't directly use this technique with powershell because it generates utf16 filename with BOM. InnoSetup does not seem to provide any conversion function. – Niki Jun 25 '18 at 14:24
  • if the file already exists, will this overwrite it? – Smith Aug 09 '21 at 19:56
  • @Smith Yes. `>>` to append. https://www.lifewire.com/how-to-redirect-command-output-to-a-file-2618084 – Grault Nov 21 '22 at 17:22
26

I had to do the same (execute command line calls and get the result) and came up with a more general solution.

It also fixes strange bugs if quoted paths are used in the actual calls by using the /S flag for cmd.exe.

// Exec with output stored in result.
// ResultString will only be altered if True is returned.
function ExecWithResult(
  Filename, Params, WorkingDir: String; ShowCmd: Integer;
  Wait: TExecWait; var ResultCode: Integer; var ResultString: String): Boolean;
var
  TempFilename: String;
  Command: String;
  ResultStringAnsi: AnsiString;
begin
  TempFilename := ExpandConstant('{tmp}\~execwithresult.txt');
  // Exec via cmd and redirect output to file.
  // Must use special string-behavior to work.
  Command :=
    Format('"%s" /S /C ""%s" %s > "%s""', [
      ExpandConstant('{cmd}'), Filename, Params, TempFilename]);
  Result :=
    Exec(ExpandConstant('{cmd}'), Command, WorkingDir, ShowCmd, Wait, ResultCode);
  if not Result then
    Exit;
  LoadStringFromFile(TempFilename, ResultStringAnsi); // Cannot fail
  // See https://stackoverflow.com/q/20912510/850848
  ResultString := ResultStringAnsi;
  DeleteFile(TempFilename);
  // Remove new-line at the end
  if (Length(ResultString) >= 2) and
     (ResultString[Length(ResultString) - 1] = #13) and
     (ResultString[Length(ResultString)] = #10) then
    Delete(ResultString, Length(ResultString) - 1, 2);
end;

Usage:

Success :=
  ExecWithResult('ipconfig', '/all', '', SW_HIDE, ewWaitUntilTerminated,
    ResultCode, ExecStdout) and
  (ResultCode = 0);

The result can also be loaded into a TStringList object to get all lines:

Lines := TStringList.Create;
Lines.Text := ExecStdout;
// ... some code ...
Lines.Free;
Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
Tobias81
  • 1,820
  • 1
  • 16
  • 17
  • One minor issue in your Usage example: if the Exec succeeds but ResultCode <> 0, ResultCode will be filled in with the exit code of the called program, not with a windows error, so SysErrorMessage will not get the correct message. Also, a non-zero exit code can sometimes be okay. I'd recommend testing ResultCode separately depending on the context. More info on this confusion: https://github.com/jrsoftware/issrc/issues/190. – Mark Berry Jan 05 '16 at 23:29
  • Yes the exception message in the example relies on windows exit-codes => https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382.aspx – Tobias81 Jan 07 '16 at 09:39
  • 1
    That is the reference for MS *error* codes returned by [GetLastError](https://msdn.microsoft.com/en-us/library/windows/desktop/ms679360.aspx). *Exit* codes come from [GetExitCodeProcess](https://msdn.microsoft.com/en-us/library/ms683189%28v=vs.85%29.aspx) and are not necessarily the same thing. For example, `ipconfig /?` is not an error but returns exit code 1. And if you use ewNoWait or ewWaitUntilIdle, the ResultCode will always be 259, which is a reserved code from the GetExitCodeProcess function meaning STILL_ACTIVE. – Mark Berry Jan 07 '16 at 19:39
  • Ok I removed the Exception part in the example. This depends on what is actually done anyway. Just wanted to show that the result is passed to InnoSetup and can be used. – Tobias81 Jan 13 '16 at 15:53
  • 1
    What is the `or (ResultCode <> 0)`? Shouldn't it be `and (ResultCode = 0)`? – Martin Prikryl Apr 05 '17 at 07:20
  • I had a problem with a JAVA_HOME path that contained spaces and I was not able to make it work using the solution proposed in the accepted answer. However, thank to this solution that avoids the bugs when quotes are in the path, I was successfully able to get the version of Java referenced by JAVA_HOME. The only thing I had to add was the redirection of the Standard Error `2>&1` as `java -version` makes use of it. – Luca Cremonesi Jul 18 '19 at 17:14