Skip to content

Instantly share code, notes, and snippets.

@siennathesane
Last active January 10, 2019 23:38
Show Gist options
  • Save siennathesane/26ea6a868a9e2d98af6ce07eaf8ffd74 to your computer and use it in GitHub Desktop.
Save siennathesane/26ea6a868a9e2d98af6ce07eaf8ffd74 to your computer and use it in GitHub Desktop.
Background processes in Windows with Go.
package main
import (
"errors"
"fmt"
"golang.org/x/sys/windows"
"syscall"
"time"
"unsafe"
)
func main() {
testBg := &BackgroundProcess{
BufferSize: 1024,
}
err := testBg.CreateBackgroundProcess("C:\\Windows\\System32\\notepad.exe")
if err != nil {
fmt.Println(err)
}
time.Sleep(time.Second * 1)
err = testBg.CloseBackgroundProcess()
if err != nil {
fmt.Println(err)
}
}
var (
modkernel32Test = windows.NewLazySystemDLL("kernel32.dll")
procTerminateProcess = modkernel32Test.NewProc("TerminateProcess")
procCreateProcessW = modkernel32Test.NewProc("CreateProcessW")
procGetLastError = modkernel32Test.NewProc("GetLastError")
procCreatePipe = modkernel32Test.NewProc("CreatePipe")
)
type (
Dword uint32
Handle uintptr
LpByte byte
LpcStr uint16
LpStr uint16
LpVoid uint16
Word uint16
)
type SecurityAttributes struct {
NLength Dword
LpSecurityDescriptor uintptr
BInheritHandle bool
}
type StartupInfo struct {
Cb Dword
LpReserved LpcStr
LpDesktop LpStr
LpTitle LpStr
DwX Dword
DwY Dword
DwXSize Dword
DwYSize Dword
DwXCountChars Dword
DwYCountChars Dword
DwFillAttribute Dword
DwFlags Dword
WShowWindow Word
CbReserved2 Word
LpReserved2 LpByte
HStdInput Handle
HStdOutput Handle
HStdError Handle
}
type ProcessInformation struct {
HProcess Handle
HThread Handle
DwProcess Dword
DwThreadId Dword
}
type BackgroundProcess struct {
readPipeHandle Handle
writePipeHandle Handle
procInfo ProcessInformation
startupInfo StartupInfo
BufferSize Dword
ChildProcessHandle Handle
}
func (bg *BackgroundProcess) CreateBackgroundProcess(lpCommandLine string) (error) {
// build some necessities.
var lpProcessAttrs SecurityAttributes
//lpProcessAttrs.BInheritHandle = true
var lpThreadAttrs SecurityAttributes
lpCli, err := StringToLpStr(lpCommandLine)
if err != nil {
return err
}
// create the pipe for the process to be read.
var lpPipeSecAttrs SecurityAttributes
if ok := CreatePipe(&bg.readPipeHandle, &bg.writePipeHandle, &lpPipeSecAttrs, bg.BufferSize); !ok {
return GetLastError()
}
if ok := CreateProcess(nil, lpCli, &lpProcessAttrs, &lpThreadAttrs, true, 0, nil, nil, &bg.startupInfo, &bg.procInfo); !ok {
return GetLastError()
}
// this is where we save the child process handle.
if bg.procInfo.HProcess <= 0 {
return errors.New("child handle does not exist")
}
return nil
}
func (bg *BackgroundProcess) CloseBackgroundProcess() error {
if ok := TerminateProcess(bg.procInfo.HProcess, 0); !ok {
return GetLastError()
}
return nil
}
func StringToLpStr(s string) (*LpStr, error) {
res, err := syscall.UTF16PtrFromString(s)
return (*LpStr)(res), err
}
// TerminateProcess terminates the specified process and all of its threads.
func TerminateProcess(hProcess Handle, uExitCode uint) (ok bool) {
r0, _, _ := syscall.Syscall(procTerminateProcess.Addr(), 2, uintptr(hProcess), uintptr(uExitCode), 0)
ok = r0 != 0
return
}
// CreateProcess creates a new process and its primary thread. The new process runs in the security context of the calling process. See: https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa
func CreateProcess(lpApplicationName *LpcStr, lpCommandLine *LpStr, lpProcessAtrributes *SecurityAttributes, lpThreadAttributes *SecurityAttributes, bInheritThreadHandles bool, dwCreationFlags Dword, lpEnvironment *LpVoid, lpCurrentDirectory *LpcStr, lpStartupInfo *StartupInfo, lpProcessInformation *ProcessInformation) (ok bool) {
var _p0 uint32
if bInheritThreadHandles {
_p0 = 1
} else {
_p0 = 0
}
r0, _, _ := syscall.Syscall12(procCreateProcessW.Addr(), 10, uintptr(unsafe.Pointer(lpApplicationName)), uintptr(unsafe.Pointer(lpCommandLine)), uintptr(unsafe.Pointer(lpProcessAtrributes)), uintptr(unsafe.Pointer(lpThreadAttributes)), uintptr(_p0), uintptr(dwCreationFlags), uintptr(unsafe.Pointer(lpEnvironment)), uintptr(unsafe.Pointer(lpCurrentDirectory)), uintptr(unsafe.Pointer(lpStartupInfo)), uintptr(unsafe.Pointer(lpProcessInformation)), 0, 0)
ok = r0 != 0
return
}
// CreatePipe creates an anonymous pipe, and returns handles to the read and write ends of the pipe.
func CreatePipe(hReadPipe *Handle, hWritePipe *Handle, lpPipeAttributes *SecurityAttributes, nSize Dword) (ok bool) {
r0, _, _ := syscall.Syscall6(procCreatePipe.Addr(), 4, uintptr(unsafe.Pointer(hReadPipe)), uintptr(unsafe.Pointer(hWritePipe)), uintptr(unsafe.Pointer(lpPipeAttributes)), uintptr(nSize), 0, 0)
ok = r0 != 0
return
}
func GetLastError() (err error) {
r1, _, e1 := syscall.Syscall(procGetLastError.Addr(), 0, 0, 0, 0)
if r1 == 0 {
if e1 != 0 {
err = eerrnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func eerrnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case eerrnoERROR_IO_PENDING:
return eerrERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
const (
eerrnoERROR_IO_PENDING = 997
)
var (
eerrERROR_IO_PENDING error = syscall.Errno(eerrnoERROR_IO_PENDING)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment