简体   繁体   中英

CreateProcessAsUser not working with LogonUser token when printing from a Windows Service

I am building a windows services under LocalSystem account that will print pdf in some time interval.

To do this I am creating user token by using LogonUser

IntPtr currentToken = IntPtr.Zero;
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON32_PROVIDER_DEFAULT = 0;
bool loggedOn = LogonUser(user, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref currentToken);

and passing this currentToken in CreateProcessAsUser

CreateProcessAsUser(primaryToken, null, command, ref Security1, ref Security2, false, CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, null, ref StartupInfo, out processInfo_);

But it's not doing it's job

If I get the current user token by using following code.

public static IntPtr GetCurrentUserToken()
{
    IntPtr currentToken = IntPtr.Zero;
    IntPtr primaryToken = IntPtr.Zero;
    IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;

    int dwSessionId = 0;
    IntPtr hUserToken = IntPtr.Zero;
    IntPtr hTokenDup = IntPtr.Zero;

    IntPtr pSessionInfo = IntPtr.Zero;
    int dwCount = 0;

    WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref dwCount);

    Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));

    Int32 current = (int)pSessionInfo;
    for (int i = 0; i < dwCount; i++)
    {
        WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO));
        if (WTS_CONNECTSTATE_CLASS.WTSActive == si.State)
        {
            dwSessionId = si.SessionID;
            break;
        }

        current += dataSize;
    }

    WTSFreeMemory(pSessionInfo);

    bool bRet = WTSQueryUserToken(dwSessionId, out currentToken);
    if (bRet == false)
    {
        return IntPtr.Zero;
    }

    bRet = DuplicateTokenEx(currentToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken);
    if (bRet == false)
    {
        return IntPtr.Zero;
    }

    return primaryToken;
}

Then CreateProcessAsUser works fine. But I need to create token by CreateProcessAsUser works fine. But I need to create token by LogonUser because after user logoff GetCurrentUserToken` method is not returning user token and I want user token also after logoff.

Update I am checking last error after calling CreateProcessAsUser like this

uint exitCode; 

if (!GetExitCodeProcess(processInfo_.hProcess, out exitCode)) 
{ 
    int lastError = Marshal.GetLastWin32Error(); 
    Logger.LogService(" GetExitCodeProcess Error " + lastError); 
} 

But GetExitCodeProcess returns true. I didn't found any error

Firstly

As with the last time you posted this question ( Print Pdf from windows service and keep working after logoff ), you need understand and workout what particular API call is failing and then call GetLastError which will give you more information about why the call is failing

GetLastError : Retrieves the calling thread's last-error code value

Secondly

It could be the case that one of the calls in GetCurrentUserToken eg WTSQueryUserToken might just have a permission problem or something else that can be fixed (though i doubt it)

Reading the documentation for WTSQueryUserToken it seems to state the following

WTSQueryUserToken : Obtains the primary access token of the logged-on user specified by the session ID.

Further more, WTSQueryUserToken is probably returning

ERROR_NO_TOKEN
1008

ERROR_NO_TOKEN : The token query is for a session in which no user is logged-on . This occurs, for example, when the session is in the idle state or SessionId is zero.

Thirdly

In my opinion, I don't think this approach is ever going to work in your situation and printing from a service is actually not recommended officially.

Please see the blow similar questions

Printing from a Windows Service

Printing from a .NET Service

Lastly

The only thing i can think of, is running you service under a User Account with access to the printers or impersonating a user by calling LogonUser , LoadUserProfile , and ImpersonateLoggedOnUser then printing via Gdi+ (which is going have its own problems)

Please note

While it is possible to send GDI+ output to a printer by obtaining a device context handle for the printer and then passing that handle to a GDI+ Graphics constructor, this is not recommended. The GDI+ functions and classes are not supported for use within a Windows service. Attempting to use these functions and classes from a Windows service may produce unexpected problems, such as diminished service performance and run-time exceptions or errors:


In short, you solution as it stands and the source code you currently have is unlikely to work, and in my opinion you are most likely going to spend a lot of time on this problem trying to do something services are really not designed to do (which is also out of the scope of the current question).

I really think you need to seriously reconsider what you are trying to do and why and wish you the best of luck

Additional information on GDI Printing and printing from a service

How to install printer which should be accessible from windows service?

Printing from a Windows Service

You didn't give any information about your printing process, but from what you provided, I guess it must need to be run under interactive session. Your first way didn't work because the token provede by LogonUser has same session id as the calling process which is 0. And your second way didn't work after log out is because WTSQueryUserToken will fail as it suppose to. So I suggest you try winlogon solution which could be summarized as find out winlogon process of interactive session, duplicate its token and use the token to create you printing process. This acticle has all the details.

Blatantly stolen from the linked pages.

Typically, the process that calls the CreateProcessAsUser function must have the SE_INCREASE_QUOTA_NAME.

The LocalSystem account has the following privileges: ... SE_INCREASE_QUOTA_NAME (disabled) So I am surprised that CreateProcessAsUser ever works in LocalSystem context.

CreateProcessAsUser does not load the specified user's profile into the HKEY_USERS registry key. Therefore, to access the information in the HKEY_CURRENT_USER registry key, you must load the user's profile information into HKEY_USERS with the LoadUserProfile function before calling CreateProcessAsUser. Be sure to call UnloadUserProfile after the new process exits.

CreateProcessAsUser seems a bit more picky than CreateProcessWithLogonW. I would try CreateProcessWithLogonW with the LOGON_WITH_PROFILE parameter.

You call DuplicateTokenEx on the token in the code that seems to work. You might want to try that with the LogonUser token.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM