Search code examples
flutterpowershelldartflutter-windows

Capture PowerShell Output in Dart and Flutter when Running Commands as an Administrator using Start-Process and -Verb RunAs


I am trying to run a Powershell command with admin privileges in Flutter/Dart. The command runs a .ps1 file inside the temp directory.
I found this article, which I implemented and it works fine. However, after the PowerShell command runs, I see an output on the PowerShell window and so I expect to print this output but I get nothing.

  Future<void> executePowerShellCommand() async {
    String tempFilePath = '${Directory.systemTemp.path}\\COMMAND.ps1';
    try {
      String command = 'powershell';
      List<String> args = [
        '-Command',
        'Start-Process -FilePath "powershell" -ArgumentList "-ExecutionPolicy Bypass -NoProfile -File `"$tempFilePath`"" -Verb RunAs -Wait'
      ];

      final ProcessResult result = await Process.run(command, args, runInShell: true, stdoutEncoding: utf8, stderrEncoding: utf8);

      String output = result.stdout.toString().trim();
      String errorOutput = result.stderr.toString().trim();
      int exitCode = result.exitCode;

      if (exitCode == 0 && errorOutput.isEmpty) {
        debugPrint('>>>Powershell Command succeeded: $output');//output is empty
      }
    } catch (e, s) {
      debugPrint(">>>Error $e");
    }
  }

I researched and found out that Start-Process spawns a separate process and that is why the output is empty. So I thought I could capture the output of the command and save it into a file, then read the contents of the file.
Something like this:

  Future<void> executePowerShellCommandWithOutput() async {
    String tempFilePath = '${Directory.systemTemp.path}\\COMMAND.ps1';
    String outputFilePath = '${Directory.systemTemp.path}\\OUTPUT.txt';
    try {
      String command = 'powershell';

      List<String> args = [
        '-Command',
        '''Start-Process -FilePath "powershell" -ArgumentList '-ExecutionPolicy Bypass -NoProfile -File "$tempFilePath" | Out-File "$outputFilePath" -Encoding utf8' -Verb RunAs -Wait'''
      ];

      final ProcessResult result = await Process.run(command, args, runInShell: true, stderrEncoding: utf8);
      
      String errorOutput = result.stderr.toString().trim();
      int exitCode = result.exitCode;

      if (exitCode == 0 && errorOutput.isEmpty) {
        File file = File(outputFilePath);
        String fileContent = file.readAsString().toString();
        debugPrint('>>>File Content: $fileContent');
      }
    } catch (e, s) {
      debugPrint(">>>Error $e");
    }
  }

This approach is unfortunately not working. How do I get it to work?
Thank you so much.


Solution

  • To make your approach work, you must use -Command rather than -File with powershell.exe, the Windows PowerShell CLI.

    Unlike -File, which only supports a target script path (*.ps1) optionally followed by literal pass-through arguments, -Command allows you to pass a snippet of PowerShell source code, which is needed in order to execute an entire pipeline such as yours (... | Out-File ...)

    List<String> args = [
      '-Command',
      '''Start-Process -FilePath "powershell" -ArgumentList '-ExecutionPolicy Bypass -NoProfile -Command & "$tempFilePath" | Out-File "$outputFilePath" -Encoding utf8' -Verb RunAs -Wait'''
    ];
    
    • Note the use of -Command instead of -File, as well as the use of &, the call operator for invoking the *.ps1 file (which is a syntactic necessity, because the script-file path is quoted).

    • Note that while it is tempting to try to use Start-Process's -RedirectStandardOut and -RedirectStandardError parameters to directly capture command output in files, this does not work in combination with -Verb RunAs, i.e. when launching an elevated process - see this answer for details.