Recai Cingöz
Recai Cingöz

Reputation: 49

How can I run the application written in Golang as a Windows service

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

Answers (2)

Recai Cing&#246;z
Recai Cing&#246;z

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

Strive Sun
Strive Sun

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

Related Questions