IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Téléchargé 3 fois
Vote des utilisateurs
1 
0 
Détails
Licence : Non renseignée
Mise en ligne le 29 octobre 2025
Plate-forme : Windows
Langue : Français
Référencé dans
Navigation

Exécuter des scripts batch ou des commandes système depuis une application

Cette unité fournit la classe utilitaire TCmdPipeExecutor qui permet de lancer cmd.exe en arrière-plan depuis une application VCL, d’envoyer des commandes via stdin, de capturer stdout/stderr à travers des pipes anonymes, et de récupérer le code de sortie du processus.
Nos ressources disponibles
Exemple d’utilisation simple (dans une Form VCL)
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
uses
  UnitCmdPipeExecutor, System.Classes, Vcl.StdCtrls;

procedure TForm1.ButtonRunClick(Sender: TObject);
var
  Executor: TCmdPipeExecutor;
  Commands: TStringList;
  Output: string;
  ExitCode: DWORD;
begin
  Executor := TCmdPipeExecutor.Create;
  Commands := TStringList.Create;
  try
    // Exemple : liste de commandes batch
    Commands.Add('echo Début du script');
    Commands.Add('dir C:\Windows');        // commande montrant la sortie
    Commands.Add('echo Fin du script');

    // Exécute via cmd.exe, fenêtre cachée, encodage OEM (UseUtf8 = False), timeout 10s
    if Executor.ExecuteCmdAndCapture(Commands, Output, ExitCode, True, False, 10000) then
    begin
      ShowMessage(Format('Sortie (%d octets) - ExitCode=%d'#13#10'%s', [Length(Output), ExitCode, Output]));
    end
    else
      ShowMessage('Erreur: impossible de lancer cmd.exe.');
  finally
    Commands.Free;
    Executor.Free;
  end;
end;
Exemple avec TMemo (utilisation directe de ExecuteFromMemo)
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
procedure TForm1.ButtonRunFromMemoClick(Sender: TObject);
var
  Executor: TCmdPipeExecutor;
begin
  Executor := TCmdPipeExecutor.Create;
  try
    // SourceMemo contient les commandes (une par ligne)
    // DestMemo affichera la sortie et le code de retour
    Executor.ExecuteFromMemo(SourceMemo, DestMemo, True, False, 15000);
  finally
    Executor.Free;
  end;
end;
Exemple utilisant UTF-8 (pour commandes/texte accentué)
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var
  Executor: TCmdPipeExecutor;
  Cmds: TStringList;
  OutStr: string;
  Code: DWORD;
begin
  Executor := TCmdPipeExecutor.Create;
  Cmds := TStringList.Create;
  try
    Cmds.Add('chcp 65001');                // optionnel : passe la console en UTF-8
    Cmds.Add('echo Café et accents: éàê');
    Cmds.Add('exit');
    if Executor.ExecuteCmdAndCapture(Cmds, OutStr, Code, True, True, 5000) then
      Memo1.Lines.Text := OutStr
    else
      Memo1.Lines.Text := 'Erreur d''exécution';
  finally
    Cmds.Free;
    Executor.Free;
  end;
end;
Avatar de ShaiLeTroll
Expert éminent sénior https://www.developpez.com
Le 29/10/2025 à 10:09
C'est intéressant car justement, j'ai un code qui génère un BAT en fichier, l'execute et le BAT à la fin contient son auto-suppression, le code de XeGregory éviterait de passer par un fichier.
Un jour, je vais m'inspirer de ce code pour modifier TSLTModuleAutoUpdateByDOSBatchEngine pour utiliser la TStringList BatOp directement au lieu de créer le BAT en fichier.

Pour info, il y avait un Exemple MSDN dont j'ai perdu le lien d'origine
En voici un surement plus récent que celui que j'avais utilisé en D7 :
Creating a Child Process with Redirected Input and Output

En tout cas, il est nettement plus long qu'en 2008

Cela exécute un EXE ou un BAT mais pas une série de commande comme l'exemple ci-dessus.


Version D10 :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
 
{* -----------------------------------------------------------------------------
la fonction CallCmd permet de lancer un programme console, tout en récupérant en quasi temps-réel le contenu devant normalement s'y afficher
@param CmdDirectory Dossier contenant le Fichier CmdName
@param CmdName programme console à executer
@param CmdParam paramètres de la ligne de commande
@param CmdWorkDir Dossier de Travail
@param ExitCode Code de Sortie renvoyé par le programme console, -1 si non récupéré
@param OutputText chaine contenant tout ce qui aurait du s'afficher (canal sortie)
@param ErrorText chaine contenant tout ce qui a été signalé comme erreurs (canal erreur)
@param Delay indique le temps entre chaque cycle de lecture des canaux, détermine la fréquence de lancement de WaitEvent, par défaut, cela attend que le programme console se termine
@param WaitEvent procédure à lancer lorsque le Delay est écoulé, Output et Error contiennent les derniers éléments envoyés par le programme console sur les canaux depuis le dernier délai, AbortProcess indique si la processus doit être arrêté
@param PipeMaxSize défini la taille maximal que l'on lit à chaque chaque cycle de lecture des canaux, si zéro, taille non limitée par défaut
@return Indique si le programme a été lancé
------------------------------------------------------------------------------ }
class function TSLTShellExecuteWrapper.CallCmd(const CmdDirectory, CmdName, CmdParam, CmdWorkDir: string; out ExitCode: Int64; out OutputText: string; out ErrorText: string; Delay: Cardinal = INFINITE; WaitEvent: TSLTShellExecuteWrapperCallCmdEvent = nil; PipeMaxSize: Cardinal = 0): Boolean;
var
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
  CommandLine: string; // utile pour le débogage, ne pas confondre CommandLine avec SysUtils.CmdLine
  SecurityAttr : TSecurityAttributes;
  hReadPipeInput, hWritePipeInput: NativeUInt;
  hReadPipeOutput, hWritePipeOutput: NativeUInt;
  hReadPipeError, hWritePipeError: NativeUInt;
  lpCurrentDirectory: PChar;
  Terminated: Boolean;
  AbortProcess: Boolean;
  HandleFunctionProcess: Cardinal;
 
  function ReadPipe(Handle: Cardinal; out Buf: string): Boolean;
  const
    MAX_INT: Cardinal = MaxInt;
  var
    PipeSize: Cardinal;
    PipeToRead, PipeReaded: Cardinal;
    PipeBuf: array of AnsiChar;
    AnsiBuf: AnsiString;
  begin
    PipeSize := GetFileSize(Handle, nil); // On oublie si cela dépasse 2Go ... normalement c'est 4Ko
    if (PipeMaxSize > 0) and (PipeSize > PipeMaxSize) then
      PipeToRead := PipeMaxSize
    else
      PipeToRead := PipeSize;
 
    Result := PipeToRead > 0;
    if Result then
    begin
      SetLength(PipeBuf, PipeToRead + 1); // + 1 Pour le Zero Terminal utilisé par OemToAnsi
      ZeroMemory(@PipeBuf[0], PipeToRead + 1);
      ReadFile(Handle, PipeBuf[0], PipeToRead, PipeReaded, nil);
 
      SetLength(AnsiBuf, PipeToRead);
      OemToAnsi(@PipeBuf[0], @AnsiBuf[1]);
      Buf := string(AnsiBuf);
    end;
  end;
 
  procedure ReadPipes();
  var
    DeltaOutputText: string;
    DeltaErrorText: string;
  begin
    if ReadPipe(hReadPipeOutput, DeltaOutputText) then
      OutputText := OutputText + DeltaOutputText;
    if ReadPipe(hReadPipeError, DeltaErrorText) then
      ErrorText := ErrorText + DeltaErrorText;
    try
      if Assigned(WaitEvent) then
        WaitEvent(DeltaOutputText, DeltaErrorText, AbortProcess);
    except
      on E: Exception do
        OutputDebugString(PChar(Format('s.CallCmd.ReadPipes.WaitEvent - "%s" : "%s"', [Self.ClassName(), E.ClassName(), E.Message])));
    end;
  end;
 
begin
  (*
  Result := True;
  OutputText := 'Dummy Output';
  ErrorText := 'Dummy Error';
  ErrorCode := 0;
  Exit;
  *)
  OutputText := '';
  ErrorText := '';
  try
    SecurityAttr.nLength := SizeOf(TSecurityAttributes);
    SecurityAttr.lpSecurityDescriptor := nil;
    SecurityAttr.bInheritHandle := True;
    if CreatePipe(hReadPipeInput, hWritePipeInput, @SecurityAttr, 0) and
      CreatePipe(hReadPipeOutput, hWritePipeOutput, @SecurityAttr, 0) and
      CreatePipe(hReadPipeError, hWritePipeError, @SecurityAttr, 0) then
    begin
      try
        ZeroMemory(@StartupInfo, SizeOf(StartupInfo)); // GetStartupInfo(StartupInfo);
        StartupInfo.cb := SizeOf(StartupInfo);
        StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES; // Active wShowWindow et hStdOutput/hStdError
        StartupInfo.wShowWindow := SW_HIDE;
        StartupInfo.hStdInput := hReadPipeInput;
        StartupInfo.hStdOutput := hWritePipeOutput;
        StartupInfo.hStdError := hWritePipeError;
        ZeroMemory(@ProcessInfo, SizeOf(ProcessInfo));
 
        if CmdDirectory <> '' then
        begin
          CommandLine := Format('"%s%s" %s', [IncludeTrailingPathDelimiter(CmdDirectory), CmdName, CmdParam]);
          if CmdWorkDir <> '' then
            lpCurrentDirectory := PChar(CmdWorkDir)
          else
            lpCurrentDirectory := PChar(CmdDirectory);
        end
        else
        begin
          CommandLine := Format('%s %s', [CmdName, CmdParam]);
          lpCurrentDirectory := PChar(CmdWorkDir);
        end;
 
        Result := CreateProcess(nil, PChar(CommandLine), @SecurityAttr, @SecurityAttr, True, 0, nil, lpCurrentDirectory, StartupInfo, ProcessInfo);
        if Result then
        begin
          try
            Terminated := False;
            AbortProcess := False;
            while not Terminated do
            begin
              case WaitForSingleObject(ProcessInfo.hProcess, Delay) of
                WAIT_OBJECT_0 :
                  begin
                    ReadPipes();
                    Terminated := True;
                  end;
                WAIT_ABANDONED : Terminated := True;
                WAIT_TIMEOUT :
                  begin
                    ReadPipes();
                    Terminated := Delay = INFINITE;
                  end;
                WAIT_FAILED: Abort;
              else
                Terminated := True;
              end;
 
              if AbortProcess then
              begin
               HandleFunctionProcess := OpenProcess(PROCESS_TERMINATE, False, ProcessInfo.dwProcessId);
               if HandleFunctionProcess > 0 then
               begin
                 TerminateProcess(HandleFunctionProcess, 0);
                 CloseHandle(HandleFunctionProcess);
               end;
              end;
            end;
 
            ULARGE_INTEGER(ExitCode).HighPart := 0;
            if not GetExitCodeProcess(ProcessInfo.hProcess, ULARGE_INTEGER(ExitCode).LowPart) then
              ExitCode := -1;
          finally
            CloseHandle(ProcessInfo.hThread);
            CloseHandle(ProcessInfo.hProcess); // The handles for both the process and the main thread must be closed through calls to CloseHandle
          end;
        end;
      finally
        CloseHandle(hReadPipeInput);
        CloseHandle(hWritePipeInput);
        CloseHandle(hReadPipeOutput);
        CloseHandle(hWritePipeOutput);
        CloseHandle(hReadPipeError);
        CloseHandle(hWritePipeError);
      end;
    end
    else
      raise Exception.Create('Impossible de créer les Pipes');
  except
    on E: Exception do
    begin
      OutputDebugString(PChar(Format('%s.CallCmd Error %s, Message : %s', [Self.ClassName(), E.ClassName(), E.Message])));
      raise;
    end;
  end;
end;
Avatar de XeGregory
Membre averti https://www.developpez.com
Le 29/10/2025 à 10:41
Citation Envoyé par ShaiLeTroll Voir le message
Pour info, il y avait un Exemple MSDN dont j'ai perdu le lien d'origine
Sur Internet, je suis tombé sur une copie en Markdown de l’article Microsoft Learn « Creating a Child Process with Redirected Input and Output ».

https://github.com/MicrosoftDocs/win...-and-output.md

https://github.com/MicrosoftDocs/win...put.md?plain=1

le code de XeGregory éviterait de passer par un fichier.
L’essayer, c’est l’adopter . Oui, c’est bien plus pratique que de créer un fichier temporaire, exécuter le .bat puis le supprimer.

Puis, le côté pratique sait automatiser des tâches externes.
Exemple : appeler des outils CLI (ffmpeg, 7zip, git, scripts batch, utilitaires système) depuis l’application.
Avatar de Papy214
Membre expert https://www.developpez.com
Le 29/10/2025 à 11:13
Joli !

Et c'est compatible à partir de quelle version de Delphi stp ?
Avatar de XeGregory
Membre averti https://www.developpez.com
Le 29/10/2025 à 11:20
Citation Envoyé par Papy214 Voir le message
Joli !

Et c'est compatible à partir de quelle version de Delphi stp ?
Delphi 2009 et toutes les versions ultérieures.
Avatar de Andnotor
Rédacteur/Modérateur https://www.developpez.com
Le 29/10/2025 à 12:59
Citation Envoyé par ShaiLeTroll Voir le message
j'ai un code qui génère un BAT en fichier, l'execute et le BAT à la fin contient son auto-suppression
Ne pas oublier que cmd.exe accepte des commandes en paramètres. C'est d'ailleurs ainsi que doit procéder l'IDE en pré(post)-compilation.

cmd.exe /C echo Hello World! > d:\test.txt && copy d:\test.txt d:\test2.txt
Avatar de XeGregory
Membre averti https://www.developpez.com
Le 29/10/2025 à 17:51


Bat :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
@echo off
echo Nettoyage du dossier Temp de l'utilisateur...
set "TEMP_FOLDER=%TEMP%"
 
echo Suppression des fichiers...
del /f /s /q "%TEMP_FOLDER%\*.*"
echo Suppression des dossiers...
for /d %%D in ("%TEMP_FOLDER%\*") do rd /s /q "%%D"
 
echo Terminé.
PowerShell



Code : Sélectionner tout
1
2
3
@echo off
 powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ^
 "Get-ChildItem -Path 'C:\Windows\System32' -File | Sort-Object Name | Format-Table Name,Length,LastWriteTime -AutoSize"
Developpez.com décline toute responsabilité quant à l'utilisation des différents éléments téléchargés.