.NET C#执行程序功能时根据Windows用户角色动态提权执行相关业务功能的方法

有时候,我们的应用程序功能和业务组成比较复杂,需要使用的组件、子程序众多,可能牵涉到桌面客户端程序,bs网页端程序,甚至还有桌面内嵌web的应用程序组成形式。

我们在执行应用程序过程中,登录的Windows操作系统的用户角色也不尽相同,用户可能多数是使用的管理员组的普通用户账户登录,开发人员有时候需要提权,使用的是Administrator账户。

应用程序在执行时如果有内嵌web功能,比如网页版的剪贴板,截图工具等功能,可能启动该应用程序时,可能只需要使用当前用户所属的”普通“权限即可(超管的普通权限就是超管权限;普通用户的普通权限就是普通权限,与此相对的是普通用户的右键”以管理员身份运行“)。

但是到了某些具体的业务环节时,比如操作注册COM组件之类的,可能有时需要使用管理员权限。如果此时是使用的普通账户登录的,这个时候就需要在代码功能实现里动态对此功能执行时进行提权,否则会操作不成功。

一般我们代码操作注册某些组件可以直接使用Process方式。需要提权时在Process的参数类ProcessStartInfo的Verb属性指定为”runas“即可。

当然碰到上面描述的场景,我们第一想法就是指定”runas“,但这样打包出来的程序会带有小盾牌,打开应用程序exe时会弹框要求提权,这样又影响了其他普通权限的功能,可能又得考虑降权,改动量较大。这不是一个好的方式。

有人说需要配置app.manifest里requestedPrivileges 节点的requestedExecutionLevel节 属性level值为requireAdministrator,试验了下,同样会带有小盾牌,执行应用程序exe就让提权,此方案走不通。

后来找到了Windows底层的一个函数,ShellExecuteEx,解决了此问题。

中间还走了一个弯路,不区分当前操作系统登录用户角色身份,都按照ShellExecuteEx方式进行处理。测试人员在使用时,当操作系统登录用户为Administrator时,发现ShellExecuteEx方法执行报错,其实可想而知,超级管理员已经是最高权限了,再提权实际没有意义。

综合Process、ShellExecuteEx,以及Administrator和普通用户的使用场景,找到了一个折中处理方案,可能不是最好的,如有更好思路,欢迎各路大神提供参考意见。现把整体解决思路整理如下。

 

0.打开应用程序exe时不设置任何提权操作代码功能。动态判断当前Windows用户的角色,如果是Administrator,那么他的权限已经足够使用,在执行COM时直接使用Process方式,它的参数类ProcessStartInfo的Verb默认为空即可。

如果是普通用户(带有管理员组权限),那么他在执行其他业务时,使用普通权限即可(这样不影响一些内嵌网页组件程序工作),当执行COM时调用ShellExecuteEx进行动态提权,以保证COM组件执行成功。如果执行失败,可以参考我之前的帖子来获取具体执行失败的原因,帖子地址:https://www.cnblogs.com/jeff151013/p/16080757.html。

1.辅助方法,获取当前Windows登录用户角色身份

using System.Security.Principal;

public static class UserRoleCheckUtils
    {
        public static bool? OsCurrentUserIsAdministrator(out Exception ex)
        {
            bool? isAdministrator = null;
            ex = null;
            try
            {
                var windowsIdentity = WindowsIdentity.GetCurrent();
                var windowsPrincipal = new WindowsPrincipal(windowsIdentity);

                isAdministrator = windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
            }
            catch (Exception exception)
            {
                ex = exception;
            }
            return isAdministrator;
        }
    }

2.工具类1 ShellUtils,上代码

using System.Runtime.InteropServices;

