PDF Imprimir a través del servicio de Windows con C #

8

Estoy usando este código para imprimir un archivo PDF en una impresora local con C # dentro de un servicio de Windows.

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();

Todo funciona bien cuando configuro un usuario para ejecutar el servicio de Windows.

Cada vez que ejecuto este código bajo la credencial LocalSystem, aparece el error "no hay ninguna aplicación asociada a esta operación", que generalmente indica que no tengo un programa listo para manejar una operación de impresión de un archivo con la extensión .pdf .

Mi problema es que tengo el programa (Foxit Reader) para hacer frente a esta operación, como lo confirma el hecho de que este código funciona con un usuario específico configurado en el servicio y que puedo enviar archivos a la impresora haciendo clic derecho sobre ellos. y seleccionando la opción de impresión.

¿Hay algo que pueda cambiar para poder imprimir en una impresora local desde un servicio sin un usuario específico?

Caio Sant'Anna
fuente
¿Qué tipo de impresora es? ¿Instalado localmente o una impresora de red compartida a través de \\ computername \ printername?
Sa.he
Es un Epson EcoTank L375 ( epson.com.jm/Support/Printers/All-In-Ones/L-Series/… ) instalado localmente.
Caio Sant'Anna

Respuestas:

4

Terminé usando pdfium para hacer el trabajo. Con ese código, el archivo PDF se envía a la impresora correctamente incluso cuando el servicio de Windows se ejecuta bajo el usuario LocalService.

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

Gracias por la respuesta chicos.

Caio Sant'Anna
fuente
0

El problema puede ser que la cuenta SYSTEM (LocalSystem) tiene capacidades limitadas de interfaz de usuario y posiblemente las extensiones de shell o shell se eliminan o deshabilitan. Y los verbos son una capacidad del subsistema de shell, específicamente, el Explorer.

Puede invocar el programa manualmente para ver si ese es el caso o si es un problema de seguridad o falta de detalles del perfil de usuario.

Para hacer eso, necesita cavar en el Registro y encontrará que muchos verbos de extensión de ejecución de shell tienen una línea de comando.

Por ejemplo, busque HKEY_CLASSES_ROOT.pdf \ shell \ printto \ command y use ese comando.

Además, puede verificar que la cuenta SYSTEM tenga acceso a esta y a las claves principales. (Raramente el caso pero vale la pena verificarlo)

Bahram Ardalan
fuente
0

Quizás pueda ejecutar su código de trabajo, pero usando un token de usuario de sesión actualmente activo (pero sin sesión activa esto no debería funcionar)

Por brevedad, este código no se compilará: primero debe adaptar y agregar P / Invocar .

Tienes que encontrar la identificación de sesión activa. Para la sesión abierta local, use esto:

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

Luego busque una ID de sesión abierta:

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

Si no se encuentra, busque una sesión rdp con:

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

Con eso obtén una ficha

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

Con eso puede ejecutar un exe desde el servicio del sistema local, en una sesión de usuario abierta.

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

Ahora vea si puede adaptar su código a 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);
        }
    }

¡Esperando que este código le sirva a usted u otra persona!

Dmo
fuente
0

¿Podría ser que la aplicación PDF no esté en la variable PATH de todo el sistema, sino solo bajo su usuario específico?

Creo que su problema ocurre porque el usuario del "sistema local" no encuentra una aplicación adecuada, por lo que deberá registrarla para él. Como ya ha aceptado otra respuesta, no dedicaré más tiempo a esto, pero si tiene más preguntas, por favor pregunte.

hasta
fuente