-1

I am trying to make an application to test certain functionality that I intend to add later to my main program, but without the risk of breaking my main program.

I'm new to Pascal, and I still don't understand how many things work.

The objective of this program is, through a PowerShell command executed in the same application, to obtain several JSON, each one from a specific directory to go through them and check certain things.

These things can be the presence of specific file extensions in certain directories, or the presence of a large volume of information in them.

Create 3 objects in Delphi for this application. A button to start the program, a Timer and a JvCreateProcess.

The interval property of the timer is 10 (ms). The onTimer property is the Timer1Timer method.

Timer1Timer code:

procedure TUProbando.Timer1Timer(Sender: TObject);
var
  Comando: TStrings;
  ValorJSON: TStrings;
begin
  if ContadorDirectorios = Length(rutas) then
  begin
    Timer1.Enabled := false;
    exit;
  end;

  Timer1.Enabled := false;
  Lista.Clear;
  // spacer to improve visibilitys
  
  memo_Salida_Command_gci.Lines.Add('******* ' + rutas[ContadorDirectorios] +
    ' *******');
  Comando := TStringList.Create;

  try
    // Open Powershell
    Comando.Add('C:\Windows\System32\WindowsPowerShell\v1.0\PowerShell.exe "');


    // I add the command line to create a JSON with the contents of the specified directory
    Comando.Add('"gci ' + rutas[ContadorDirectorios] +
      ' -Recurse | ConvertTo-Json"');


    // I add the command line to the process
    JvCreateProcess1.CommandLine := Comando.Text;

    // I run the process
    JvCreateProcess1.run;
    ValorJSON := JvCreateProcess1.ConsoleOutput;

  finally
    begin
      Comando.Free;
    end;
  end;
end;

The rutas[] array is a String array containing the directories to check.

rutas: array [0 .. 2] of String = ('C:\Users\operario43\Desktop',
    'C:\Users\operario43\Documents', 'C:\Users\operario43\Pictures');

Now, JvCreateProcess1 code:

procedure TUProbando.JvCreateProcess1Terminate(Sender: TObject;
  ExitCode: Cardinal);
begin
  // Calculate the size of the current directory

// I WANT GET JSON OF PREVIOUS POWER SHELL COMMAND HERE
  SizeDirectory := GetSizeFromJson('JSON HERE', Extensiones);
  

  if checkSizeDirectory(SizeDirectory, SizeControlDirectory) then
  begin
    ShowMessage('This Directory' + rutas[ContadorDirectorios] +
      ' has important files not protected by a backup');
  end;

  // +1 counter of directories traversed
    inc(ContadorDirectorios);
end;

Other methods that I use:

function GetSizeFromJson(JSON: String; vExtensiones: array of string): integer;
var
  
  Contenedor: TJSONArray;
  Jfichero: TJSONObject;
  JNombre: TJSONValue;
  // value of the 'length' element of each element of the JSON
  JSizeFile: TJSONNumber; //
  
  I: integer;
  
  SizeDirectory: Int64;
begin
  SizeDirectory := 0;
  result := -1;
  try
    Contenedor := TJSONObject.ParseJSONValue(JSON) as TJSONArray;
    
    if Contenedor = nil then
    begin
      ShowMessage('Error al parsear el json' + JSON);
      exit;
    end;

    
    for I := 0 to Contenedor.Count - 1 do
    begin

      Jfichero := Contenedor.Items[I] as TJSONObject;
      if Jfichero <> nil then
      begin
        // I extract the name of the element
        JNombre := Jfichero.GetValue('Name');

        // If the extensions of the files in the directory are in the 'Extensions' array
        if MatchStr(ExtractFileExt(JNombre.Value), vExtensiones) then
        begin
          // get the value of the lenght element in bytes
          JSizeFile := Jfichero.GetValue('Length') as TJSONNumber;

          if JSizeFile <> nil then
          begin

            // I add the value of 'leght' of the element to the variable SizeDirectory
            inc(SizeDirectory, JSizeFile.AsInt);
          end; // if JSizeFile <> nil
        end; // if MatchStr(ExtractFileExt(JNombre.Value), vExtensiones)
      end; // if Jfichero <> nil
    end; // for I := 0 to Contenedor.Count - 1

    // I return the value of the size of the directory
    result := SizeDirectory;
  except
    on E: Exception do
      ShowMessage(E.Message);
  end;
