0

I have a Go application that takes in PowerShell commands as input, creates a new ps1 file, writes the commands to the file, then executes the script file.

It would be great if the file were not visible to users.

If there were some way to create the script file as a memory-mapped file and then execute that memory-mapped file, that would be a good solution.

Using CGo, I can call into C or C++ from the Go application. Since PowerShell can work with C# objects, I'm thinking that maybe we can somehow call into C# code (either from C/C++ or Go) and create a C# memory-mapped file object that PowerShell can then use to execute as a script.

Admittedly, I know nothing about how calling managed code from unmanaged code works.

So, is there some way to take this string containing PowerShell commands and execute it as a script without creating an on-disk file?

kostix
  • 51,517
  • 14
  • 93
  • 176
brandon_busby
  • 122
  • 11
  • Both `powershell.exe` and `pwsh` take an [`-EncodedCommand`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_exe?view=powershell-5.1#-encodedcommand-base64encodedcommand) command line argument that might be useful here. Why is it important to hide the script from the user? – Mathias R. Jessen Mar 19 '21 at 01:59
  • Mostly because of Hyrum's Law. We don't want customers to be able to depend on implementation details/be able to modify the scripts. We've tried using the Windows %TEMP% directory, storing the scripts in a pseudorandom location in there, but there are security concerns with that approach. So, going straight from memory to PowerShell.exe, skipping the disk seems like the right thing. – brandon_busby Mar 19 '21 at 02:23
  • Thanks for the -EncodedCommand suggestion. Unfortunately, it has a character limit of, I believe 32767 characters, which does not work for some of our scripts. That being said, maybe a solution would be to use the -EncodedCommand to spin up a PowerShell session which then proceeds to read the script a stdin pipe. – brandon_busby Mar 19 '21 at 02:24
  • While we're at at, 1) [the language is called Go](https://golang.org/doc/faq#go_or_golang) (you don't say JavaLang or PythonLang either, right?); 2) memory-mapped file is not what you appear to think it is, you might consider doing a little research. Also, the concept explained by Mathias in their answer is common to all _interpreted_ language hosts (usually called shells though it's not quite correct)—including Python, Ruby, Tcl and the gazillion others, so you can keep this in mind as it may come in handy the next time. – kostix Mar 19 '21 at 04:29
  • @kostix, thanks for the feedback. Agreed that a memory-mapped file does not appear to be what I need in this situation. However, what specifically makes you think that my understanding is wrong? – brandon_busby Mar 19 '21 at 05:18
  • Looks like I have misinterpreted that part your question; possibly that was _my_ bias while interpreting it: I would not think about "real" memory-mapped file for such a task because I _knew_ the same thing Mathias did—you just can solve it way simpler ;-) I'm sorry about that; your understanding is clearly not wrong but mine was. – kostix Mar 19 '21 at 09:43

1 Answers1

5

To make powershell.exe read script input as standard input, pass - as a command line argument:

package main

import (
    "bytes"
    "fmt"
    "log"
    "os/exec"
    "strings"
)

func main() {
    cmd := exec.Command("powershell", "-")
    cmd.Stdin = strings.NewReader("Write-Output 'Hello, World!'")
    var out bytes.Buffer
    cmd.Stdout = &out
    err := cmd.Run()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("pwsh out: %q\n", out.String())
}

With Go 1.16 you can embed the script in the executable at build time:

package main

import (
    "bytes"
    _ "embed"
    "fmt"
    "log"
    "os/exec"
    "strings"
)

//go:embed script.ps1
var scriptBytes []byte

func main() {
    cmd := exec.Command("powershell", "-")
    cmd.Stdin = strings.NewReader(string(scriptBytes))
    var out bytes.Buffer
    cmd.Stdout = &out
    err := cmd.Run()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("pwsh out: %q\n", out.String())
}
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206