使用C#通过Windows服务进行PDF打印

8
我正在使用这段代码在Windows服务中使用C#将PDF文件打印到本地打印机。
Process process = new Process();
PrinterSettings printerSettings = new PrinterSettings();

if (!string.IsNullOrWhiteSpace(basePrint))
   printerSettings.PrinterName = basePrint;

process.StartInfo.FileName = fileName;
process.StartInfo.Verb = "printto";
process.StartInfo.Arguments = "\"" + printerSettings.PrinterName + "\"";
process.Start();
process.WaitForInputIdle();

当我设置一个用户运行Windows服务时,一切正常。

但是,当我使用LocalSystem凭据运行此代码时,出现错误“没有与此操作关联的应用程序”,这通常表示我没有一个处理带有.pdf扩展名的文件打印操作的程序。

我的问题是,我确实拥有能够处理此操作(Foxit Reader)的程序,这已通过使用特定用户设置服务并且我能够右键单击文件并选择打印选项来确认此事实。

有什么方法可以在没有具体用户的情况下从服务内部打印到本地打印机上吗?


它是什么类型的打印机?本地安装还是通过\计算机名\打印机名共享网络打印机? - sa.he
这是一台Epson EcoTank L375打印机(https://epson.com.jm/Support/Printers/All-In-Ones/L-Series/Epson-EcoTank-L375/s/SPT_C11CE92301),已经本地安装。 - Caio Sant'Anna
4个回答

4
我最终使用pdfium来完成这项工作。有了那段代码,即使Windows服务在LocalService用户下运行,PDF文件也能正确地发送到打印机。
PrinterSettings printerSettings = new PrinterSettings()
{
    PrinterName = printerName,
    Copies = 1
};

PageSettings pageSettings = new PageSettings(printerSettings)
{
    Margins = new Margins(0, 0, 0, 0)
};

foreach (PaperSize paperSize in printerSettings.PaperSizes)
{
    if (paperSize.PaperName == "A4")
    {
        pageSettings.PaperSize = paperSize;
        break;
    }
}

using (PdfDocument pdfDocument = PdfDocument.Load(filePath))
{
    using (PrintDocument printDocument = pdfDocument.CreatePrintDocument())
    {
        printDocument.PrinterSettings = printerSettings;
        printDocument.DefaultPageSettings = pageSettings;
        printDocument.PrintController = (PrintController) new     StandardPrintController();
        printDocument.Print();
    }
}

感谢大家的回答。

0

可能是因为PDF应用程序不在系统范围内的PATH变量中,而只在您特定的用户下?

我认为您的问题是因为“本地系统”用户找不到适当的应用程序,所以您需要为他注册它。由于您已经接受了另一个答案,我不会再花更多时间在这个问题上,但如果有进一步的问题,请随时提问。


0

问题可能是SYSTEM(LocalSystem)帐户具有有限的用户界面功能,可能已删除或禁用了shell或shell扩展。而动词是shell子系统的一种能力,特别是Explorer。

您可以手动调用程序以查看是否存在此问题,或者是安全问题或缺乏用户配置文件详细信息。

为此,您需要深入研究注册表,并发现许多shell执行扩展动词具有命令行。

例如,查找HKEY_CLASSES_ROOT.pdf\shell\printto\command并使用该命令。

此外,您还可以检查SYSTEM帐户是否可以访问此键和父键。(很少出现但值得检查)


0

你可能可以执行你的工作代码,但需要使用当前活动会话用户令牌(但如果没有活动会话,则不应该起作用)

为了简洁起见,此代码无法编译:您必须先适应并添加一些P/Invoke

你必须找到活动会话ID。对于本地打开的会话,请使用以下方法:

    [DllImport("wtsapi32.dll", SetLastError = true)]
    public static extern int WTSEnumerateSessions(
        IntPtr hServer,
        int Reserved,
        int Version,
        ref IntPtr ppSessionInfo,
        ref int pCount);

然后搜索已打开的会话 ID:

        var typeSessionInfo = typeof(WTSApi32.WTSSessionInfo);
        var sizeSessionInfo = Marshal.SizeOf(typeSessionInfo);
        var current = handleSessionInfo;
        for (var i = 0; i < sessionCount; i++)
        {
            var sessionInfo = (WTSApi32.WTSSessionInfo)Marshal.PtrToStructure(current, typeSessionInfo);
            current += sizeSessionInfo;
            if (sessionInfo.State == WTSApi32.WTSConnectStateClass.WTSActive)
                return sessionInfo.SessionID;
        }

如果没有找到,则搜索带有以下内容的 rdp 会话:

    [DllImport("kernel32.dll")]
    public static extern uint WTSGetActiveConsoleSessionId();

通过这个获取一个令牌

    private static IntPtr GetUserImpersonatedToken(uint activeSessionId)
    {
        if (!WTSApi32.WTSQueryUserToken(activeSessionId, out var handleImpersonationToken))
            Win32Helper.RaiseInvalidOperation("WTSQueryUserToken");

        try
        {
            return DuplicateToken(handleImpersonationToken, AdvApi32.TokenType.TokenPrimary);
        }
        finally
        {
            Kernel32.CloseHandle(handleImpersonationToken);
        }
    }

通过这个方法,你可以在一个已打开的用户会话中,以 Local System 服务身份执行一个exe文件。

    public static void ExecuteAsUserFromService(string appExeFullPath, uint activeSessionId, bool isVisible = false, string cmdLine = null, string workDir = null)
    {
        var tokenUser = GetUserImpersonatedToken(activeSessionId);
        try
        {
            if (!AdvApi32.SetTokenInformation(tokenUser, AdvApi32.TokenInformationClass.TokenSessionId, ref activeSessionId, sizeof(UInt32)))
                Win32Helper.RaiseInvalidOperation("SetTokenInformation");

            ExecuteAsUser(tokenUser, appExeFullPath, isVisible, cmdLine, workDir);
        }
        finally
        {
            Kernel32.CloseHandle(tokenUser);
        }
    }

现在看看你能否将你的代码适应 CreateProcessAsUser(...)

    private static void ExecuteAsUser(IntPtr token, string appExeFullPath, bool isVisible, string cmdLine, string workDir)
    {
        PrepareExecute(appExeFullPath, isVisible, ref workDir, out var creationFlags, out var startInfo, out var procInfo);
        try
        {
            startInfo.lpDesktop = "WinSta0\\Default";
            var processAttributes = new AdvApi32.SecurityAttributes
            {
                lpSecurityDescriptor = IntPtr.Zero
            };
            var threadAttributes = new AdvApi32.SecurityAttributes
            {
                lpSecurityDescriptor = IntPtr.Zero
            };
            if (!AdvApi32.CreateProcessAsUser(token,
                appExeFullPath, // Application Name
                cmdLine, // Command Line
                ref processAttributes,
                ref threadAttributes,
                true,
                creationFlags,
                IntPtr.Zero,
                workDir, // Working directory
                ref startInfo,
                out procInfo))
            {
                throw Win32Helper.RaiseInvalidOperation("CreateProcessAsUser");
            }
        }
        finally
        {
            Kernel32.CloseHandle(procInfo.hThread);
            Kernel32.CloseHandle(procInfo.hProcess);
        }
    }

希望这段代码能服务于你或其他人!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接