public class ShellUtils
    {
        #region ShellExecute
        public enum ShowCommands : int
        {
            SW_HIDE = 0,
            SW_SHOWNORMAL = 1,
            SW_NORMAL = 1,
            SW_SHOWMINIMIZED = 2,
            SW_SHOWMAXIMIZED = 3,
            SW_MAXIMIZE = 3,
            SW_SHOWNOACTIVATE = 4,
            SW_SHOW = 5,
            SW_MINIMIZE = 6,
            SW_SHOWMINNOACTIVE = 7,
            SW_SHOWNA = 8,
            SW_RESTORE = 9,
            SW_SHOWDEFAULT = 10,
            SW_FORCEMINIMIZE = 11,
            SW_MAX = 11
        }
        [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr ShellExecute(
            IntPtr hwnd,
            string lpOperation,
            string lpFile,
            string lpParameters,
            string lpDirectory,
            ShowCommands nShowCmd);

        #endregion

        #region ShellExecuteEx
        private const int SwShow = 5;
        private const uint SeeMaskInvokeidlist = 12;

        /// <summary>
        /// 对指定应用程序执行某个操作
        /// </summary>
        /// <param name="lpExecInfo"></param>
        /// <returns></returns>
        [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool ShellExecuteEx(ref Shellexecuteinfo lpExecInfo);
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public struct Shellexecuteinfo
        {
            /// <summary>
            /// 结构大小,以字节为单位
            /// </summary>
            public int cbSize;
            /// <summary>
            /// 一个标志数组,用来设置其他成员的有效性。
            /// </summary>
            public uint fMask;
            /// <summary>
            /// 可选。执行ShellExecuteEx的窗口句柄,可设为NULL。
            /// </summary>
            public IntPtr hwnd;
            /// <summary>
            /// 指定执行的动作,包括:edit ,explore ,find ,open,print, properties
            /// </summary>
            [MarshalAs(UnmanagedType.LPTStr)]
            public string lpVerb;
            /// <summary>
            /// 以\0 结尾的字符串,指出 lpVerb 的操作对象的路径,被系统支持的操作包括文本的 open 、 print等
            /// </summary>
            [MarshalAs(UnmanagedType.LPTStr)]
            public string lpFile;
            /// <summary>
            /// 可选。运行/打开程序的参数,如果打开的是一个文档,则该项无效
            /// </summary>
            [MarshalAs(UnmanagedType.LPTStr)]
            public string lpParameters;
            /// <summary>
            /// 可选。指明工作目录的名字,成员没有说明,则默认为当前目录
            /// </summary>
            [MarshalAs(UnmanagedType.LPTStr)]
            public string lpDirectory;
            /// <summary>
            /// 必须。指定打开的程序的显示方式,为SW_值中的一个。
            /// </summary>
            public int nShow;
            /// <summary>
            /// 【out】如果设置SEE_MASK_NOCLOSEPROCESS S值并且ShellExecuteEx 调用成功,则该项的值大于32,如果调用失败,则将设置为 SE_ERR_XXX 的错误值。
            /// </summary>
            public IntPtr hInstApp;
            /// <summary>
            /// 一个ITEMIDLIST结构的地址,用来存储成员的特别标识符,当fMask不包括SEE_MASK_IDLIST或SEE_MASK_INVOKEIDLIST时该项被忽略
            /// </summary>
            public IntPtr lpIDList;
            /// <summary>
            /// 用以指明文件类别的名字或GUID,当fMask不包括SEE_MASK_CLASSNAME时该项被忽略
            /// </summary>
            [MarshalAs(UnmanagedType.LPTStr)]
            public string lpClass;
            /// <summary>
            /// 获得已在系统注册的文件类型的Handle,当fMask不包括SEE_MASK_HOTKEY时该项被忽略
            /// </summary>
            public IntPtr hkeyClass;
            /// <summary>
            /// 程序的热键关联,低位存储虚拟关键码(Key Code),高位存储修改标志位(HOTKEYF_),修改标志为(modifier flags)的详细列表请看WM_SETHOTKEY消息的描述,当fmask不包括SEE_MASK_HOTKEY时该项被忽略
            /// </summary>
            public uint dwHotKey;
            /// <summary>
            /// 取得对应文件类型的图标的Handle,当fMask不包括SEE_MASK_ICON时该项被忽略
            /// </summary>
            public IntPtr hIcon;
            /// <summary>
            /// 指向新启动的程序的句柄。若fMask不设为SEE_MASK_NOCLOSEPROCESS则该项值为NULL。但若程序没有启动,即使fMask设为SEE_MASK_NOCLOSEPROCESS,该值也仍为NULL。
            /// </summary>
            public IntPtr hProcess;
        }

        public static bool Execute(string verb, string fileFullPath, string arguments)
        {
            ShellUtils.Shellexecuteinfo info = new ShellUtils.Shellexecuteinfo();
            info.cbSize = Marshal.SizeOf(info);
            info.fMask = SeeMaskInvokeidlist;
            info.lpVerb = verb;
            info.lpFile = fileFullPath;
            info.lpParameters = arguments;
            info.nShow = SwShow;

            return ShellExecuteEx(ref info);
        }

        #endregion
    }

3.工具类2 AddinHelper,主要是提供操作系统登录用户角色判断、Process方法调用和ShellUtils.Execute方法的调用

{
    public static bool RegisterAddIn(string dllPath, bool isBit64)
    {
        string winDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
        string regasmPath;
        if (isBit64)
        {
            regasmPath = Path.Combine(winDir, @"Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe");
        }
        else
        {
            regasmPath = Path.Combine(winDir, @"Microsoft.NET\Framework\v4.0.30319\RegAsm.exe");
        }
   //获取当前操作系统登录用户角色是否是超级管理员
        var osCurrentUserIsAdministrator = UserRoleCheckUtils.OsCurrentUserIsAdministrator(out Exception ex);
        //如果是超管、使用process方式注册;否则使用ShellExecuteEx方式
        if (osCurrentUserIsAdministrator.HasValue && osCurrentUserIsAdministrator.Value)
        {
            return UseProcessOperateAddIn(regasmPath, " /codebase \"" + dllPath + "\"");
        }
        //如果获取用户身份失败或者是普通用户,将提权注册插件
        return ShellUtils.Execute("runas", regasmPath, dllPath);
    }

    /// <summary>
    /// 操作COM插件
    /// </summary>
    /// <param name="regasmPath"></param>
    /// <param name="dllPath">含其他操作命令</param>
    /// <returns></returns>
    public static bool UseProcessOperateAddIn(string regasmPath, string dllPath)
    {
        try
        {
            var startInfo = new ProcessStartInfo();
            startInfo.Verb = string.Empty;
            startInfo.FileName = regasmPath;
            startInfo.Arguments = dllPath;
            startInfo.WindowStyle = ProcessWindowStyle.Normal;
            startInfo.RedirectStandardOutput = true;
            startInfo.RedirectStandardError = true;
            startInfo.RedirectStandardInput = true;
            startInfo.UseShellExecute = false;
            startInfo.CreateNoWindow = true; //让窗体不显示
            startInfo.ErrorDialog = false;
            Process process = Process.Start(startInfo);
            process.EnableRaisingEvents = true;
            process.WaitForExit();
            StreamReader reader = process.StandardOutput; //截取输出流
            string output = process.StandardError.ReadToEnd();
            var rtn = process.ExitCode;
            if (rtn != 0)
                throw new Exception(output);
            return true;
        }
        catch (Exception ex)
        {
            return false;
        }
    }
}

posted @ 2022-03-31 14:15  jeff151013  阅读(707)  评论(0编辑  收藏  举报