1
Answer

Print Pdf from windows service and keep working after logoff

Photo of Manoj Kumawat

Manoj Kumawat

7y
138
1
I am creating a windows service that will print pdf in time interval.
 
I have done some research on this and found a class
  1. public class ProcessStarter : IDisposable  
  2. {  
  3. #region Import Section  
  4. private static uint STANDARD_RIGHTS_REQUIRED = 0x000F0000;  
  5. private static uint STANDARD_RIGHTS_READ = 0x00020000;  
  6. private static uint TOKEN_ASSIGN_PRIMARY = 0x0001;  
  7. private static uint TOKEN_DUPLICATE = 0x0002;  
  8. private static uint TOKEN_IMPERSONATE = 0x0004;  
  9. private static uint TOKEN_QUERY = 0x0008;  
  10. private static uint TOKEN_QUERY_SOURCE = 0x0010;  
  11. private static uint TOKEN_ADJUST_PRIVILEGES = 0x0020;  
  12. private static uint TOKEN_ADJUST_GROUPS = 0x0040;  
  13. private static uint TOKEN_ADJUST_DEFAULT = 0x0080;  
  14. private static uint TOKEN_ADJUST_SESSIONID = 0x0100;  
  15. private static uint TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);  
  16. 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);  
  17. private const uint NORMAL_PRIORITY_CLASS = 0x0020;  
  18. private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;  
  19. private const uint MAX_PATH = 260;  
  20. private const uint CREATE_NO_WINDOW = 0x08000000;  
  21. private const uint INFINITE = 0xFFFFFFFF;  
  22. [StructLayout(LayoutKind.Sequential)]  
  23. public struct SECURITY_ATTRIBUTES  
  24. {  
  25. public int nLength;  
  26. public IntPtr lpSecurityDescriptor;  
  27. public int bInheritHandle;  
  28. }  
  29. public enum SECURITY_IMPERSONATION_LEVEL  
  30. {  
  31. SecurityAnonymous, SecurityIdentification, SecurityImpersonation, SecurityDelegation  
  32. }  
  33. public enum TOKEN_TYPE  
  34. {  
  35. TokenPrimary = 1, TokenImpersonation  
  36. }  
  37. public enum WTS_CONNECTSTATE_CLASS  
  38. {  
  39. WTSActive, WTSConnected, WTSConnectQuery, WTSShadow, WTSDisconnected, WTSIdle, WTSListen, WTSReset, WTSDown, WTSInit  
  40. }  
  41. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]  
  42. public struct STARTUPINFO  
  43. {  
  44. public Int32 cb;  
  45. public string lpReserved;  
  46. public string lpDesktop;  
  47. public string lpTitle;  
  48. public Int32 dwX;  
  49. public Int32 dwY;  
  50. public Int32 dwXSize;  
  51. public Int32 dwYSize;  
  52. public Int32 dwXCountChars;  
  53. public Int32 dwYCountChars;  
  54. public Int32 dwFillAttribute;  
  55. public Int32 dwFlags;  
  56. public Int16 wShowWindow;  
  57. public Int16 cbReserved2;  
  58. public IntPtr lpReserved2;  
  59. public IntPtr hStdInput;  
  60. public IntPtr hStdOutput;  
  61. public IntPtr hStdError;  
  62. }  
  63. [StructLayout(LayoutKind.Sequential)]  
  64. internal struct PROCESS_INFORMATION  
  65. {  
  66. public IntPtr hProcess;  
  67. public IntPtr hThread;  
  68. public int dwProcessId;  
  69. public int dwThreadId;  
  70. }  
  71. [StructLayout(LayoutKind.Sequential)] private struct WTS_SESSION_INFO  
  72. {  
  73. public Int32 SessionID;  
  74. [MarshalAs(UnmanagedType.LPStr)]  
  75. public String pWinStationName;  
  76. public WTS_CONNECTSTATE_CLASS State;  
  77. }  
  78. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]  
  79. static extern uint WTSGetActiveConsoleSessionId();  
  80. [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]  
  81. static extern bool WTSQueryUserToken(int sessionId, out IntPtr tokenHandle);  
  82. [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]  
  83. public extern static bool DuplicateTokenEx(IntPtr existingToken, uint desiredAccess, IntPtr tokenAttributes, SECURITY_IMPERSONATION_LEVEL impersonationLevel, TOKEN_TYPE tokenType, out IntPtr newToken);  
  84. [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]  
  85. 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)]  
  86. static extern bool CloseHandle(IntPtr handle);  
  87. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]  
  88. static extern int GetLastError();  
  89. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]  
  90. static extern int WaitForSingleObject(IntPtr token, uint timeInterval); [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]  
  91. static extern int WTSEnumerateSessions(System.IntPtr hServer, int Reserved, int Version, ref System.IntPtr ppSessionInfo, ref int pCount);  
  92. [DllImport("userenv.dll", CharSet = CharSet.Auto, SetLastError = true)]  
  93. static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);  
  94. [DllImport("wtsapi32.dll", ExactSpelling = true, SetLastError = false)]  
  95. public static extern void WTSFreeMemory(IntPtr memory);  
  96. [DllImport("userenv.dll", SetLastError = true)]  
  97. [return: MarshalAs(UnmanagedType.Bool)]  
  98. static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);  
  99. #endregion  
  100. /// <summary>  
  101. /// Initializes a new instance of the <see cref="ProcessStarter"/> class.  
  102. /// </summary>  
  103. public ProcessStarter() { }  
  104. /// <summary>  
  105. /// Initializes a new instance of the <see cref="ProcessStarter"/> class.  
  106. /// </summary>  
  107. /// <param name="processName">Name of the process.</param>  
  108. /// <param name="fullExeName">Full name of the exe.</param>  
  109. public ProcessStarter(string processName, string fullExeName)  
  110. {  
  111. processName_ = processName; processPath_ = fullExeName; }  
  112. /// <summary>  
  113. /// Initializes a new instance of the <see cref="ProcessStarter"/> class.  
  114. /// </summary>  
  115. /// <param name="processName">Name of the process.</param>  
  116. /// <param name="fullExeName">Full name of the exe.</param>  
  117. /// <param name="arguments">The arguments.</param>  
  118. public ProcessStarter(string processName, string fullExeName, string arguments)  
  119. {  
  120. processName_ = processName; processPath_ = fullExeName; arguments_ = arguments; }  
  121. /// <summary>  
  122. /// Gets the current user token.  
  123. /// </summary>  
  124. /// <returns></returns>  
  125. public static IntPtr GetCurrentUserToken()  
  126. {  
  127. IntPtr currentToken = IntPtr.Zero;  
  128. IntPtr primaryToken = IntPtr.Zero;  
  129. IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;  
  130. int dwSessionId = 0;  
  131. IntPtr hUserToken = IntPtr.Zero;  
  132. IntPtr hTokenDup = IntPtr.Zero;  
  133. IntPtr pSessionInfo = IntPtr.Zero;  
  134. int dwCount = 0; WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref dwCount);  
  135. Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));  
  136. Int32 current = (int)pSessionInfo;  
  137. for (int i = 0; i < dwCount; i++)  
  138. {  
  139. WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO));  
  140. if (WTS_CONNECTSTATE_CLASS.WTSActive == si.State)  
  141. {  
  142. dwSessionId = si.SessionID;  
  143. break;  
  144. }  
  145. current += dataSize;  
  146. }  
  147. WTSFreeMemory(pSessionInfo);  
  148. bool bRet = WTSQueryUserToken(dwSessionId, out currentToken);  
  149. if (bRet == false)  
  150. {  
  151. return IntPtr.Zero;  
  152. }  
  153. bRet = DuplicateTokenEx(currentToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken);  
  154. if (bRet == false)  
  155. {  
  156. return IntPtr.Zero;  
  157. }  
  158. return primaryToken; }  
  159. /// <summary>  
  160. /// Runs this instance.  
  161. /// </summary>  
  162. public void Run()  
  163. {  
  164. IntPtr primaryToken = GetCurrentUserToken();  
  165. if (primaryToken == IntPtr.Zero)  
  166. {  
  167. return;  
  168. }  
  169. STARTUPINFO StartupInfo = new STARTUPINFO();  
  170. processInfo_ = new PROCESS_INFORMATION();  
  171. StartupInfo.cb = Marshal.SizeOf(StartupInfo);  
  172. SECURITY_ATTRIBUTES Security1 = new SECURITY_ATTRIBUTES();  
  173. SECURITY_ATTRIBUTES Security2 = new SECURITY_ATTRIBUTES();  
  174. string command = "\"" + processPath_ + "\""if ((arguments_ != null) && (arguments_.Length != 0))  
  175. {  
  176. command += " " + arguments_;  
  177. }  
  178. IntPtr lpEnvironment = IntPtr.Zero;  
  179. bool resultEnv = CreateEnvironmentBlock(out lpEnvironment, primaryToken, false);  
  180. if (resultEnv != true)  
  181. {  
  182. int nError = GetLastError();  
  183. }  
  184. CreateProcessAsUser(primaryToken, null, command, ref Security1, ref Security2, false, CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, nullref StartupInfo, out processInfo_);  
  185. DestroyEnvironmentBlock(lpEnvironment);  
  186. CloseHandle(primaryToken);  
  187. }  
  188. /// <summary>  
  189. /// Stops this instance.  
  190. /// </summary>  
  191. public void Stop()  
  192. {  
  193. Process[] processes = Process.GetProcesses();  
  194. foreach (Process current in processes)  
  195. {  
  196. if (current.ProcessName == processName_)  
  197. {  
  198. current.Kill();  
  199. }  
  200. }  
  201. }  
  202. /// <summary>  
  203. /// Waits for exit.  
  204. /// </summary>  
  205. /// <returns></returns>  
  206. public int WaitForExit()  
  207. {  
  208. WaitForSingleObject(processInfo_.hProcess, INFINITE);  
  209. int errorcode = GetLastError();  
  210. return errorcode;  
  211. }  
  212. #region IDisposable Members  
  213. /// <summary>  
  214. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.  
  215. /// </summary>  
  216. public void Dispose()  
  217. {  
  218. }  
  219. #endregion  
  220. private string processPath_ = string.Empty;  
  221. private string processName_ = string.Empty;  
  222. private string arguments_ = string.Empty;  
  223. private PROCESS_INFORMATION processInfo_;  
  224. /// <summary>  
  225. /// Gets or sets the process path.  
  226. /// </summary> ///  
  227. <value>The process path.</value>  
  228. public string ProcessPath  
  229. {  
  230. get  
  231. {  
  232. return processPath_;  
  233. }  
  234. set  
  235. {  
  236. processPath_ = value;  
  237. }  
  238. }  
  239. /// <summary>  
  240. /// Gets or sets the name of the process.  
  241. /// </summary>  
  242. /// <value>The name of the process.</value>  
  243. public string ProcessName  
  244. {  
  245. getreturn processName_; }  
  246. set { processName_ = value; }  
  247. }  
  248. /// <summary>  
  249. /// Gets or sets the arguments.  
  250. /// </summary>  
  251. /// <value>The arguments.</value>  
  252. public string Arguments  
  253. {  
  254. get { return arguments_; }  
  255. set { arguments_ = value; }  
  256. }  
  257. }  
