1

I want to encode a powershell command (a string (get-date).date) to base64 in order to run it via powershell -encodedcommand xxx.

Using the standard VBS methods (or even https://www.base64encode.org/) I get KGdldC1kYXRlKS5kYXRl which does not run.

Using the following powershell script:

$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)

I get KABnAGUAdAAtAGQAYQB0AGUAKQAuAGQAYQB0AGUA which works. The difference appears to be that the command is first encoded as Unicode bytes.

Can anyone provide a VBS Function which does this or the VBS equivalent of Unicode.GetBytes() so that we can get the right string encoded?

Marc
  • 13,011
  • 11
  • 78
  • 98

2 Answers2

3

PowerShell only accepts Base64 encodings of UTF-16 LE-encoded strings with its
-EncodedCommand parameter.
UTF-16 LE is what Unicode stands for in [System.Text.Encoding]::Unicode, and it encodes the vast majority of Unicode characters (code points) as two bytes each; it is also the string encoding used internally by both VBScript and PowerShell.

By contrast, most VBScript solutions out there use the single-byte ASCII encoding, and even the otherwise laudably Unicode-aware https://www.base64encode.org/ only offers UTF-8-based encoding (which is a mostly-single-byte-for-Western-languages encoding with other languages' chars. represented as 2-4 bytes).

Here's a robust UTF-16 LE-based Base64 encoding solution.

I've posted a more modular variant that optionally supports UTF-8 here; the code in both locations builds on this great answer.

Example call:

Base64EncodeUtf16Le("(get-date).date") ' -> "KABnAGUAdAAtAGQAYQB0AGUAKQAuAGQAYQB0AGUA"

Source code:Tip of the hat to MC ND for helping to simplify the solution.

' Base64-encodes the specified string using UTF-16 LE as the underlying text
' encoding.
Function Base64EncodeUtf16Le(ByVal sText)

    Dim bytesUtf16Le

    ' Create an aux. stream from which we can get a binary (byte array)
    ' representation of the input string in UTF-16 LE encoding. 
    With CreateObject("ADODB.Stream")
        ' Create a UTF 16-LE encoded text stream...
        .Type = 2  ' adTypeText
        .Charset = "utf-16le"
        .Open
        .WriteText sText
       ' ... and convert it to a binary stream, 
       ' so we can get the string as a byte array
        .Position = 0
        .Type = 1   ' adTypeBinary
        .Position = 2 ' Skip BOM
        bytesUtf16Le = .Read
        .Close
    End With 

    ' Use an aux. XML document with a Base64-encoded element.
    ' Assigning a byte stream (array) to .NodeTypedValue
    ' automatically performs Base64-encoding, whose result can then be accessed
    ' as the element's text.
    With CreateObject("Msxml2.DOMDocument").CreateElement("aux")
        .DataType = "bin.base64"
        .NodeTypedValue = bytesUtf16Le
        Base64EncodeUtf16Le = .Text
    End With

End Function
Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Great!. I was testing but I missed the `utf-16le` option. I've adapted my code to your information, but I have a doubt: Why two `adodb.stream` objects? Is there any case in where the solution with only one could fail? Is there any improvement in using two instances? – MC ND Oct 19 '16 at 06:10
  • 1
    @MCND D'oh! Thanks for that, I've updated my answer - 1 stream is entirely sufficient; a combination of old code and making mistakes while experimenting with a single stream had me wrongly convinced that 2 streams are needed. – mklement0 Oct 19 '16 at 14:31
2

I'm not sure this will handle all your needs, but at least it matches the output indicated in your question

Function ToBase64( ByVal text )
    Const adTypeText = 2
    Const adTypeBinary = 1

    ' Right pad each character with a null
    With New RegExp
        .Pattern = "(.)"
        .Global = True
        text = .Replace( text, "$1" & Chr(0) )
    End With

    ' Convert String to binary
    With WScript.CreateObject("ADODB.Stream")
        .Type = adTypeText
        .CharSet = "us-ascii"
        .Open
        .WriteText text
        .Position = 0
        .Type = adTypeBinary
        text = .Read
    End With

    ' Encode binary as Base64
    With WScript.CreateObject("Msxml2.DOMDocument.6.0").CreateElement("base64")
        .dataType = "bin.base64"
        .nodeTypedValue = text
        ToBase64 = Replace( .text, vbLf, "" )
    End With
End Function

WScript.Echo ToBase64("(get-date).date")

edited just to adapt my previous code to the information in mklement0 answer where you can find the details of the powershell requirements and all the details about how the code works.

Function ToBase64( ByVal text )
    Const adTypeText = 2
    Const adTypeBinary = 1

    ' Change string encoding
    With WScript.CreateObject("ADODB.Stream")
        ' Convert input string to UTF-16 LE
        .Type = adTypeText
        .CharSet = "utf-16le"
        .Open
        .WriteText text
        ' Get binary representation of the string
        .Position = 0
        .Type = adTypeBinary
        .Position = 2 ' Skip BOM
        text = .Read
    End With

    ' Encode binary as Base64
    With WScript.CreateObject("Msxml2.DOMDocument.6.0").CreateElement("base64")
        .dataType = "bin.base64"
        .nodeTypedValue = text
        ToBase64 = Replace( .text, vbLf, "" )
    End With
End Function

WScript.Echo ToBase64("(get-date).date")
Community
  • 1
  • 1
MC ND
  • 69,615
  • 8
  • 84
  • 126