Search code examples
javawindows-servicesjnainteractive

CreateProcessAsUser function to run a GUI program from a service


Below is the code I am using to run a GUI app from service application. I am passing cmd string "C:\Windows\notepad.exe".

It is not opening the Notepad and not even giving any error. hToken is null even after using WTSQueryUserToken. Here is a documentation link for create process as user : https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera

private void cpasUser(String cmd) {
        HANDLE h = null; 
        final HANDLEByReference childStdInRead = new HANDLEByReference();
        final HANDLEByReference childStdInWrite = new HANDLEByReference();
        final HANDLEByReference childStdOutRead = new HANDLEByReference();
        final HANDLEByReference childStdOutWrite = new HANDLEByReference();
        
        final int HANDLE_FLAG_INHERIT = 0x00000001;
        final int HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x00000002;
        
        final int BUFSIZE = 4096;
        final int GENERIC_READ = 0x80000000;
        final int FILE_ATTRIBUTE_READONLY = 1;
        final int OPEN_EXISTING = 3;
        final DWORD STD_OUTPUT_HANDLE = new DWORD(-11);
        final int STARTF_USESTDHANDLES = 0x00000100;
        
        String szCmdline = cmd;

        PROCESS_INFORMATION processInformation = new PROCESS_INFORMATION();
        STARTUPINFO startupInfo = new STARTUPINFO();
        startupInfo.cb = new DWORD(processInformation.size());
        startupInfo.hStdError = childStdOutWrite.getValue();
        startupInfo.hStdOutput = childStdOutWrite.getValue();
        startupInfo.hStdInput = childStdInRead.getValue();
        startupInfo.dwFlags |= STARTF_USESTDHANDLES;
 
        // Create the child process. 
        HANDLE hToken = null;
        MyWtsapi32 mw = MyWtsapi32.INSTANCE;
        mw.WTSQueryUserToken(Kernel32Ext.INSTANCE.WTSGetActiveConsoleSessionId(), hToken) ;

      //be sure that the handle is correct ! (can be the issue)
      if (hToken == null) logger.info("Token error.");
        if (!Advapi32.INSTANCE.CreateProcessAsUser(
                hToken, 
                szCmdline, 
                null, 
                null, 
                null,
                true, 
                32, 
                null, 
                null, 
                startupInfo, 
                processInformation)){
           // System.err.println(Advapi32.INSTANCE.GetLastError());
            logger.error("Cannot create process as User ");
            logger.error("error code "+Native.getLastError());
        }

MyWtsApi32.java

public interface MyWtsapi32 extends Wtsapi32 {
      // Your own instance to access your functions
     MyWtsapi32 INSTANCE =  Native.load("Wtsapi32", MyWtsapi32.class, W32APIOptions.DEFAULT_OPTIONS);
    
    // From https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox
    int MB_OK = 0; 

    // The function to send the message
      boolean WTSSendMessageW(HANDLE hServer, int SessionId,
        LPWSTR pTitle, int TitleLength,
        LPWSTR pMessage, int MessageLength,
        int Style, int Timeout, IntByReference pResponse, boolean bWait);
      
      boolean WTSQueryUserToken(long SessionId,HANDLE hServer);
}

Kernel32Ext.java

public interface Kernel32Ext extends Kernel32{
    Kernel32Ext INSTANCE = Native.load("Kernel32",Kernel32Ext.class,W32APIOptions.DEFAULT_OPTIONS);
    
    int WTSGetActiveConsoleSessionId();
}

Solution

  • When your JNA function mappings don't work, the first debugging step should be to check your function mappings.

    WTSQueryUserToken is defined as:

    BOOL WTSQueryUserToken(
        ULONG SessionId,
        PHANDLE phToken
    );
    

    The Windows type ULONG is an unsigned 32-bit integer; it should be mapped as int, not long.

    PHANDLE is a pointer to a HANDLE, not the handle itself. The correct JNA mapping is HANDLEByReference.

    So your interface function mapping should be:

    boolean WTSQueryUserToken(int SessionId, HANDLEByReference hServer);
    

    And your code to call it should be:

    HANDLEByReference phToken = new HANDLEByReference();
    MyWtsapi32 mw = MyWtsapi32.INSTANCE;
    // you should probably check the return value here
    // on failure throw LastErrorException
    mw.WTSQueryUserToken(Kernel32Ext.INSTANCE.WTSGetActiveConsoleSessionId(), phToken);
    
    // Extract the HANDLE for use in later code
    HANDLE hToken = phToken.getValue();