Reputation: 49
I cannot use the screenshot application that I developed with the Win32 api as a Windows Background service. I install and run it as a Windows background service, I have no problem so far. My problem: The service doesn't give me a printout. Doesn't take a screenshot. I tried making another simple app. I tried sending a message using the OutputDebugStringW function but my problem was not resolved.
My Windows background service that is not producing output
package main
import (
"fmt"
"log"
"time"
"github.com/checkgo/win"
"github.com/kardianos/service"
)
var logger service.Logger
type program struct {
exit chan struct{}
}
func (p *program) Start(s service.Service) error {
if service.Interactive() {
logger.Info("Running in terminal.")
} else {
logger.Info("Running under service manager.")
}
p.exit = make(chan struct{})
// Start should not block. Do the actual work async.
go p.run()
return nil
}
func (p *program) run() {
logger.Infof("I'm running %v.", service.Platform())
ticker := time.NewTicker(2 * time.Second)
for {
select {
case tm := <-ticker.C:
win.OutputDebugString(fmt.Sprintf("%s : %v", "This is test message", tm))
case <-p.exit:
ticker.Stop()
}
} // link to whaterver image from the web
}
func (p *program) Stop(s service.Service) error {
// Stop should not block. Return with a few seconds.
return nil
}
func main() {
svcConfig := &service.Config{
Name: "GoServiceExampleSimple",
DisplayName: "Go Service Example",
Description: "This is an example Go service.",
}
prg := &program{}
s, err := service.New(prg, svcConfig)
if err != nil {
log.Fatal(err)
}
logger, err = s.Logger(nil)
if err != nil {
log.Fatal(err)
}
err = s.Run()
if err != nil {
logger.Error(err)
}
}
ScreenShot: DebugView and Windows Services Screen Capture
Upvotes: 2
Views: 9377
Reputation: 49
Solved with CreateProcessAsUser function.
GOO source codes that I used to solve problems.
Source code of the Windows background service application:
package main
import (
"log"
"reflect"
"sync"
"syscall"
"github.com/checkgo/win"
"github.com/kardianos/service"
"golang.org/x/sys/windows"
)
var logger service.Logger
type program struct {
exit chan struct{}
}
func (p *program) Start(s service.Service) error {
if service.Interactive() {
logger.Info("Running in terminal.")
} else {
logger.Info("Running under service manager.")
}
p.exit = make(chan struct{})
// Start should not block. Do the actual work async.
go p.run()
return nil
}
func (p *program) run() {
var wgT sync.WaitGroup
wgT.Add(1)
test(&wgT)
wgT.Wait()
}
func test(wg *sync.WaitGroup) {
var saAttr win.SECURITY_ATTRIBUTES
saAttr.NLength = uint32(reflect.TypeOf(syscall.SecurityAttributes{}).Size())
saAttr.BInheritHandle = win.TRUE
saAttr.LpSecurityDescriptor = uintptr(0)
var si syscall.StartupInfo
si.Cb = uint32(reflect.TypeOf(syscall.SecurityAttributes{}).Size())
si.Desktop = windows.StringToUTF16Ptr("Winsta0\\default")
si.Flags = windows.STARTF_USESTDHANDLES
var hToken windows.Token
id := win.WTSGetActiveConsoleSessionId()
err := windows.WTSQueryUserToken(uint32(id), &hToken)
if err != nil {
logger.Info(err)
}
path := windows.StringToUTF16Ptr("C:\\Users\\cingo\\go\\src\\srv\\agent\\test_agent.exe")
var pi syscall.ProcessInformation
syscall.CreateProcessAsUser(syscall.Token(hToken),
path,
nil,
nil,
nil,
true,
windows.CREATE_NO_WINDOW,
nil,
nil,
&si,
&pi)
}
func (p *program) Stop(s service.Service) error {
// Stop should not block. Return with a few seconds.
return nil
}
func main() {
svcConfig := &service.Config{
Name: "GoServiceExampleSimple",
DisplayName: "Go Service Example",
Description: "This is an example Go service.",
}
prg := &program{}
s, err := service.New(prg, svcConfig)
if err != nil {
log.Fatal(err)
}
logger, err = s.Logger(nil)
if err != nil {
log.Fatal(err)
}
err = s.Run()
if err != nil {
logger.Error(err)
}
}
Running application
package main
import (
"fmt"
"github.com/checkgo/win"
"time"
)
func main() {
for {
time.Sleep(time.Second * 1)
win.OutputDebugString(fmt.Sprintf("%s", "This is test message"))
}
}
Upvotes: 2
Reputation: 6289
If a service is running in the security context of the LocalSystem account but does not include the SERVICE_INTERACTIVE_PROCESS attribute, it uses the following window station and desktop: Service-0x0-3e7$\default. This window station is not interactive, so the service cannot display a user interface. In addition, processes created by the service cannot display a user interface.
Refer: Window Station and Desktop Creation
The interactive window station is the only window station that can display a user interface or receive user input. It is assigned to the logon session of the interactive user, and contains the keyboard, mouse, and display device. It is always named "WinSta0". All other window stations are noninteractive, which means they cannot display a user interface or receive user input.
Refer: Window Stations
It is said that screen you want to capture is in the default desktop of the interactive window station (Winsta0\default). You can create a child process in Winsta0\default
, and the child process is used for screenshots. Use CreateProcessAsUser to call the child process in the service.
Refer to the following C++ code. Although this is not a Go language, I think it is enough to be familiar with the call of winapi.
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
{
OutputDebugString(_T("My Sample Service: ServiceWorkerThread: Entry"));
WCHAR station[] = L"Winsta0\\default";
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = station;
si.dwFlags = STARTF_USESTDHANDLES;
PROCESS_INFORMATION pi;
HANDLE hToken;
bool err = WTSQueryUserToken(WTSGetActiveConsoleSessionId(), &hToken);
WCHAR path[] = L"D:\\child.exe"; //child.exe is used to take screenshots
if (CreateProcessAsUser(hToken, path, NULL, 0, 0, true, CREATE_NO_WINDOW, 0, 0, &si, &pi))
{
...
Upvotes: 2