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
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)
Exemple avec TMemo (utilisation directe de ExecuteFromMemo)
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 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; |
| 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; |
| 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; |
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 :
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 D7 : Redirection des entrées/sorties du process (ping)
- Version XE2 : TSLTShellExecuteWrapper
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; |
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
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.
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.
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.
Joli !
Et c'est compatible à partir de quelle version de Delphi stp ?
Et c'est compatible à partir de quelle version de Delphi stp ?
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é. |
| 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.
