Go언어에 exec 패키지를 통해 exec.Command().Run 이나 Start등으로 외부 명령을 실행할 수 있으나 이 함수들의 단점은 CreateProcess 기반인데다 부가적인 옵션이 거의 존재하지 않습니다. 예를들면 Run 함수의 경우에는 실행시킨 명령이 종료되기 전까진 대기를 해야 하지만 콘솔 어플리케이션이 아닌 윈도 어플리케이션을 호출하는 경우 일반적으로 콘솔기준에선 바로 실행이 끝난것으로 넘어오기 때문에 .. 조금 맞지 않는 부분이 생기구요..
예를들어 윈도 프로그래밍에선 ShellExcuteEx 를 사용한뒤에 WaitForSingleEvent 같은 함수로 해당 프로세스의 종료까지 대기가 가능하고 명령어를 open 이 아닌 runAs 같은걸 지정하면 관리자권한으로 실행등의 부가적인 효과를 얻을 수 있습니다.
아무래도 Go언어는 멀티플랫폼을 기준하다보니 모든 플랫폼의 세부적인 API에 대응되진 않는 편이라 그런데요..
그런 이유로 윈도에서 좀더 상세한 옵션을 포함하여 외부 프로세스를 실행하고자 할때는 ShellExcute, ShellExcuteEx함수를 사용하는 편입니다.
아래 코드는 제가 작성한 코드는 아닙니다.. 웹서핑 과정에서 .. 코드프로젝트 였나.. 스택오버플로였나;; 둘중 하나였던것 같은데.. 어떤 질문의 답변을 쫒아가다 보니 .. 나온 코드긴 한데 .. 작성자 정보를 찾게 되면 추가로 수정해 두도록 하겠습니다.
언제나 많은 능력자들 덕에 저같은 미천한 개발자가 도움을 받고 사는것 같네요.
package main
import (
"errors"
"fmt"
"os"
"syscall"
"unsafe"
)
var (
modshell32 = syscall.NewLazyDLL("shell32.dll")
procShellExecuteEx = modshell32.NewProc("ShellExecuteExW")
)
const (
_SEE_MASK_DEFAULT = 0x00000000
_SEE_MASK_CLASSNAME = 0x00000001
_SEE_MASK_CLASSKEY = 0x00000003
_SEE_MASK_IDLIST = 0x00000004
_SEE_MASK_INVOKEIDLIST = 0x0000000C
_SEE_MASK_ICON = 0x00000010
_SEE_MASK_HOTKEY = 0x00000020
_SEE_MASK_NOCLOSEPROCESS = 0x00000040
_SEE_MASK_CONNECTNETDRV = 0x00000080
_SEE_MASK_NOASYNC = 0x00000100
_SEE_MASK_FLAG_DDEWAIT = 0x00000100
_SEE_MASK_DOENVSUBST = 0x00000200
_SEE_MASK_FLAG_NO_UI = 0x00000400
_SEE_MASK_UNICODE = 0x00004000
_SEE_MASK_NO_CONSOLE = 0x00008000
_SEE_MASK_ASYNCOK = 0x00100000
_SEE_MASK_NOQUERYCLASSSTORE = 0x01000000
_SEE_MASK_HMONITOR = 0x00200000
_SEE_MASK_NOZONECHECKS = 0x00800000
_SEE_MASK_WAITFORINPUTIDLE = 0x02000000
_SEE_MASK_FLAG_LOG_USAGE = 0x04000000
_SEE_MASK_FLAG_HINST_IS_SITE = 0x08000000
)
const (
_ERROR_BAD_FORMAT = 11
)
const (
_SE_ERR_FNF = 2
_SE_ERR_PNF = 3
_SE_ERR_ACCESSDENIED = 5
_SE_ERR_OOM = 8
_SE_ERR_DLLNOTFOUND = 32
_SE_ERR_SHARE = 26
_SE_ERR_ASSOCINCOMPLETE = 27
_SE_ERR_DDETIMEOUT = 28
_SE_ERR_DDEFAIL = 29
_SE_ERR_DDEBUSY = 30
_SE_ERR_NOASSOC = 31
)
type (
dword uint32
hinstance syscall.Handle
hkey syscall.Handle
hwnd syscall.Handle
ulong uint32
lpctstr uintptr
lpvoid uintptr
)
// SHELLEXECUTEINFO struct
type _SHELLEXECUTEINFO struct {
cbSize dword
fMask ulong
hwnd hwnd
lpVerb lpctstr
lpFile lpctstr
lpParameters lpctstr
lpDirectory lpctstr
nShow int
hInstApp hinstance
lpIDList lpvoid
lpClass lpctstr
hkeyClass hkey
dwHotKey dword
hIconOrMonitor syscall.Handle
hProcess syscall.Handle
}
// _ShellExecuteAndWait is version of ShellExecuteEx which want process
func _ShellExecuteAndWait(hwnd hwnd, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error {
var lpctstrVerb, lpctstrParameters, lpctstrDirectory lpctstr
if len(lpOperation) != 0 {
lpctstrVerb = lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpOperation)))
}
if len(lpParameters) != 0 {
lpctstrParameters = lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpParameters)))
}
if len(lpDirectory) != 0 {
lpctstrDirectory = lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpDirectory)))
}
i := &_SHELLEXECUTEINFO{
fMask: _SEE_MASK_NOCLOSEPROCESS,
hwnd: hwnd,
lpVerb: lpctstrVerb,
lpFile: lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpFile))),
lpParameters: lpctstrParameters,
lpDirectory: lpctstrDirectory,
nShow: nShowCmd,
}
i.cbSize = dword(unsafe.Sizeof(*i))
return _ShellExecuteEx(i)
}
// _ShellExecuteNoWait is version of ShellExecuteEx which don't want process
func _ShellExecuteNowait(hwnd hwnd, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error {
var lpctstrVerb, lpctstrParameters, lpctstrDirectory lpctstr
if len(lpOperation) != 0 {
lpctstrVerb = lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpOperation)))
}
if len(lpParameters) != 0 {
lpctstrParameters = lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpParameters)))
}
if len(lpDirectory) != 0 {
lpctstrDirectory = lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpDirectory)))
}
i := &_SHELLEXECUTEINFO{
fMask: _SEE_MASK_DEFAULT,
hwnd: hwnd,
lpVerb: lpctstrVerb,
lpFile: lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpFile))),
lpParameters: lpctstrParameters,
lpDirectory: lpctstrDirectory,
nShow: nShowCmd,
}
i.cbSize = dword(unsafe.Sizeof(*i))
return _ShellExecuteEx(i)
}
// ShellExecuteEx is Windows API
func _ShellExecuteEx(pExecInfo *_SHELLEXECUTEINFO) error {
ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(pExecInfo)))
if ret == 1 && pExecInfo.fMask&_SEE_MASK_NOCLOSEPROCESS != 0 {
s, e := syscall.WaitForSingleObject(syscall.Handle(pExecInfo.hProcess), syscall.INFINITE)
switch s {
case syscall.WAIT_OBJECT_0:
break
case syscall.WAIT_FAILED:
return os.NewSyscallError("WaitForSingleObject", e)
default:
return errors.New("Unexpected result from WaitForSingleObject")
}
}
errorMsg := ""
if pExecInfo.hInstApp != 0 && pExecInfo.hInstApp <= 32 {
switch int(pExecInfo.hInstApp) {
case _SE_ERR_FNF:
errorMsg = "The specified file was not found"
case _SE_ERR_PNF:
errorMsg = "The specified path was not found"
case _ERROR_BAD_FORMAT:
errorMsg = "The .exe file is invalid (non-Win32 .exe or error in .exe image)"
case _SE_ERR_ACCESSDENIED:
errorMsg = "The operating system denied access to the specified file"
case _SE_ERR_ASSOCINCOMPLETE:
errorMsg = "The file name association is incomplete or invalid"
case _SE_ERR_DDEBUSY:
errorMsg = "The DDE transaction could not be completed because other DDE transactions were being processed"
case _SE_ERR_DDEFAIL:
errorMsg = "The DDE transaction failed"
case _SE_ERR_DDETIMEOUT:
errorMsg = "The DDE transaction could not be completed because the request timed out"
case _SE_ERR_DLLNOTFOUND:
errorMsg = "The specified DLL was not found"
case _SE_ERR_NOASSOC:
errorMsg = "There is no application associated with the given file name extension"
case _SE_ERR_OOM:
errorMsg = "There was not enough memory to complete the operation"
case _SE_ERR_SHARE:
errorMsg = "A sharing violation occurred"
default:
errorMsg = fmt.Sprintf("Unknown error occurred with error code %v", pExecInfo.hInstApp)
}
} else {
return nil
}
return errors.New(errorMsg)
}