7

A program written in Go does certain tasks by shelling out to PowerShell. This is slow because each call to exec.Command() starts up PowerShell, runs one command, then exits. We're running hundreds of commands and starting PowerShell hundreds of times is a large overhead.

If the commands were known ahead of time, we could simply generate powershell script and execute it. However the commands that need to be run are generated dynamically, therefore this won't work for us.

Here's the code we use to run command

  out, err := exec.Command("powershell", "-NoProfile", command).CombinedOutput()
  if err != nil {
    // ...
  }

Is it possible to start PowerShell once and feed it individual commands?

Update: I was asked which PS commands are being run. Some are:

  • Get-DnsServerResourceRecord -ComputerName FOO -ZoneName BAR
  • Remove-DnsServerResourceRecord -Force -ComputerName "FOO" -ZoneName "BAR" -Name "X" -RRType "Y" -RecordData "Z"
  • Add-DnsServerResourceRecordX-ComputerName FOO -ZoneName BAR ...

Update 2022-03-09: I'm embarrassed to say that I found the slowness problem and it wasn't what I assumed when I asked the question. The PowerShell command being run was occasionally getting huge datasets that took a long time to process. So, the problem wasn't Go or exec at all! That said, this question is still useful for people that want to batch up commands.

TomOnTime
  • 4,175
  • 4
  • 36
  • 39
  • 3
    Could you provide an example of a sequence of commands that are generated dynamically that _cannot be ported_ to a PowerShell script that itself recapitulates flow control and variables (from either itself or passed from Go)? I'm having a failure of imagination. – msanford Dec 16 '20 at 21:48
  • PowerShell commands in the end also just call the system APIs, so the fastest way is obviously calling those APIs directly – phuclv Dec 17 '20 at 00:32
  • @msanford For example PS is used to gather info, Go processes it and determines what update to do, and PS is used to do the update. Repeat in a loop. A specific example would be https://github.com/StackExchange/dnscontrol 's plug-in for updating Active Directory DNS. The diff'ing algorithm is in Go. If I ported the diff'ing code to PowerShell it would be the only plug-in that works that way. – TomOnTime Dec 17 '20 at 01:00
  • @phuclv I find your comment is not very helpful. What if I had replied to https://stackoverflow.com/questions/44104238/how-to-properly-handle-a-file-only-used-locally-in-git by saying, "Why don't you use Mercurial instead?" A more useful comment would direct me to documentation on how to call Windows APIs from Go or similar. – TomOnTime Dec 17 '20 at 01:17
  • you didn't show any commands so no one knows what you want to do, then how can one proceed with suggestions? – phuclv Dec 17 '20 at 01:22
  • How about starting [powershell once in a mode that reads commands](https://stackoverflow.com/q/21833829) with [`exec.Command(...).Start()`](https://pkg.go.dev/os/exec#Cmd.Start), then writing commands to its [Stdin](https://pkg.go.dev/os/exec#Cmd.StdinPipe) and reading the output? Could get a bit complicated to get right but should work and starts Powershell only *once*. It might be hard to determine when a command is finished – xarantolus Dec 17 '20 at 11:22
  • As requested, I've included some of the commands being run. – TomOnTime Dec 18 '20 at 11:51
  • are they run concurrently ? Do they need to execute in sequence ? Might be a way to faster the program simpler than writing on stdin on the fly. though this all unclear. –  Dec 18 '20 at 13:40

1 Answers1

6

You can use Cmd.StdinPipe:

package main

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

func main() {
   cmd := exec.Command("powershell", "-nologo", "-noprofile")
   stdin, err := cmd.StdinPipe()
   if err != nil {
      log.Fatal(err)
   }
   go func() {
      defer stdin.Close()
      fmt.Fprintln(stdin, "New-Item a.txt")
      fmt.Fprintln(stdin, "New-Item b.txt")
   }()
   out, err := cmd.CombinedOutput()
   if err != nil {
      log.Fatal(err)
   }
   fmt.Printf("%s\n", out)
}

Although, it's a strange request, as with some work Go should pretty much be able to do anything PowerShell can do, including making Syscalls.

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Zombo
  • 1
  • 62
  • 391
  • 407