and calling this class like this
  1. string pdfPrinterLocation = @"C:\Program Files (x86)\Adobe\Reader 11.0\Reader\AcroRd32.exe";  
  2. string pdfArguments = "/t /o C:\Windows\TEMP\761372.pdf";  
  3. ProcessStarter processStarter = new ProcessStarter("AcroRd32", pdfPrinterLocation, pdfArguments);  
  4. processStarter.Run();  
  5. processStarter.WaitForExit();  
  6. processStarter.Stop();  
This code is working fine and doing great job.
 
But This is working only when some user logged in in system. Means when I login then it's working. But when I logoff my user then it's not working. When I lock my system then it's working but when logoff and switch user then it's not working.
 
I have checked service logs for checking where the issue is.
 
So I found this statement in Run method
  1. IntPtr primaryToken = GetCurrentUserToken();  
  2. if (primaryToken == IntPtr.Zero) { return; }  
When user logged in then primaryToken is good but when signout or switch user then it is IntPtr.Zero.
 
Then I have checked GetCurrentUserToken method. and I found that this method is creating token for current logged in user token. that's why this is not working after logoff.
 
But I want printing after logoff.
 
In this Run method CreateProcessAsUser is used for creating print process.
 
But I don't know how can I create process without user. There is a method CreateProcess. Perhaps it will create process without user token I don't know much about it. Found this here.
 
But it is also not working. Even it is not working while user logged in.
 
I want to print pdf files in some interval using windows services. And It should be keep working after user logoff or switch user or lock.
 
Anyone know how can I achieve this?
 
Thanks

Answers (1)

0
Photo of Rahul Kaushik
NA 383 14.6k 7y
There is a perfect way while using flag to detect whether someone is already logged in or not and in case if System get power off , you need an event handler called as SystemEvents.SessionEnds.
 
Use this to change the flag value from true to false
 
Use this link as a reference
https://stackoverflow.com/questions/6799955/how-to-detect-windows-shutdown-or-logoff 
0
Photo of Mann Maurya
NA 47 869 7y
Hey Ajeesh
 
one more question... ??
 
If power off happen then how you suppose to Reset the flag
 
0
Photo of Ajeesh
NA 349 1.6k 7y
You can take a look at this article which discuss a similar topic. 
 
https://blog.learningtree.com/preventing-duplicate-logins-in-asp-net/
 
Another option would be to have a flag in your table and set the value to true when an user login and when user log off set the value to false.  Also for when a second user tries to login we will first check if login flag has been set to true for any other user. If its true then show message to user and display the login page, if not allow the user to login.