Tyrrrz / CliWrap

Library for running command-line processes
MIT License
4.32k stars 264 forks source link

Long argument sent to PowerShell does not seem to work #166

Closed akshinmustafayev closed 2 years ago

akshinmustafayev commented 2 years ago

Version

3.5.0

Details

When I try to send Command argument to Powershell via CliWrap, it does not seem to want to get there.

Steps to reproduce

Try this code:

string script = "
$arg1 = $args[0]
$arg2 = $args[1]
\"\" | out-file -Force -FilePath \"D:\bbbb.txt\"
\"Begin file\" | out-file -Force -FilePath \"D:\bbbb.txt\" -Append
\"new line arg1 $arg1  \" | out-file -Force -FilePath \"D:\bbbb.txt\" -Append
\"new line arg2 $arg2  \" | out-file -Force -FilePath \"D:\bbbb.txt\" -Append
"
string scriptArgumentsData = "\"argument 1\" \"argument 2\""

ArgumentsBuilder argumentsBuilder = new ArgumentsBuilder();
argumentsBuilder.Add("-NoProfile -Command { . { " + script + " } " + scriptArgumentsData + " } ", false);

var stdOutBuffer = new StringBuilder();
var stdErrBuffer = new StringBuilder();

Console.WriteLine(argumentsBuilder.Build());

await Cli.Wrap(@"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe")
    .WithArguments(argumentsBuilder.Build())
    .WithWorkingDirectory(@"C:\Windows\System32")
    .WithValidation(CommandResultValidation.None)
    .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer))
    .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer))
    .ExecuteAsync();

Console.WriteLine(stdOutBuffer.ToString());
Console.WriteLine(stdErrBuffer.ToString());

If I execute in powershell terminal manually:

powershell.exe -NoProfile -Command { . { $arg1 = $args[0]
$arg2 = $args[1]
"" | out-file -Force -FilePath "D:\bbbb.txt"
"Begin file" | out-file -Force -FilePath "D:\bbbb.txt" -Append
"new line arg1 $arg1  " | out-file -Force -FilePath "D:\bbbb.txt" -Append
"new line arg2 $arg2  " | out-file -Force -FilePath "D:\bbbb.txt" -Append
 } "b argument 1" "b argument 2" }

argument successfully being sent to PowerShell and script successfully executes specified tasks

Tyrrrz commented 2 years ago

Chances are, you messed up the escaping somewhere. Either use arguments builder with escaping, or format the command as base64 and use -EncodedCommand instead.

akshinmustafayev commented 2 years ago

Thanks for the suggestion.

I have found a workaround using EncodedCommand argument. But now I face with second issue.

According to that manual first part of powershell command pipes out data to the second Encoded part which operates with arguments.

I wrote this code for this purpose:

string script = @"
param
(
    [Parameter(Mandatory)]
    [string]
    $arg1,

    [Parameter(Mandatory)]
    [string]
    $arg2
)
"""" | out-file -Force -FilePath ""D:\bbbb.txt""
""Begin file"" | out-file -Force -FilePath ""D:\bbbb.txt"" -Append
""new line arg1 $arg1  """" | out-file -Force -FilePath ""D:\bbbb.txt"" -Append
""new line arg2 $arg2  """" | out-file -Force -FilePath ""D:\bbbb.txt"" -Append";
var plainTextBytes = System.Text.Encoding.Unicode.GetBytes(script);
string scriptEncoded = System.Convert.ToBase64String(plainTextBytes);

string scriptArgumentsData = "'arg1', 'arg2'";

var execution = Cli.Wrap(@"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe")
                .WithArguments("-NoProfile -Command " + scriptArgumentsData)
                .WithWorkingDirectory(@"C:\Windows\System32")
                .WithValidation(CommandResultValidation.None) |
                Cli.Wrap(@"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe")
                .WithArguments("-NoProfile -EncodedCommand " + scriptEncoded)
                .WithWorkingDirectory(@"C:\Windows\System32")
                .WithValidation(CommandResultValidation.None);

await execution.ExecuteAsync();

After execution it still asks to specify pipeline input in the console: image

Tyrrrz commented 2 years ago

Don't use the string overload of WithArguments if you have variable parameters.

akshinmustafayev commented 2 years ago

Variable parameters may differ and it is hard to cut them out from the long string. Anyways I tried to simplify it just by specifying them one by one, with no luck. Even if I change script to:

string script = "Get-Date | out-file -Force -FilePath \"D:\\bbbb.txt\"";

and specify:

var execution = Cli.Wrap(@"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe")
                .WithArguments(args => args.Add("-NoProfile").Add("-Command " + scriptArgumentsData))
                .WithWorkingDirectory(@"C:\Windows\System32")
                .WithValidation(CommandResultValidation.None) |
                Cli.Wrap(@"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe")
                .WithArguments(args => args.Add("-NoProfile").Add("-EncodedCommand " + scriptEncoded))
                .WithWorkingDirectory(@"C:\Windows\System32")
                .WithValidation(CommandResultValidation.None);

nothing happens after execution

akshinmustafayev commented 2 years ago

I tried to do using pipelines, and ended up doing this another way.