栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > .Net

关于 C# / .Net / IIS Web Service 调用 exe

.Net 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

关于 C# / .Net / IIS Web Service 调用 exe

最近一个面试,面试官说他们现在的架构是通过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 后解决。

其次是

                 // retrieve the primary access token for the user associated with the specified session Id.
                if (!WTSQueryUserToken(curSessionid, out dupedToken))
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }

在Debug 运行时,由于权限及安全性相关问题,无法直接运行。

可以考虑在Debug 时注释掉,或添加Release Tag 以继续Debug。

    // Ref: https://stackoverflow.com/questions/19776716/c-sharp-windows-service-creates-process-but-doesnt-executes-it
    [SuppressUnmanagedCodeSecurity]
    public class ProcessHandler
    {
        public const int GENERIC_ALL_ACCESS = 0x10000000;
        //public const int CREATE_NO_WINDOW = 0x08000000;
        public const int STARTF_USESHOWWINDOW = 0x00000001;

        public const int SE_PRIVILEGE_ENABLED = 0x00000002;
        public const string SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege";
        internal const string SE_TCB_NAME = "SeTcbPrivilege";


        enum CreateProcessFlags
        {
            CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
            CREATE_DEFAULT_ERROR_MODE = 0x04000000,
            CREATE_NEW_CONSOLE = 0x00000010,
            CREATE_NEW_PROCESS_GROUP = 0x00000200,
            CREATE_NO_WINDOW = 0x08000000,
            CREATE_PROTECTED_PROCESS = 0x00040000,
            CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
            CREATE_SEPARATE_WOW_VDM = 0x00000800,
            CREATE_SHARED_WOW_VDM = 0x00001000,
            CREATE_SUSPENDED = 0x00000004,
            CREATE_UNICODE_ENVIRONMENT = 0x00000400,
            DEBUG_ONLY_THIS_PROCESS = 0x00000002,
            DEBUG_PROCESS = 0x00000001,
            DETACHED_PROCESS = 0x00000008,
            EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
            INHERIT_PARENT_AFFINITY = 0x00010000
        }


        enum TOKEN_INFORMATION_CLASS
        {

            TokenUser = 1,
            TokenGroups,
            TokenPrivileges,
            TokenOwner,
            TokenPrimaryGroup,
            TokenDefaultDacl,
            TokenSource,
            TokenType,
            TokenImpersonationLevel,
            TokenStatistics,
            TokenRestrictedSids,
            TokenSessionId,
            TokenGroupsAndPrivileges,
            TokenSessionReference,
            TokenSandBoxInert,
            TokenAuditPolicy,
            TokenOrigin,
            TokenElevationType,
            TokenLinkedToken,
            TokenElevation,
            TokenHasRestrictions,
            TokenAccessInformation,
            TokenVirtualizationAllowed,
            TokenVirtualizationEnabled,
            TokenIntegrityLevel,
            TokenUIAccess,
            TokenMandatoryPolicy,
            TokenLogonSid,
            MaxTokenInfoClass
        }

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

        public enum TOKEN_TYPE
        {
            TokenPrimary = 1,
            TokenImpersonation
        }

        #region struct
        [StructLayout(LayoutKind.Sequential)]
        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 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)]
        public struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public Int32 dwProcessID;
            public Int32 dwThreadID;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        {
            public Int32 Length;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct TokPriv1Luid
        {
            public int Count;
            public long Luid;
            public int Attr;
        }
        #endregion

        #region Win32 API
        [DllImport("advapi32.dll", SetLastError = true)]
        internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);

        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);

        [DllImport("advapi32.dll", EntryPoint = "ImpersonateLoggedOnUser", SetLastError = true,
         CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr ImpersonateLoggedOnUser(IntPtr hToken);

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

        [
           DllImport("advapi32.dll",
              EntryPoint = "CreateProcessAsUser", SetLastError = true,
              CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)
        ]
        public static extern bool
           CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine,
                               ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes,
                               bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment,
                               string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
                               ref PROCESS_INFORMATION lpProcessInformation);

        [
           DllImport("advapi32.dll",
              EntryPoint = "DuplicateTokenEx")
        ]
        public static extern bool
           DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess,
                            ref SECURITY_ATTRIBUTES lpThreadAttributes,
                            Int32 ImpersonationLevel, Int32 dwTokenType,
                            ref IntPtr phNewToken);
        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool RevertToSelf();

        [DllImport("Kernel32.dll", SetLastError = true)]
        public static extern IntPtr WTSGetActiveConsoleSessionId();

        [DllImport("advapi32.dll")]
        public static extern IntPtr SetTokenInformation(IntPtr TokenHandle, IntPtr TokenInformationClass, IntPtr TokenInformation, IntPtr TokenInformationLength);


        [DllImport("wtsapi32.dll", SetLastError = true)]
        public static extern bool WTSQueryUserToken(uint sessionId, out IntPtr Token);
        #endregion

        private static int GetCurrentUserSessionID()
        {
            uint dwSessionId = (uint)WTSGetActiveConsoleSessionId();

            // gets the Id of the User logged in with WinLogOn
            Process[] processes = Process.GetProcessesByName("winlogon");
            foreach (Process p in processes)
            {
                if ((uint)p.SessionId == dwSessionId)
                {

                    // this is the process controlled by the same sessionID
                    return p.SessionId;
                } 
            }

            return -1;
        }

        /// 
        /// Main method for Create process used advapi32: CreateProcessAsUser
        /// 
        /// Execute path, for example: c:appmyapp.exe
        /// Arugments passing to execute application
        /// Process just been created
        public static Process CreateProcessAsUser(string filePath, string args)
        {
            
            var dupedToken = IntPtr.Zero;

            var pi = new PROCESS_INFORMATION();
            var sa = new SECURITY_ATTRIBUTES();
            sa.Length = Marshal.SizeOf(sa);

            try
            {
                // get current token
                var token = WindowsIdentity.GetCurrent().Token;

                var si = new STARTUPINFO();
                si.cb = Marshal.SizeOf(si);
                si.lpDesktop = "";
                si.dwFlags = STARTF_USESHOWWINDOW;

                var dir = Path.GetDirectoryName(filePath);
                var fileName = Path.GetFileName(filePath);

                // Create new access token for current token
                if (!DuplicateTokenEx(
                    token,
                    GENERIC_ALL_ACCESS,
                    ref sa,
                    (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                    (int)TOKEN_TYPE.TokenPrimary,
                    ref dupedToken
                ))
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
                
                // got the session Id from user level
                uint curSessionid = (uint)GetCurrentUserSessionID();
                
                // retrieve the primary access token for the user associated with the specified session Id.
                if (!WTSQueryUserToken(curSessionid, out dupedToken))
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }

                WindowsIdentity.RunImpersonated(WindowsIdentity.GetCurrent().AccessToken, () => 
                {

                    if (!CreateProcessAsUser(
                                          dupedToken, // user token
                                          filePath, // app name or path
                                          string.Format(""{0}" {1}", fileName.Replace(""", """"), args), // command line
                                          ref sa, // process attributes
                                          ref sa, // thread attributes
                                          false, // do not inherit handles
                                          (int)CreateProcessFlags.CREATE_NEW_CONSOLE, //flags
                                          IntPtr.Zero, // environment block
                                          dir, // current dir
                                          ref si, // startup info
                                          ref pi // process info
                                  ))
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                });
              
                return Process.GetProcessById(pi.dwProcessID);
            }
            finally
            {
                // close all open resource
                if (pi.hProcess != IntPtr.Zero)
                    CloseHandle(pi.hProcess);
                if (pi.hThread != IntPtr.Zero)
                    CloseHandle(pi.hThread);
                if (dupedToken != IntPtr.Zero)
                    CloseHandle(dupedToken);
            }
        }
    }

 

调用时,使用:

Process process = ProcessHandler.CreateProcessAsUser("Absolute path of your exe", "args");
//you can wait here, but it will block thread
//process.WaitForExit();

用完该方法后,每次IIS 收到一个Request, Server 端均会执行你的exe 并展示页面。

 

 

如果有更好的办法,或哪里不对,还请指出。

 

完结,撒花。。。

我真闲~~~~~~~~~~~

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/954357.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号