2

I would like to run command mkdir C:\Temp\555 in Powershell via a Base64-encoded string.

This code should work

$4 = "bWtkaXIgQzpcVGVtcFw1NTU="

$code = [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($4))

powershell $code

but if i convert $code to ASCII, how can I run it like that, because this one won't work:

$4 = "bWtkaXIgQzpcVGVtcFw1NTU="

$code = [char]091+[char]084+[char]101+[char]120+[char]116+[char]046+[char]069+[char]110+[char]099+[char]111+[char]100+[char]105+[char]110+[char]103+[char]093+[char]058+[char]058+[char]085+[char]116+[char]102+[char]056+[char]046+[char]071+[char]101+[char]116+[char]083+[char]116+[char]114+[char]105+[char]110+[char]103+[char]040+[char]091+[char]067+[char]111+[char]110+[char]118+[char]101+[char]114+[char]116+[char]093+[char]058+[char]058+[char]070+[char]114+[char]111+[char]109+[char]066+[char]097+[char]115+[char]101+[char]054+[char]052+[char]083+[char]116+[char]114+[char]105+[char]110+[char]103+[char]040+[char]036+[char]052+[char]041+[char]041

powershell $code

because the last code doesn't work while the first one does, any help please?

mklement0
  • 382,024
  • 64
  • 607
  • 775
Didrapost
  • 23
  • 1
  • 1
  • 4

2 Answers2

3

Note: This answer addresses common use cases first; for a discussion of what you tried, see the bottom section.


Note: If you just want to execute commands stored in a string from inside a PowerShell session, without needing to run commands in a child process, use Invoke-Expression, but do note that Invoke-Expression should generally be avoided:

Note: For illustrative purposes, I'm substituting command Get-Date -UFormat "%s" for your original command, mkdir "C:\Temp\555". This Get-Date command prints the current point in time as a Unix timestamp, i.e., an integer denoting the seconds since 1 Jan 1970, midnight UTC; e.g., 1565229614 for Wednesday, August 7, 2019 10:00:13 PM ETD.

$cmd = 'Get-Date -UFormat "%s"'
Invoke-Expression $cmd  # Execute the string $cmd as code.

If you control construction of the commands, it's better to store pieces of code as script blocks ({ ... }) inside a PowerShell session:

$cmd = { Get-Date -UFormat "%s" }  # create a script block
& $cmd                             # execute the script block

How to pass a complex command to a PowerShell child process via its CLI, using the -EncodedCommand parameter:


  • From outside PowerShell, use the -EncodedCommand parameter, which requires a Base64 encoding of the bytes of a UTF-16LE-encoded string ("Unicode"-encoded), not of a UTF-8-encoded one:
# Get the Base64-encoding of the bytes that make up the UTF-16LE
# ("Unicode") encoding of string 'Get-Date -UFormat "%s"'
# Assigns the following to $base64Cmd: 
#   'RwBlAHQALQBEAGEAdABlACAALQBVAEYAbwByAG0AYQB0ACAAIgAlAHMAIgA='
$base64Cmd = 
  [System.Convert]::ToBase64String(
     [System.Text.Encoding]::Unicode.GetBytes(
       'Get-Date -UFormat "%s"'
     )
  )

powershell -EncodedCommand $base64Cmd  # executes: Get-Date -UFormat "%s"

Note: The above assumes Windows PowerShell. If you're using PowerShell Core, use pwsh instead of powershell.
Specifying the System. component in PowerShell type literals explicitly is optional; e.g., [System.Text.Encoding] can be shortened to [Text.Encoding].


  • From inside PowerShell, there's no need to pass a Base64-encoded string via
    -EncodedCommand explicitly, because PowerShell does that implicitly when you simply pass the code to execute as a script block({ ... }):
powershell { Get-Date -UFormat "%s" }   # -command (-c) parameter implied

Note:

  • You cannot refer to the caller's variables in a script block, but you can pass arguments to it via the -args parameter; e.g.:
    powershell { Get-Date -UFormat $args[0] } -args '%s'.

Behind the scenes, the appropriate Base64 encoding is performed, and the encoded command is passed to -EncodedCommand, as follows:

# The command to execute:
$cmd = 'Get-Date -UFormat "%s"'

# Obtain a Base64-encoded version of the command from the bytes of its
# UTF-16LE encoding.
$base64Cmd = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd))

# Pass the result to the -EncodedCommand parameter:
powershell -EncodedCommand $base64Cmd -inputFormat xml -outputFormat xml

Note:

  • -inputFormat xml -outputFormat xml are automatically added when you pass a script block to the (possibly positionally implied) -Command / -c parameter.

  • They trigger CLIXML serializing of the arguments passed to as well as the output from the CLI call.

  • This serialization is the same as the one used by PowerShell's remoting / background-job infrastructure and has two benefits:

    • PowerShell's output streams are preserved (whereas passing a string -Command merges all output into the single standard stdout stream).
    • Arguments and output are passed and received as objects (rather than as text only, as happens when you pass a string -Command), albeit with the same limitations on type fidelity as in remoting - see this answer for more information.
  • The equivalent of using -args to pass arguments to a script block is to pass an explicitly Base64-encoded argument list to -encodedArguments (-ea):

    • This parameter - undocumented as of this writing - additionally requires serializing the argument list to XML (CLIXML format) before Base64-encoding the result, as demonstrated in this answer.

As for what you tried:

  • Your 1st command works, because you're passing a decoded plain-text version of the mkdir command to powershell.exe, which implicitly binds to the -Command parameter and is executed as a command in the new PowerShell session.
    As an aside: pwsh, PowerShell Core's CLI, now defaults to -File, so -Command (or -c) would have to be used explicitly.

  • Your 2nd command does not work, because $code now contains the plain text of the Base64-decoding command from your 1st command.
    That command references variable $4, which the new PowerShell instance you're creating knows nothing about.

However, instead of trying to defer the decoding of the Base64-encoded mkdir command to the new PowerShell session, it makes much more sense to pass the Base64-encoded command directly to the new session (if a new session is even needed, see above), via -EncodedCommand.

However, -EncodedCommand requires a Base64 encoding based on UTF-16LE, not UTF-8 - see above for how to produce such an encoding explicitly (if needed).

If you're given a UTF-8-based Base64 encoding, you can translate it into a UTF-16LE-based one as follows:

# UTF-8-based Base64-encoding
$4 = "bWtkaXIgQzpcVGVtcFw1NTU="

# Decode to plain text.
$plainTextCode = [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($4))

# Re-encode to Base64 via UTF-16 ("Unicode"):
$utf16Base64Command = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($plainTextCode))

# Pass to powershell.exe via -EncodedCommand
powershell -EncodedCommand $code
mklement0
  • 382,024
  • 64
  • 607
  • 775
0

A string can be executed as a script through the CommandInvocationIntrinsics class method InvokeScript.

powershell $executioncontext.InvokeCommand.InvokeScript($code)

See InvokeScript Method for more information.

AdminOfThings
  • 23,946
  • 4
  • 17
  • 27
  • `$executioncontext.InvokeCommand.InvokeScript($code)` is a more obscure way of calling `Invoke-Expression $code` (strictly speaking, `& { Invoke-Expression $code }`, but that the child scope makes no difference here) except that errors aren't surfaced. It is worth noting that a much more sensible way to pass the Base64-encoded `mkdir` command is to _directly_ pass it to `powershell -EncodedCommand`; however, for that to work the Base64 encoding would have to be based on UTF16-LE, not on UTF-8. – mklement0 Aug 08 '19 at 02:53