Как распечатать PDF-документ из службы Windows

#c# #visual-studio-2010

#c# #visual-studio-2010

Вопрос:

У меня есть служба Windows, которая должна периодически печатать PDF-документ с сервера. Моя функция

 private void PrintFormPdfData(byte[] formPdfData)
{
    string tempFile;

    tempFile = Path.GetTempFileName();

    using (FileStream fs = new FileStream(tempFile, FileMode.Create))
    {
        fs.Write(formPdfData, 0, formPdfData.Length);
        fs.Flush();
    }

    string pdfArguments = string.Format("/p /h"{0}"", tempFile);

    string pdfPrinterLocation = @"C:Program Files (x86)AdobeReader 9.0ReaderAcroRd32.exe";


    ProcessStartInfo newProcess = new ProcessStartInfo(pdfPrinterLocation, pdfArguments);
    newProcess.CreateNoWindow = true;
    newProcess.RedirectStandardOutput = true;
    newProcess.UseShellExecute = false;
    newProcess.RedirectStandardError = true;

    Process pdfProcess = new Process();
    pdfProcess.StartInfo = newProcess;
    pdfProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
    pdfProcess.Start();
    pdfProcess.WaitForExit();
}
  

Когда я реализую это в приложении Windows, это работает, но когда я реализую в службе Windows, это не работает.

Можете ли вы мне помочь?

Комментарии:

1. Что «не работает»? Вы получаете сообщение об ошибке?

Ответ №1:

Я решил проблему с сеансом 0. Я использую этот класс:

 public class ProcessStarter : IDisposable
{
    #region Import Section

