簡體   English   中英

PDF 打印通過 Windows 服務與 C#

[英]PDF Print Through Windows Service with C#

我正在使用此代碼在 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)來處理此操作,這一點通過以下事實得到證實:此代碼適用於服務上的特定用戶集,並且我可以通過右鍵單擊將文件發送到打印機它們並選擇打印選項。

我有什么可以改變的,以便能夠在沒有特定用戶的服務中在本地打印機上打印?

我最終使用 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();
    }
}

謝謝你們的答案。

問題可能是 SYSTEM (LocalSystem) 帳戶的用戶界面功能有限,並且可能 shell 或 shell 擴展被刪除或禁用。 動詞是 shell 子系統,特別是 Explorer 的一項功能。

您可以手動調用該程序以查看是這種情況還是安全問題或缺少用戶配置文件詳細信息。

為此,您需要在注冊表中挖掘,您會發現許多 shell 執行擴展動詞都有命令行。

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

此外,您可以檢查 SYSTEM 帳戶是否有權訪問此密鑰和父密鑰。 (很少見,但值得檢查)

您也許可以執行您的工作代碼,但使用當前活動的 session 用戶令牌(但沒有活動的 session 這應該不起作用)

為簡潔起見,此代碼無法編譯:您必須先調整並添加一些P/Invoke

您必須找到有效的 session id。 對於本地打開的 session,使用這個:

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

然后搜索一個打開的session 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 session:

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

有了它,您可以在打開的用戶 session 上從本地系統服務執行 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);
        }
    }

希望此代碼對您或其他人有用!

會不會是 PDF 應用程序不在系統范圍的 PATH 變量中,而僅在您的特定用戶下?

我認為您的問題出現是因為“本地系統”用戶找不到合適的應用程序,因此您必須為他注冊。 由於您已經接受了另一個答案,我不會花更多時間在此,但如果對此還有其他問題,請詢問。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM