Last active
January 10, 2019 23:38
-
-
Save siennathesane/26ea6a868a9e2d98af6ce07eaf8ffd74 to your computer and use it in GitHub Desktop.
Background processes in Windows with Go.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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