Search code examples
c#winapiwindows-services

How to use Win32Api in C# Windows Service App


I'm creating a windows service app in visual studio and I want to get the currently active window title. below is the code I have tried but the function GetForegroundWindow() returns 0 every time. Is it okay to use win32api in windows services?

public partial class My_Service: ServiceBase
{
    Timer timer = new Timer();
    [DllImport("User32.dll")]
    static extern IntPtr GetForegroundWindow();

    [DllImport("User32.dll")]
    static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);

    public My_Service()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        WriteToFile("Service is started at " + DateTime.Now);
        timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);
        timer.Interval = 10000; //number in milisecinds  
        timer.Enabled = true;
    }

    protected override void OnStop()
    {
        WriteToFile("Service is stopped at " + DateTime.Now);
    }

    private void OnElapsedTime(object source, ElapsedEventArgs e)
    {
        string title = GetActivewindow();
        WriteToFile("Service is recall at " + title + DateTime.Now);

    }

    public void WriteToFile(string Message)
    {
       ....
    }

    public string GetActivewindow()
    {
        const int nChars = 256;
        StringBuilder buff = new StringBuilder(nChars);
        string title = "- ";
        IntPtr handle; 
        handle = GetForegroundWindow();
        if( GetWindowText(handle,buff,nChars) > 0)
        {
            title = buff.ToString();
        }
        return title;
    }
}

Solution

  • As comments, and also according to the document: About Window Stations and Desktops - Window Station and Desktop Creation.

    Your service may be running under Service-0x0-3e7$\default, but the foreground window you want to retrieve is in the default desktop of the interactive window station (Winsta0\default)

    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.

    That means you cannot use GetForegroundWindow directly from the service. You could create a child process in Winsta0\default and redirect its output. Then call GetForegroundWindow in the child process and output.

    Sample(removed error checking):

    Child:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Runtime.InteropServices;
    using System.IO;
    using System.Threading;
    namespace ConsoleApp1
    {
        class Program
        {
            [DllImport("User32.dll")]
            static extern IntPtr GetForegroundWindow();
    
            [DllImport("User32.dll")]
            static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
            static void Main(string[] args)
            {
                const int nChars = 256;
                StringBuilder buff = new StringBuilder(nChars);
                string title = "- ";
                IntPtr handle;
                handle = GetForegroundWindow();
                if (GetWindowText(handle, buff, nChars) > 0)
                {
                    title = buff.ToString();
                }
                Console.WriteLine(title);
                return;
            }
        }
    }
    

    Service:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Diagnostics;
    using System.Linq;
    using System.ServiceProcess;
    using System.Text;
    using System.Threading.Tasks;
    using System.Runtime.InteropServices;
    using System.IO;
    using System.Diagnostics.Tracing;
    
    namespace WindowsService3
    {
        public partial class MyNewService : ServiceBase
        {
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            internal struct STARTUPINFO
            {
                public Int32 cb;
                public string lpReserved;
                public string lpDesktop;
                public string lpTitle;
                public Int32 dwX;
                public Int32 dwY;
                public Int32 dwXSize;
                public Int32 dwYSize;
                public Int32 dwXCountChars;
                public Int32 dwYCountChars;
                public Int32 dwFillAttribute;
                public Int32 dwFlags;
                public Int16 wShowWindow;
                public Int16 cbReserved2;
                public IntPtr lpReserved2;
                public IntPtr hStdInput;
                public IntPtr hStdOutput;
                public IntPtr hStdError;
            }
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            internal struct PROCESS_INFORMATION
            {
                public IntPtr hProcess;
                public IntPtr hThread;
                public Int32 dwProcessId;
                public Int32 dwThreadId;
            }
            [StructLayout(LayoutKind.Sequential)]
            public struct SECURITY_ATTRIBUTES
            {
                public int nLength;
                public IntPtr lpSecurityDescriptor;
                public int bInheritHandle;
            }
    
    
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            static extern bool CreateProcessAsUser(
                IntPtr hToken,
                string lpApplicationName,
                string lpCommandLine,
                IntPtr lpProcessAttributes,
                IntPtr lpThreadAttributes,
                bool bInheritHandles,
                uint dwCreationFlags,
                IntPtr lpEnvironment,
                IntPtr lpCurrentDirectory,
                ref STARTUPINFO lpStartupInfo,
                out PROCESS_INFORMATION lpProcessInformation);
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            static extern bool CreatePipe(
                ref IntPtr hReadPipe,
                ref IntPtr hWritePipe,
                ref SECURITY_ATTRIBUTES lpPipeAttributes,
                uint nSize);
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            static extern bool ReadFile(
                IntPtr hFile,
                byte[] lpBuffer,
                uint nNumberOfBytesToRead,
                out uint lpNumberOfBytesRead,
                IntPtr lpOverlapped);
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            static extern bool CloseHandle(IntPtr hObject);
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            static extern uint WTSGetActiveConsoleSessionId();
            [DllImport("Wtsapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            static extern bool WTSQueryUserToken(UInt32 SessionId, out IntPtr hToken);
    
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            static extern uint WaitForSingleObject(IntPtr hProcess, uint dwMilliseconds);
            public MyNewService()
            {
                InitializeComponent();
            }
    
            protected override void OnStart(string[] args)
            {
                System.Diagnostics.Debugger.Launch();
                using (StreamWriter sw = File.CreateText("Path\\Log.txt"))
                {
                    IntPtr read = new IntPtr();
                    IntPtr write = new IntPtr();
                    IntPtr read2 = new IntPtr();
                    IntPtr write2 = new IntPtr();
                    SECURITY_ATTRIBUTES saAttr = new SECURITY_ATTRIBUTES();
                    saAttr.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
                    saAttr.bInheritHandle = 1;
                    saAttr.lpSecurityDescriptor = IntPtr.Zero;
    
                    CreatePipe(ref read, ref write, ref saAttr, 0);
                    CreatePipe(ref read2, ref write2, ref saAttr, 0);
    
                    uint CREATE_NO_WINDOW = 0x08000000;
                    int STARTF_USESTDHANDLES = 0x00000100;
                    STARTUPINFO si = new STARTUPINFO();
                    si.cb = Marshal.SizeOf(typeof(STARTUPINFO));
                    si.hStdOutput = write;
                    si.hStdError = write;
                    si.hStdInput = read2;
                    si.lpDesktop = "Winsta0\\default";
                    si.dwFlags = STARTF_USESTDHANDLES;
                    PROCESS_INFORMATION pi;
    
                    IntPtr hToken;
                    bool err = WTSQueryUserToken(WTSGetActiveConsoleSessionId(), out hToken);
                    string path = "Path\\ConsoleApp1.exe";
                    if (CreateProcessAsUser(hToken, path, null, IntPtr.Zero, IntPtr.Zero, true, CREATE_NO_WINDOW, IntPtr.Zero, IntPtr.Zero, ref si, out pi))
                    {
                        uint ret = WaitForSingleObject(pi.hProcess, 2000); //wait for the child process exit.
                        if (ret == 0)
                        {
                            byte[] title = new byte[200];
                            uint reads = 0;
                            CloseHandle(write);
                            err = ReadFile(read, title, 200, out reads, IntPtr.Zero);
                            string result = System.Text.Encoding.UTF8.GetString(title).Replace("\0","").Replace("\r", "").Replace("\n", "");
    
                            sw.WriteLine(result);
    
                        }
                    }
                    CloseHandle(read2);
                    CloseHandle(write2);
                    CloseHandle(read);
                }
    
            }
    
            protected override void OnStop()
            {
    
            }
        }
    }