-
-
Save mholt/f52b56653de12dd9075db1b2ce914bd8 to your computer and use it in GitHub Desktop.
| package main | |
| import ( | |
| "github.com/kardianos/service" | |
| "log" | |
| "flag" | |
| ) | |
| type Service struct {} | |
| var logger service.Logger | |
| func (*Service) Start(_ service.Service) error { | |
| if err := StartProcessAsCurrentUser("notepad.exe", "", ""); err != nil { | |
| return err | |
| } | |
| return nil | |
| } | |
| func (*Service) Stop(_ service.Service) error { | |
| return nil | |
| } | |
| var serviceFlag = flag.String("service", "", "Control the service") | |
| func main() { | |
| svcConfig := &service.Config{ | |
| Name: "RunAsUserTest", | |
| DisplayName: "Run As User Test", | |
| Description: "Service to test launching programs as user from service", | |
| } | |
| svc := &Service{} | |
| s, err := service.New(svc, svcConfig) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| flag.Parse() | |
| if *serviceFlag != "" { | |
| if err := service.Control(s, *serviceFlag); err != nil { | |
| log.Fatal(err) | |
| } | |
| return | |
| } | |
| logger, err = s.Logger(nil) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| err = s.Run() | |
| if err != nil { | |
| logger.Error(err) | |
| } | |
| } |
| package main | |
| import ( | |
| "fmt" | |
| "unsafe" | |
| "golang.org/x/sys/windows" | |
| ) | |
| var ( | |
| modwtsapi32 *windows.LazyDLL = windows.NewLazySystemDLL("wtsapi32.dll") | |
| modkernel32 *windows.LazyDLL = windows.NewLazySystemDLL("kernel32.dll") | |
| modadvapi32 *windows.LazyDLL = windows.NewLazySystemDLL("advapi32.dll") | |
| moduserenv *windows.LazyDLL = windows.NewLazySystemDLL("userenv.dll") | |
| procWTSEnumerateSessionsW *windows.LazyProc = modwtsapi32.NewProc("WTSEnumerateSessionsW") | |
| procWTSGetActiveConsoleSessionId *windows.LazyProc = modkernel32.NewProc("WTSGetActiveConsoleSessionId") | |
| procWTSQueryUserToken *windows.LazyProc = modwtsapi32.NewProc("WTSQueryUserToken") | |
| procDuplicateTokenEx *windows.LazyProc = modadvapi32.NewProc("DuplicateTokenEx") | |
| procCreateEnvironmentBlock *windows.LazyProc = moduserenv.NewProc("CreateEnvironmentBlock") | |
| procCreateProcessAsUser *windows.LazyProc = modadvapi32.NewProc("CreateProcessAsUserW") | |
| ) | |
| const ( | |
| WTS_CURRENT_SERVER_HANDLE uintptr = 0 | |
| ) | |
| type WTS_CONNECTSTATE_CLASS int | |
| const ( | |
| WTSActive WTS_CONNECTSTATE_CLASS = iota | |
| WTSConnected | |
| WTSConnectQuery | |
| WTSShadow | |
| WTSDisconnected | |
| WTSIdle | |
| WTSListen | |
| WTSReset | |
| WTSDown | |
| WTSInit | |
| ) | |
| type SECURITY_IMPERSONATION_LEVEL int | |
| const ( | |
| SecurityAnonymous SECURITY_IMPERSONATION_LEVEL = iota | |
| SecurityIdentification | |
| SecurityImpersonation | |
| SecurityDelegation | |
| ) | |
| type TOKEN_TYPE int | |
| const ( | |
| TokenPrimary TOKEN_TYPE = iota + 1 | |
| TokenImpersonazion | |
| ) | |
| type SW int | |
| const ( | |
| SW_HIDE SW = 0 | |
| SW_SHOWNORMAL = 1 | |
| SW_NORMAL = 1 | |
| SW_SHOWMINIMIZED = 2 | |
| SW_SHOWMAXIMIZED = 3 | |
| SW_MAXIMIZE = 3 | |
| SW_SHOWNOACTIVATE = 4 | |
| SW_SHOW = 5 | |
| SW_MINIMIZE = 6 | |
| SW_SHOWMINNOACTIVE = 7 | |
| SW_SHOWNA = 8 | |
| SW_RESTORE = 9 | |
| SW_SHOWDEFAULT = 10 | |
| SW_MAX = 1 | |
| ) | |
| type WTS_SESSION_INFO struct { | |
| SessionID windows.Handle | |
| WinStationName *uint16 | |
| State WTS_CONNECTSTATE_CLASS | |
| } | |
| const ( | |
| CREATE_UNICODE_ENVIRONMENT uint16 = 0x00000400 | |
| CREATE_NO_WINDOW = 0x08000000 | |
| CREATE_NEW_CONSOLE = 0x00000010 | |
| ) | |
| // GetCurrentUserSessionId will attempt to resolve | |
| // the session ID of the user currently active on | |
| // the system. | |
| func GetCurrentUserSessionId() (windows.Handle, error) { | |
| sessionList, err := WTSEnumerateSessions() | |
| if err != nil { | |
| return 0xFFFFFFFF, fmt.Errorf("get current user session token: %s", err) | |
| } | |
| for i := range sessionList { | |
| if sessionList[i].State == WTSActive { | |
| return sessionList[i].SessionID, nil | |
| } | |
| } | |
| if sessionId, _, err := procWTSGetActiveConsoleSessionId.Call(); sessionId == 0xFFFFFFFF { | |
| return 0xFFFFFFFF, fmt.Errorf("get current user session token: call native WTSGetActiveConsoleSessionId: %s", err) | |
| } else { | |
| return windows.Handle(sessionId), nil | |
| } | |
| } | |
| // WTSEnumerateSession will call the native | |
| // version for Windows and parse the result | |
| // to a Golang friendly version | |
| func WTSEnumerateSessions() ([]*WTS_SESSION_INFO, error) { | |
| var ( | |
| sessionInformation windows.Handle = windows.Handle(0) | |
| sessionCount int = 0 | |
| sessionList []*WTS_SESSION_INFO = make([]*WTS_SESSION_INFO, 0) | |
| ) | |
| if returnCode, _, err := procWTSEnumerateSessionsW.Call(WTS_CURRENT_SERVER_HANDLE, 0, 1, uintptr(unsafe.Pointer(&sessionInformation)), uintptr(unsafe.Pointer(&sessionCount))); returnCode == 0 { | |
| return nil, fmt.Errorf("call native WTSEnumerateSessionsW: %s", err) | |
| } | |
| structSize := unsafe.Sizeof(WTS_SESSION_INFO{}) | |
| current := uintptr(sessionInformation) | |
| for i := 0; i < sessionCount; i++ { | |
| sessionList = append(sessionList, (*WTS_SESSION_INFO)(unsafe.Pointer(current))) | |
| current += structSize | |
| } | |
| return sessionList, nil | |
| } | |
| // DuplicateUserTokenFromSessionID will attempt | |
| // to duplicate the user token for the user logged | |
| // into the provided session ID | |
| func DuplicateUserTokenFromSessionID(sessionId windows.Handle) (windows.Token, error) { | |
| var ( | |
| impersonationToken windows.Handle = 0 | |
| userToken windows.Token = 0 | |
| ) | |
| if returnCode, _, err := procWTSQueryUserToken.Call(uintptr(sessionId), uintptr(unsafe.Pointer(&impersonationToken))); returnCode == 0 { | |
| return 0xFFFFFFFF, fmt.Errorf("call native WTSQueryUserToken: %s", err) | |
| } | |
| if returnCode, _, err := procDuplicateTokenEx.Call(uintptr(impersonationToken), 0, 0, uintptr(SecurityImpersonation), uintptr(TokenPrimary), uintptr(unsafe.Pointer(&userToken))); returnCode == 0 { | |
| return 0xFFFFFFFF, fmt.Errorf("call native DuplicateTokenEx: %s", err) | |
| } | |
| if err := windows.CloseHandle(impersonationToken); err != nil { | |
| return 0xFFFFFFFF, fmt.Errorf("close windows handle used for token duplication: %s", err) | |
| } | |
| return userToken, nil | |
| } | |
| func StartProcessAsCurrentUser(appPath, cmdLine, workDir string) error { | |
| var ( | |
| sessionId windows.Handle | |
| userToken windows.Token | |
| envInfo windows.Handle | |
| startupInfo windows.StartupInfo | |
| processInfo windows.ProcessInformation | |
| commandLine uintptr = 0 | |
| workingDir uintptr = 0 | |
| err error | |
| ) | |
| if sessionId, err = GetCurrentUserSessionId(); err != nil { | |
| return err | |
| } | |
| if userToken, err = DuplicateUserTokenFromSessionID(sessionId); err != nil { | |
| return fmt.Errorf("get duplicate user token for current user session: %s", err) | |
| } | |
| if returnCode, _, err := procCreateEnvironmentBlock.Call(uintptr(unsafe.Pointer(&envInfo)), uintptr(userToken), 0); returnCode == 0 { | |
| return fmt.Errorf("create environment details for process: %s", err) | |
| } | |
| creationFlags := CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | |
| startupInfo.ShowWindow = SW_SHOW | |
| startupInfo.Desktop = windows.StringToUTF16Ptr("winsta0\\default") | |
| if len(cmdLine) > 0 { | |
| commandLine = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(cmdLine))) | |
| } | |
| if len(workDir) > 0 { | |
| workingDir = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(workDir))) | |
| } | |
| if returnCode, _, err := procCreateProcessAsUser.Call( | |
| uintptr(userToken), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(appPath))), commandLine, 0, 0, 0, | |
| uintptr(creationFlags), uintptr(envInfo), workingDir, uintptr(unsafe.Pointer(&startupInfo)), uintptr(unsafe.Pointer(&processInfo)), | |
| ); returnCode == 0 { | |
| return fmt.Errorf("create process as user: %s", err) | |
| } | |
| return nil | |
| } |
To support that above statement, calling WTSQueryUserToken() requires your service process to have SE_TCB_NAME privilege enabled which is available to an NT USER(SYSTEM) which is part of the trusted computer base.
After register this RunAsUserTest as a windows service by command : main.exe -service install and main.exe -service start, it won't be error like "WTSQueryUserToken: A required privilege is not held by the client", works fine, good job.
Hey!
I used your code in my project and modified it to start the process for all active users.
I noticed a memory leak when I repeatedly call WTSEnumerateSessionsW.
I looked at the MS documentation I suggested that I need to free the WTS_SESSION_INFO memory in the WTSEnumerateSessionsW function. I tried to free the WTS_SESSION_INFO memory area
by calling:
modwtsapi32a = syscall.NewLazyDLL ("wtsapi32.dll")
procWTSFreeMemory = modwtsapi32a.NewProc ("WTSFreeMemory")
syscall.Syscall (procWTSFreeMemory.Addr (), 1, uintptr (unsafe.Pointer (& sessionInformation)), 0, 0)
But the memory still leaks away. What else could be causing the leak?
Excellent gits!!!
But when I want to hide the console window using const CREATE_NO_WINDOW, it failed to compile:
And I searched MSDN:
Then I changed uint16 to uint32, and it worked!
const (
CREATE_UNICODE_ENVIRONMENT uint32 = 0x00000400 // changed from uint16 to uint32
CREATE_NO_WINDOW = 0x08000000
CREATE_NEW_CONSOLE = 0x00000010
)

@BelodedAleksey Might be a bit late to the party, but if it can help someone, your service needs to be running as SYSTEM for this to work. Works for me.