    private static uint STANDARD_RIGHTS_REQUIRED = 0x000F0000;
    private static uint STANDARD_RIGHTS_READ = 0x00020000;
    private static uint TOKEN_ASSIGN_PRIMARY = 0x0001;
    private static uint TOKEN_DUPLICATE = 0x0002;
    private static uint TOKEN_IMPERSONATE = 0x0004;
    private static uint TOKEN_QUERY = 0x0008;
    private static uint TOKEN_QUERY_SOURCE = 0x0010;
    private static uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
    private static uint TOKEN_ADJUST_GROUPS = 0x0040;
    private static uint TOKEN_ADJUST_DEFAULT = 0x0080;
    private static uint TOKEN_ADJUST_SESSIONID = 0x0100;
    private static uint TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);
    private static uint TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID);

    private const uint NORMAL_PRIORITY_CLASS = 0x0020;

    private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;


    private const uint MAX_PATH = 260;

    private const uint CREATE_NO_WINDOW = 0x08000000;

    private const uint INFINITE = 0xFFFFFFFF;

    [StructLayout(LayoutKind.Sequential)]
    public struct SECURITY_ATTRIBUTES
    {
        public int nLength;
        public IntPtr lpSecurityDescriptor;
        public int bInheritHandle;
    }

    public enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityAnonymous,
        SecurityIdentification,
        SecurityImpersonation,
        SecurityDelegation
    }

    public enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation
    }

    public enum WTS_CONNECTSTATE_CLASS
    {
        WTSActive,
        WTSConnected,
        WTSConnectQuery,
        WTSShadow,
        WTSDisconnected,
        WTSIdle,
        WTSListen,
        WTSReset,
        WTSDown,
        WTSInit
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct STARTUPINFO
    {
        public Int32 cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public Int32 dwX;
        public Int32 dwY;
        public Int32 dwXSize;
        public Int32 dwYSize;
        public Int32 dwXCountChars;
        public Int32 dwYCountChars;
        public Int32 dwFillAttribute;
        public Int32 dwFlags;
        public Int16 wShowWindow;
        public Int16 cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct WTS_SESSION_INFO
    {
        public Int32 SessionID;

        [MarshalAs(UnmanagedType.LPStr)]
        public String pWinStationName;

        public WTS_CONNECTSTATE_CLASS State;
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern uint WTSGetActiveConsoleSessionId();

    [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern bool WTSQueryUserToken(int sessionId, out IntPtr tokenHandle);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public extern static bool DuplicateTokenEx(IntPtr existingToken, uint desiredAccess, IntPtr tokenAttributes, SECURITY_IMPERSONATION_LEVEL impersonationLevel, TOKEN_TYPE tokenType, out IntPtr newToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern bool CreateProcessAsUser(IntPtr token, string applicationName, string commandLine, ref SECURITY_ATTRIBUTES processAttributes, ref SECURITY_ATTRIBUTES threadAttributes, bool inheritHandles, uint creationFlags, IntPtr environment, string currentDirectory, ref STARTUPINFO startupInfo, out PROCESS_INFORMATION processInformation);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern bool CloseHandle(IntPtr handle);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern int GetLastError();

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern int WaitForSingleObject(IntPtr token, uint timeInterval);

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

    [DllImport("userenv.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);

    [DllImport("wtsapi32.dll", ExactSpelling = true, SetLastError = false)]
    public static extern void WTSFreeMemory(IntPtr memory);

    [DllImport("userenv.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);

    #endregion

    /// <summary>
    /// Initializes a new instance of the <see cref="ProcessStarter"/> class.
    /// </summary>
    public ProcessStarter()
    {

    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ProcessStarter"/> class.
    /// </summary>
    /// <param name="processName">Name of the process.</param>
    /// <param name="fullExeName">Full name of the exe.</param>
    public ProcessStarter(string processName, string fullExeName)
    {
        processName_ = processName;
        processPath_ = fullExeName;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ProcessStarter"/> class.
    /// </summary>
    /// <param name="processName">Name of the process.</param>
    /// <param name="fullExeName">Full name of the exe.</param>
    /// <param name="arguments">The arguments.</param>
    public ProcessStarter(string processName, string fullExeName, string arguments)
    {
        processName_ = processName;
        processPath_ = fullExeName;
        arguments_ = arguments;
    }

    /// <summary>
    /// Gets the current user token.
    /// </summary>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// Runs this instance.
    /// </summary>
    public void Run()
    {

        IntPtr primaryToken = GetCurrentUserToken();
        if (primaryToken == IntPtr.Zero)
        {
            return;
        }
        STARTUPINFO StartupInfo = new STARTUPINFO();
        processInfo_ = new PROCESS_INFORMATION();
        StartupInfo.cb = Marshal.SizeOf(StartupInfo);

        SECURITY_ATTRIBUTES Security1 = new SECURITY_ATTRIBUTES();
        SECURITY_ATTRIBUTES Security2 = new SECURITY_ATTRIBUTES();

        string command = """   processPath_   """;
        if ((arguments_ != null) amp;amp; (arguments_.Length != 0))
        {
            command  = " "   arguments_;
        }

        IntPtr lpEnvironment = IntPtr.Zero;
        bool resultEnv = CreateEnvironmentBlock(out lpEnvironment, primaryToken, false);
        if (resultEnv != true)
        {
            int nError = GetLastError();
        }

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

        DestroyEnvironmentBlock(lpEnvironment);
        CloseHandle(primaryToken);
    }

    /// <summary>
    /// Stops this instance.
    /// </summary>
    public void Stop()
    {
        Process[] processes = Process.GetProcesses();
        foreach (Process current in processes)
        {
            if (current.ProcessName == processName_)
            {
                current.Kill();
            }
        }
    }

    /// <summary>
    /// Waits for exit.
    /// </summary>
    /// <returns></returns>
    public int WaitForExit()
    {
        WaitForSingleObject(processInfo_.hProcess, INFINITE);
        int errorcode = GetLastError();
        return errorcode;
    }



    #region IDisposable Members

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
    }

    #endregion

    private string processPath_ = string.Empty;
    private string processName_ = string.Empty;
    private string arguments_ = string.Empty;
    private PROCESS_INFORMATION processInfo_;

    /// <summary>
    /// Gets or sets the process path.
    /// </summary>
    /// <value>The process path.</value>
    public string ProcessPath
    {
        get
        {
            return processPath_;
        }
        set
        {
            processPath_ = value;
        }
    }

    /// <summary>
    /// Gets or sets the name of the process.
    /// </summary>
    /// <value>The name of the process.</value>
    public string ProcessName
    {
        get
        {
            return processName_;
        }
        set
        {
            processName_ = value;
        }
    }

    /// <summary>
    /// Gets or sets the arguments.
    /// </summary>
    /// <value>The arguments.</value>
    public string Arguments
    {
        get
        {
            return arguments_;
        }
        set
        {
            arguments_ = value;
        }
    }
}
  

Теперь моя функция выглядит следующим образом:

 private void PrintFormPdfData(byte[] formPdfData)
{
    string tempFile;

    tempFile = Path.GetTempFileName();

    using (FileStream fs = new FileStream(tempFile, FileMode.Create))
    {
        fs.Write(formPdfData, 0, formPdfData.Length);
        fs.Flush();
    }
    string pdfArguments =string.Format("/t /o {0} "Printer name"", tempFile);

    string pdfPrinterLocation = @"C:Program Files (x86)AdobeReader 9.0ReaderAcroRd32.exe";

    try
    {
        ProcessStarter processStarter = new ProcessStarter("AcroRd32", pdfPrinterLocation, pdfArguments);
        processStarter.Run();
        processStarter.WaitForExit();
        processStarter.Stop();
    }
    finally
    {
        File.Delete(tempFile);
    }
}
  

Кроме того, ServiceProcessInstaller для учетной записи ` должно быть установлено значение «LocalSystem». Когда я создал службу, я установил «Локальную службу» с этим пользователем, она не работает. Я не пробовал использовать «Сетевую службу» или «Пользователя».

Ответ №2:

Я решил эту проблему с помощью изменений в реестре, приведенных в этой статье: https://support.microsoft.com/en-us/kb/184291.

При печати из служб Windows (а также IIS) используется системная учетная запись, насколько я понимаю. У этой учетной записи нет доступа к принтеру, но эти изменения в реестре дают этой системной учетной записи права на печать.

Вот краткое изложение:

  1. экспортируйте 3 ключа («папки внутри regedit.exe «) в reg-файлы:
    • HKEY_CURRENT_USERSoftwareMicrosoft Windows NT Текущая версия Устройства
    • HKEY_CURRENT_USERSoftwareMicrosoft Windows NT Текущая версия PrinterPorts
    • HKEY_CURRENT_USERSoftwareMicrosoft Windows NT Текущая версия Windows
  2. отредактируйте ваши 3 недавно созданных reg-файла и измените «HKEY_CURRENT_USER» на «HKEY_USERS.ПО УМОЛЧАНИЮ»
  3. запустите ваши 3 reg-файла, дважды щелкнув по ним в проводнике (это добавит их в реестр)

Это все, что требуется. Это помогло мне печатать PDF-файлы на принтере с PHP в IIS 7. Мой php-скрипт работал бы просто отлично, если бы на сервере был зарегистрирован администратор при запуске скрипта. Теперь никому не нужно входить в систему, и скрипт все еще работает 🙂

Я надеюсь, что это избавит кого-то от лишних поисков и переделок, которые я затратил, чтобы найти его.

Ответ №3:

Вы не упомянули ОС, но я подозреваю, что вы столкнулись с функцией изоляции сеанса 0, которая была добавлена в Windows Vista, Server 2008 и последующих версиях ОС

Классы приложений, на которые влияет эта функция, включают:

Службы, создающие пользовательский интерфейс.

Служба, которая пытается использовать функции оконных сообщений, такие как SendMessage и postMessage, для связи с приложением.

Приложения, создающие объекты с глобальными именами.

Комментарии:

1. В настоящее время я работаю в Windows 7. Но позже мне нужно будет запустить XP, Windows 7 или vista. При запуске или отладке этой функции у меня не возникает никаких ошибок.

2. Да, он не выдаст ошибку, но вы не увидите ожидаемых результатов во время выполнения. Однако пробовали ли вы на компьютере с Windows XP проверить, действительно ли печать работает, это подтвердит, является ли это сеансом 0, потому что Windows XP не имеет изоляции S0