【问题标题】:CreateProcessAsUser not working with LogonUser token when printing from a Windows Service从 Windows 服务打印时,CreateProcessAsUser 不使用 LogonUser 令牌
【发布时间】:2018-01-29 05:37:59
【问题描述】:

我正在 LocalSystem 帐户下构建一个 Windows 服务,它将在某个时间间隔内打印 pdf。

为此,我使用 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);

并将这个currentToken 传递给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_);

但它没有做它的工作

如果我使用以下代码获取当前用户令牌。

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;
}

然后CreateProcessAsUser works fine. But I need to create token byLogonUserbecause after user logoffGetCurrentUserToken`方法没有返回用户令牌,我也想要用户令牌在注销后。

更新 像这样调用CreateProcessAsUser 后,我正在检查最后一个错误

uint exitCode; 

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

但 GetExitCodeProcess 返回 true。我没有发现任何错误

【问题讨论】:

    标签: c# windows-services pinvoke createprocessasuser


    【解决方案1】:

    首先

    与您上次发布此问题 (Print Pdf from windows service and keep working after logoff) 一样,您需要了解和锻炼哪些特定 API 调用失败,然后调用 GetLastError,这将为您提供有关调用失败原因的更多信息

    GetLastError : 获取调用线程的最后一个错误码值

    其次

    可能是GetCurrentUserToken 中的一个调用,例如WTSQueryUserToken 可能只是存在权限问题或其他可以修复的问题(尽管我对此表示怀疑)

    阅读WTSQueryUserToken 的文档似乎说明了以下内容

    WTSQueryUserToken :获取登录用户指定的主访问令牌 会话 ID。

    此外,WTSQueryUserToken 可能正在回归

    ERROR_NO_TOKEN
    1008
    

    ERROR_NO_TOKEN:令牌查询是针对没有用户登录的会话。这 例如,当会话处于空闲状态或 SessionId 为零。

    第三

    在我看来,我认为这种方法在您的情况下永远不会奏效,并且实际上不推荐从服务打印。

    请看类似的问题

    Printing from a Windows Service

    Printing from a .NET Service

    最后

    我能想到的唯一一件事就是在可以访问打印机的用户帐户下运行您的服务,或者通过调用 LogonUserLoadUserProfileImpersonateLoggedOnUser 来模拟用户,然后通过 Gdi+ 打印(即将有自己的问题)

    请注意

    虽然可以通过获取一个 打印机的设备上下文句柄,然后将该句柄传递给 GDI+ 图形构造函数,不建议这样做。 GDI+ 不支持在 Windows 中使用函数和类 服务。尝试从 Windows 中使用这些函数和类 服务可能会产生意想不到的问题,例如服务减少 性能和运行时异常或错误:


    简而言之,您现有的解决方案和您当前拥有的源代码不太可能工作,在我看来,您很可能会在这个问题上花费大量时间来尝试做一些服务实际上不是设计的事情做(这也超出了当前问题的范围)。

    我真的认为你需要认真重新考虑你正在尝试做什么以及为什么并祝你好运

    有关 GDI 打印和从服务打印的其他信息

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

    Printing from a Windows Service

    【讨论】:

    • 我正在检查这样的最后一个错误uint exitCode; if (!GetExitCodeProcess(processInfo_.hProcess, out exitCode)) { int lastError = Marshal.GetLastWin32Error(); Logger.LogService(" GetExitCodeProcess Error " + lastError); }GetExitCodeProcess 返回true。我没有发现任何错误
    【解决方案2】:

    从链接页面公然窃取。

    通常,调用CreateProcessAsUser 函数的进程必须具有 SE_INCREASE_QUOTA_NAME。

    LocalSystem 帐户具有以下权限: ... SE_INCREASE_QUOTA_NAME(已禁用) 所以我很惊讶 CreateProcessAsUser 曾经在 LocalSystem 上下文中工作。

    CreateProcessAsUser 不会将指定用户的配置文件加载到 HKEY_USERS 注册表项中。因此,要访问 HKEY_CURRENT_USER 注册表项中的信息,您必须在调用 CreateProcessAsUser 之前使用 LoadUserProfile 函数将用户的配置文件信息加载到 HKEY_USERS 中。请务必在新进程退出后调用 UnloadUserProfile。

    CreateProcessAsUser 似乎比 CreateProcessWithLogonW 更挑剔。我会尝试使用带有 LOGON_WITH_PROFILE 参数的CreateProcessWithLogonW

    您在似乎有效的代码中对令牌调用 DuplicateTokenEx。您可能想尝试使用 LogonUser 令牌。

    【讨论】:

      【解决方案3】:

      您没有提供有关打印过程的任何信息,但根据您提供的信息,我想它必须需要在交互式会话下运行。您的第一种方法不起作用,因为LogonUser 证明的令牌与调用进程具有相同的会话ID,即0。而您的第二种方法在注销后不起作用是因为WTSQueryUserToken 将失败,因为它假设。所以我建议你尝试winlogon解决方案,可以概括为找出交互式会话的winlogon进程,复制它的令牌并使用令牌来创建你的打印进程。 This acticle 有所有细节。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-03
        • 1970-01-01
        • 1970-01-01
        • 2017-01-17
        相关资源
        最近更新 更多