end;

// method to find out if the size of the current directory exceeds the recommended size for directories in the variable called SizeControlDirectory
function checkSizeDirectory(vSizeDirectory, vSizeControlDirectory
  : Int64): Boolean;
var
  Contenedor: TJSONArray;
begin
  result := false;

  vSizeDirectory := GetSizeFromJson(Contenedor.ToString, Extensiones);

  if vSizeDirectory > vSizeControlDirectory then
  begin
    ShowMessage('Directory ' + rutas[ContadorDirectorios] +
      ' have files don't protected by Backup');
  end;

end;

My question is, how do I get JSON for JvCreateProcess1Terminate method?.

I've made a lot of changes trying to get it to work, and made small pieces of code to make it more manageable.

  • By using the JEDIVCL, and/or JEDICL, You can get the output, that was created by your Process-Component. This output is a raw native Text String (with LineEndings of #13#10). So, if you would like create a JSON compatible String, You have to parse the getting/redirected Console Text form the Console into your Delphi-Application. This can be done by your self, or ask a member, that do this for you. But I think, if you have some Delphi skill's (in handling Text Strings), you are able to do this. Hope this helps you a little bit ... – Jens Oct 31 '22 at 13:37
  • I edited your question to correct some language and spelling errors, but the Spanish comments in the code you should translate yourself, thank you. That is, if the comments help understand your code and question. Otherwise, please remove them. – Tom Brunberg Oct 31 '22 at 15:13
  • 1
    I removed the meaningless image of your form from the question. Telling us that the components are on the form is sufficient; it's not necessary to include a large image of your form that provides no value. Images should be used only when there is no other way to demonstrate an issue. See [Please do not upload images of code/data/errors when asking a question.](//meta.stackoverflow.com/q/285551) for a list of the many reassons NOT to include images in your post. – Ken White Nov 01 '22 at 00:35

2 Answers2

0

Just a note may be It helps someone.
You can save the output of any windows terminal command or application into a file. Just add " > FileName" after the ExeName or commandm then the output will saved to "FileName". I can use this from Delphi too. Suppose I want the output of a program called MyApp.exe to be saved to a file then I can use the following command:

Myprog := 'd:\MyApp.exe > FullOutputFileName';
r := ShellExecute(0, 'open', PChar(Myprog), nil, nil, SW_HIDE);
if r <> o then
  //Check for error
else
  //load the file FullOutputFileName and get results

When using some terminal internal commands like 'dir, ver, vol, ...' I create a batch file (FileName.bat) that contains the command or commands I want to execute then I use shellexecute to execute this file.

Notes:

  1. You can also use " >> FileName" to append the output to the end of the file.
  2. This method is slow so it is not advised to use it in loops.
  3. mORMot has a good and easy to use JSON implementation.
Ehab
  • 284
  • 1
  • 9
  • This is the worst/dirtiest way to do it. And when the program asks for input it will hang endlessly. Which is why one should [connect pipes and start the process properly](https://stackoverflow.com/q/16225025/4299358), where you can even better distinguish between output and errors. – AmigoJack Nov 01 '22 at 08:47
  • Thanks for the reply @Ehab. I know that I could do what you have told me, but it is not my goal. I'm looking to redirect the output of the PowerShell command into a JSON type variable that I can use. – Yerón Barreiro Martínez Nov 02 '22 at 08:28
0

I can understand that you have two parts to be done.

  1. Execute a windows terminal command and get the out put into string.
  2. Convert that string you get to a JSON.

For the first part I mentioned before using a file to capture the output (the old way) and here is a function to do the same using pips.

function ExecAndCapture(const ACmdLine: string; var AOutput: string): Boolean;
const
  cBufferSize = 2048;
var
  SA: TSecurityAttributes;
  SI: TStartupInfo;
  PI: TProcessInformation;
  StdOutPipeRead, StdOutPipeWrite: THandle;
  WasOK: Boolean;
  Buffer: array[0..255] of AnsiChar;
  BytesRead: Cardinal;
  WorkDir: string;
  Handle: Boolean;
begin
  Result := False;
  AOutput := '';
  with SA do begin
    nLength := SizeOf(SA);
    bInheritHandle := True;
    lpSecurityDescriptor := nil;
  end;
  CreatePipe(StdOutPipeRead, StdOutPipeWrite, @SA, 0);
  try
    with SI do
    begin
      FillChar(SI, SizeOf(SI), 0);
      cb := SizeOf(SI);
      dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
      wShowWindow := SW_HIDE;
      hStdInput := GetStdHandle(STD_INPUT_HANDLE);
      hStdOutput := StdOutPipeWrite;
      hStdError := StdOutPipeWrite;
    end;
    WorkDir := GetCurrentDir;
    Handle := CreateProcess(nil, PChar(ACmdLine),
                            nil, nil, True, 0, nil,
                            PChar(WorkDir), SI, PI);
    CloseHandle(StdOutPipeWrite);
    if Handle then
      try
        repeat
          WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
          if BytesRead > 0 then
          begin
            Buffer[BytesRead] := #0;
            AOutput := AOutput + string(Buffer);
          end;
        until not WasOK or (BytesRead = 0);
        WaitForSingleObject(PI.hProcess, INFINITE);
      finally
        CloseHandle(PI.hThread);
        CloseHandle(PI.hProcess);
      end;

    Result := True;
  finally
    CloseHandle(StdOutPipeRead);
  end;
end;

For the second part I prefer using mOrmot framework as the following example:

procedure DoTest();
var
  s : string;
  UtfStr : RawUTF8;
  i : Integer;
  O : Variant;
begin
    if ExecAndCapture('powershell.exe gci E:\AIIconPack -Recurse | ConvertTo-Json', S) then
    begin
      UtfStr := s;
      O := TDocVariant.NewJSON(UtfStr);
      for i := 0 to O._Count-1 do
      begin
        Writeln(i+1, ':', O._(i).Name, ' <> ', O._(i).Parent.Name);
      end;
    end;
end;

If you used mORMot then your (GetSizeFromJson) will be something like that (I didn't test it) :

function GetSizeFromJson(JSON: String; vExtensiones: array of string): integer;
var
  
  Contenedor: Variant;
  Jfichero: Variant;
  JNombre: string;
  // value of the 'length' element of each element of the JSON
  JSizeFile: Integer; //
  
  I: integer;

  SizeDirectory: Int64;
begin
  SizeDirectory := 0;
  result := -1;
  try
    Contenedor := TDocVariant.NewJSON(JSON);
    
    if Contenedor = UnAssigned then
    begin
      ShowMessage('Error al parsear el json' + JSON);
      exit;
    end;

    
    for I := 0 to Contenedor._Count - 1 do
    begin

      Jfichero := O._(i);
      if Jfichero <> UnAssigned then
      begin
        // I extract the name of the element
        JNombre := Jfichero.Name;

        // If the extensions of the files in the directory are in the 'Extensions' array
        if MatchStr(ExtractFileExt(JNombre), vExtensiones) then
        begin
          // get the value of the lenght element in bytes
          JSizeFile := Jfichero.Length;

          if JSizeFile <> nil then
          begin

            // I add the value of 'leght' of the element to the variable SizeDirectory
            inc(SizeDirectory, JSizeFile);
          end; // if JSizeFile <> nil
        end; // if MatchStr(ExtractFileExt(JNombre.Value), vExtensiones)
      end; // if Jfichero <> nil
    end; // for I := 0 to Contenedor.Count - 1

    // I return the value of the size of the directory
    result := SizeDirectory;
  except
    on E: Exception do
      ShowMessage(E.Message);
  end;
end;

I hope this will help someone.

Ehab
  • 284
  • 1
  • 9