转自:https://blog.csdn.net/sby5104/article/details/110189048
最近一个面试,面试官说他们现在的架构是通过IIS 部署的Web Service 调用Server 端的Windows Application 也就是exe。
面试拉跨之后自己尝试了一下这种实现方式,在这里记录一下自己遇到的坑,然后留一下查到的解决方案。
(PS:虽然不理解这种架构的使用场景,但也就是闲得无聊试试看。。。。)
简单写好一个Web Service 的demo 之后,Debug run 了一下,发现一切完美,exe 正常执行,页面打开顺利。
发布至IIS 后发现,访问Web Service 的Method,提示只允许local 调用,应该是Web.config 没有配置HttpGet,添加后解决。
再次尝试,发现Web Service 正常运行,正在等待Process 结束(Method 最后代码为process.WaitForExit()), 但没有exe 的页面弹出。
开始以为是防火墙或代码问题,检查之后发现不是。
打开任务管理器后发现,exe 的process 正常执行,但没有页面。
百度之后发现好多前辈都出过类似的问题,挨个方法尝试。
包括但不限于:
1. 修改IIS 权限
2. 修改IIS machine.config
3. IIS Admin Service, allow service to interact with desktop
以上方法均已失败告终。
转战至Google,刚开始发现的解决方案和百度的一毛一样,直到发现一个回复,回复中说:
Zoltán Zörgő 5-Nov-13 5:07am
As IIS is a service it is not running under a separate session - different from the one you are logged in, and under a different user account! So you can't see the application, even if it is really started. Still, there are only few situations when starting a process on server side from an asp.net application is wise - use this approach only when the application you start is command line application, and never requires user interaction to terminate (even with error condition).
大体上的意思是说:
Windows Service 和Login 桌面是在两个Session 中(此处只Process 的Session ID),而IIS 是一个Windows Service,因此通过IIS 起的Process 无法展示到当前User Login 的桌面中。
按照这个思路往下查询,发现是自Windows Vista 起,巨硬修改了Desktop 的显示,即Login,Windows Service 及UAC 分别处于三个桌面中,Process的SessionID 不同,无法直接相互切换。
在自己的IIS 服务器上打开任务管理器,在Colunms 中选中SessionID 发现,w3wp 的SessionID 为1,而winlogon Process 的SessionID 为0,符合发现的结果。
那么为什么本机Debug 好用呢?
想了一下,可能是Debug 时使用的时IIS Express, 这是个进程,有VS 启动,和winlogon Process 在同一个桌面的Session 中,因此可以成功调用exe 并展示页面。
那,是否有解决方案呢?
身边的C++ 同事说,可以通过获取SessionID,将执行exe 的process 挂到Winlogon 的Session 中解决。
按照同事的思路,Google 到一篇文章,成功解决了个问题,文章的链接为:
How to make windows service interactivity with desktop application
文章中关于这个问题的描述和解决方案都给出了明确的说明,在此就不多做解释了。
为了方便网络可能不那么好的小伙伴,单纯的把代码贴在下面,当然更推荐去阅读一下具体的内容~
代码中需要注意.Net Framework 的版本,我开始使用4.0,发现4.0 没有WindowsIdentity.RunImpersonated,改到4.8 后解决。
其次是
1 // retrieve the primary access token for the user associated with the specified session Id. 2 if (!WTSQueryUserToken(curSessionid, out dupedToken)) 3 { 4 throw new Win32Exception(Marshal.GetLastWin32Error()); 5 }
在Debug 运行时,由于权限及安全性相关问题,无法直接运行。
可以考虑在Debug 时注释掉,或添加Release Tag 以继续Debug。
1 // Ref: https://stackoverflow.com/questions/19776716/c-sharp-windows-service-creates-process-but-doesnt-executes-it 2 [SuppressUnmanagedCodeSecurity] 3 public class ProcessHandler 4 { 5 public const int GENERIC_ALL_ACCESS = 0x10000000; 6 //public const int CREATE_NO_WINDOW = 0x08000000; 7 public const int STARTF_USESHOWWINDOW = 0x00000001; 8 9 public const int SE_PRIVILEGE_ENABLED = 0x00000002; 10 public const string SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege"; 11 internal const string SE_TCB_NAME = "SeTcbPrivilege"; 12 13 14 enum CreateProcessFlags 15 { 16 CREATE_BREAKAWAY_FROM_JOB = 0x01000000, 17 CREATE_DEFAULT_ERROR_MODE = 0x04000000, 18 CREATE_NEW_CONSOLE = 0x00000010, 19 CREATE_NEW_PROCESS_GROUP = 0x00000200, 20 CREATE_NO_WINDOW = 0x08000000, 21 CREATE_PROTECTED_PROCESS = 0x00040000, 22 CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000, 23 CREATE_SEPARATE_WOW_VDM = 0x00000800, 24 CREATE_SHARED_WOW_VDM = 0x00001000, 25 CREATE_SUSPENDED = 0x00000004, 26 CREATE_UNICODE_ENVIRONMENT = 0x00000400, 27 DEBUG_ONLY_THIS_PROCESS = 0x00000002, 28 DEBUG_PROCESS = 0x00000001, 29 DETACHED_PROCESS = 0x00000008, 30 EXTENDED_STARTUPINFO_PRESENT = 0x00080000, 31 INHERIT_PARENT_AFFINITY = 0x00010000 32 } 33 34 35 enum TOKEN_INFORMATION_CLASS 36 { 37 38 TokenUser = 1, 39 TokenGroups, 40 TokenPrivileges, 41 TokenOwner, 42 TokenPrimaryGroup, 43 TokenDefaultDacl, 44 TokenSource, 45 TokenType, 46 TokenImpersonationLevel, 47 TokenStatistics, 48 TokenRestrictedSids, 49 TokenSessionId, 50 TokenGroupsAndPrivileges, 51 TokenSessionReference, 52 TokenSandBoxInert, 53 TokenAuditPolicy, 54 TokenOrigin, 55 TokenElevationType, 56 TokenLinkedToken, 57 TokenElevation, 58 TokenHasRestrictions, 59 TokenAccessInformation, 60 TokenVirtualizationAllowed, 61 TokenVirtualizationEnabled, 62 TokenIntegrityLevel, 63 TokenUIAccess, 64 TokenMandatoryPolicy, 65 TokenLogonSid, 66 MaxTokenInfoClass 67 } 68 69 public enum SECURITY_IMPERSONATION_LEVEL 70 { 71 SecurityAnonymous, 72 SecurityIdentification, 73 SecurityImpersonation, 74 SecurityDelegation 75 } 76 77 public enum TOKEN_TYPE 78 { 79 TokenPrimary = 1, 80 TokenImpersonation 81 } 82 83 #region struct 84 [StructLayout(LayoutKind.Sequential)] 85 public struct STARTUPINFO 86 { 87 public Int32 cb; 88 public string lpReserved; 89 public string lpDesktop; 90 public string lpTitle; 91 public Int32 dwX; 92 public Int32 dwY; 93 public Int32 dwXSize; 94 public Int32 dwXCountChars; 95 public Int32 dwYCountChars; 96 public Int32 dwFillAttribute; 97 public Int32 dwFlags; 98 public Int16 wShowWindow; 99 public Int16 cbReserved2; 100 public IntPtr lpReserved2; 101 public IntPtr hStdInput; 102 public IntPtr hStdOutput; 103 public IntPtr hStdError; 104 } 105 106 [StructLayout(LayoutKind.Sequential)] 107 public struct PROCESS_INFORMATION 108 { 109 public IntPtr hProcess; 110 public IntPtr hThread; 111 public Int32 dwProcessID; 112 public Int32 dwThreadID; 113 } 114 115 [StructLayout(LayoutKind.Sequential)] 116 public struct SECURITY_ATTRIBUTES 117 { 118 public Int32 Length; 119 public IntPtr lpSecurityDescriptor; 120 public bool bInheritHandle; 121 } 122 123 [StructLayout(LayoutKind.Sequential, Pack = 1)] 124 public struct TokPriv1Luid 125 { 126 public int Count; 127 public long Luid; 128 public int Attr; 129 } 130 #endregion 131 132 #region Win32 API 133 [DllImport("advapi32.dll", SetLastError = true)] 134 internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid); 135 136 [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] 137 internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen); 138 139 [DllImport("advapi32.dll", EntryPoint = "ImpersonateLoggedOnUser", SetLastError = true, 140 CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 141 public static extern IntPtr ImpersonateLoggedOnUser(IntPtr hToken); 142 143 [ 144 DllImport("kernel32.dll", 145 EntryPoint = "CloseHandle", SetLastError = true, 146 CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall) 147 ] 148 public static extern bool CloseHandle(IntPtr handle); 149 150 [ 151 DllImport("advapi32.dll", 152 EntryPoint = "CreateProcessAsUser", SetLastError = true, 153 CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall) 154 ] 155 public static extern bool 156 CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine, 157 ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, 158 bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment, 159 string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, 160 ref PROCESS_INFORMATION lpProcessInformation); 161 162 [ 163 DllImport("advapi32.dll", 164 EntryPoint = "DuplicateTokenEx") 165 ] 166 public static extern bool 167 DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess, 168 ref SECURITY_ATTRIBUTES lpThreadAttributes, 169 Int32 ImpersonationLevel, Int32 dwTokenType, 170 ref IntPtr phNewToken); 171 [DllImport("advapi32.dll", SetLastError = true)] 172 static extern bool RevertToSelf(); 173 174 [DllImport("Kernel32.dll", SetLastError = true)] 175 public static extern IntPtr WTSGetActiveConsoleSessionId(); 176 177 [DllImport("advapi32.dll")] 178 public static extern IntPtr SetTokenInformation(IntPtr TokenHandle, IntPtr TokenInformationClass, IntPtr TokenInformation, IntPtr TokenInformationLength); 179 180 181 [DllImport("wtsapi32.dll", SetLastError = true)] 182 public static extern bool WTSQueryUserToken(uint sessionId, out IntPtr Token); 183 #endregion 184 185 private static int GetCurrentUserSessionID() 186 { 187 uint dwSessionId = (uint)WTSGetActiveConsoleSessionId(); 188 189 // gets the Id of the User logged in with WinLogOn 190 Process[] processes = Process.GetProcessesByName("winlogon"); 191 foreach (Process p in processes) 192 { 193 if ((uint)p.SessionId == dwSessionId) 194 { 195 196 // this is the process controlled by the same sessionID 197 return p.SessionId; 198 } 199 } 200 201 return -1; 202 } 203 204 /// <summary> 205 /// Main method for Create process used advapi32: CreateProcessAsUser 206 /// </summary> 207 /// <param name="filePath">Execute path, for example: c:\app\myapp.exe</param> 208 /// <param name="args">Arugments passing to execute application</param> 209 /// <returns>Process just been created</returns> 210 public static Process CreateProcessAsUser(string filePath, string args) 211 { 212 213 var dupedToken = IntPtr.Zero; 214 215 var pi = new PROCESS_INFORMATION(); 216 var sa = new SECURITY_ATTRIBUTES(); 217 sa.Length = Marshal.SizeOf(sa); 218 219 try 220 { 221 // get current token 222 var token = WindowsIdentity.GetCurrent().Token; 223 224 var si = new STARTUPINFO(); 225 si.cb = Marshal.SizeOf(si); 226 si.lpDesktop = ""; 227 si.dwFlags = STARTF_USESHOWWINDOW; 228 229 var dir = Path.GetDirectoryName(filePath); 230 var fileName = Path.GetFileName(filePath); 231 232 // Create new access token for current token 233 if (!DuplicateTokenEx( 234 token, 235 GENERIC_ALL_ACCESS, 236 ref sa, 237 (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, 238 (int)TOKEN_TYPE.TokenPrimary, 239 ref dupedToken 240 )) 241 { 242 throw new Win32Exception(Marshal.GetLastWin32Error()); 243 } 244 245 // got the session Id from user level 246 uint curSessionid = (uint)GetCurrentUserSessionID(); 247 248 // retrieve the primary access token for the user associated with the specified session Id. 249 if (!WTSQueryUserToken(curSessionid, out dupedToken)) 250 { 251 throw new Win32Exception(Marshal.GetLastWin32Error()); 252 } 253 254 WindowsIdentity.RunImpersonated(WindowsIdentity.GetCurrent().AccessToken, () => 255 { 256 257 if (!CreateProcessAsUser( 258 dupedToken, // user token 259 filePath, // app name or path 260 string.Format("\"{0}\" {1}", fileName.Replace("\"", "\"\""), args), // command line 261 ref sa, // process attributes 262 ref sa, // thread attributes 263 false, // do not inherit handles 264 (int)CreateProcessFlags.CREATE_NEW_CONSOLE, //flags 265 IntPtr.Zero, // environment block 266 dir, // current dir 267 ref si, // startup info 268 ref pi // process info 269 )) 270 { 271 throw new Win32Exception(Marshal.GetLastWin32Error()); 272 } 273 }); 274 275 return Process.GetProcessById(pi.dwProcessID); 276 } 277 finally 278 { 279 // close all open resource 280 if (pi.hProcess != IntPtr.Zero) 281 CloseHandle(pi.hProcess); 282 if (pi.hThread != IntPtr.Zero) 283 CloseHandle(pi.hThread); 284 if (dupedToken != IntPtr.Zero) 285 CloseHandle(dupedToken); 286 } 287 } 288 }
调用时,使用:
1 Process process = ProcessHandler.CreateProcessAsUser("Absolute path of your exe", "args"); 2 //you can wait here, but it will block thread 3 //process.WaitForExit();
用完该方法后,每次IIS 收到一个Request, Server 端均会执行你的exe 并展示页面。
————————————————
版权声明:本文为CSDN博主「sby5104」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sby5104/article/details/110189048