【问题标题】:Conditional Windows administrator prompt有条件的 Windows 管理员提示
【发布时间】:2013-06-19 01:05:32
【问题描述】:

我正在编写一个 C++ DLL 来连接到 MySQL 数据库。除其他外,它尝试向 Windows 防火墙列表添加一个例外,以便它可以通过 TCP(端口 3306)连接到远程 MySQL 服务器。当我尝试添加端口时,我得到一个 HRESULTE_ACCESSDENIED,除非我以管理员身份运行该程序。

我想提示用户输入管理员密码,但前提是端口不在例外列表中。这意味着我不能只创建一个UAC manifest,因为据我所知,设置level="requireAdministrator" 总是会提示输入管理员密码。我可以有条件地调出管理员提示符吗?

【问题讨论】:

  • 您应该在安装过程中处理这个问题,而不是程序本身。
  • @Wug 我只是在写一个 DLL,而不是一个完整的可执行文件,所以没有安装程序。
  • 也许应该有一个。这个 dll 会作为另一个程序的一部分安装吗?该程序可能会运行安装程序,并且可能需要管理权限。你可以在你的 DLL 中添加一个函数来执行防火墙设置并从应用程序安装程序中点击它。
  • @1" 那么正确的做法是让你的 DLL 的用户处理这个问题。这不应该是你的问题。否则,你可以将你的程序一分为二:没有清单的 DLL,和.exe 带有一个清单,您的 DLL 仅在需要时运行。
  • 你写这个DLL是为了什么?你的市场/目标/观众是谁?

标签: c++ windows uac windows-firewall


【解决方案1】:

只要您获得 E_ACCESSDENIED,您就可以使用带有“runas”动词的 ShellExecuteEx 重新启动您的程序。像这样的

        wchar_t szPath[MAX_PATH];
        if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath)))
        {`enter code here`
            // Launch itself as administrator.
            SHELLEXECUTEINFO sei = { sizeof(sei) };
            sei.lpVerb = L"runas";
            sei.lpFile = szPath;
            sei.lpParameters=L"admin";
            sei.nShow = SW_NORMAL;

            if (!ShellExecuteEx(&sei))
            {
                DWORD dwError = GetLastError();
                if (dwError == ERROR_CANCELLED)
                {
                    ExitProcess(0);
                }
            }
            else
            {
                ExitProcess(0);
            }
        }

【讨论】:

  • 这在软件中是非常糟糕的行为。除非软件需要持续运行的管理权限(在这种情况下,它不应该要求它们,但总是需要它们),它不应该要求它们。您将无法在任何需要在没有用户输入的情况下运行的系统中使用此 DLL,因为防火墙配置的更改可能会使其请求许可并且永远不会运行。
  • 哈哈,你打败了我。随意将有关 RunDll 的信息合并到您的答案中,因为它直接适用于 OP 需要做的事情。
  • 如果DLL只需要这样做一次,并且调用它时用户总是在计算机上,这不是很好吗?
  • 如果调用时他们不在计算机上怎么办?如果有人在没有 GUI 访问权限的情况下远程在服务器上使用您的插件怎么办? runas 提示符在哪里?如果插件被授予管理访问权限,那是什么阻止我编写一个修补内核并安装 rootkit 的插件?最好在安装时设置一次。如果你这样做,它在运行时将永远不需要管理权限。
  • @Wug:实际上,大多数软件无法在非交互式环境中有意义地使用。除非用户插件所使用的软件自然是非交互式的,否则插件没有理由不显示提升提示。
【解决方案2】:

一旦启动应用程序,就无法提升它的权限。在您的情况下,一种解决方法是使用ShellExecuteEx 启动具有提升权限的小型外部应用程序并在那里添加防火墙例外。添加异常后,进行必要的清理并退出。然后,您可以等待应用程序完成后再继续执行。


非常感谢Cody Gray 指出why RunDll should not be used 和额外的blog entry by Raymond Chen,其中包含有关RunDll 问题的更多信息

【讨论】:

  • 我不建议这样做; you should not use RunDll32。此外,唯一可行的方法是,如果已明确编写 DLL 以与 RunDll32 一起使用。函数签名必须完全匹配,否则您可能会损坏堆栈。这不仅是documented in a KB article,还是Raymond Chen has blogged about
  • @CodyGray 感谢您指出这一点。我不知道它已被弃用。我已经更新了我的答案。
  • 另一种选择是使用 COM,正如 Raymond 建议的那样。我相信有一种方法可以提升 COM 对象,尽管我不记得细节了。
【解决方案3】:

根据您的要求,我正在根据我的评论做出答复。


执行此操作的正确方法是让您的 DLL 的用户自己处理这种情况。作为一名 API 开发人员,您绝对要为防火墙之类的事情烦恼:API 用户和/或最终用户确保它正常工作,或者您可以优雅地失败。正如其他人所说,最终用户无法回答 UAC 请求(例如在无头服务器上)的原因有很多,因此您不能依赖在交互式上下文中使用您的 DLL。这根本不是你的责任。


如果您真的必须继续您最初的想法(我再次强调,恕我直言,这是一个坏主意),我最好的选择是将您的 DLL 一分为二:

  • DLL 本身,没有任何清单,因此可以在任何用户下运行,
  • 和一个单独的 .exe 带有需要管理员权限的清单,只有在需要时才会由您的 DLL 运行。

只需要求两者都存储在同一目录中,这样您就可以使用GetModuleFileName 轻松找到您的 DLL(以及 .exe)目录。

其他人指出了runasRunDll(IMO 的答案同样有效),但我更多的是 Unix-y 类型,因此我建议完全使用单独的二进制文件。从长远来看,我发现它更容易维护。


“介于两者之间”的解决方案是您的 DLL 根本不打扰防火墙(应该如此),但您提供了一个完全独立的工具(带有清单的.exe)帮助您的用户设置防火墙在他们需要的时候正确。这可能是最好的解决方案:简洁的设计(责任分离),并且您仍然向用户提供所有必需的工具。

【讨论】:

  • 我完全不同意开发人员不应处理防火墙设置的说法。如果一个软件需要防火墙例外,它应该创建它。这是实际应用程序中的常见做法。
  • @HarryJohnston,仅仅因为它可能是 一些 产品类别中的常见 做法(即不关心适合的消费者程序)企业环境)并不意味着它是好的实践。作为开发人员,我们永远不应忘记用户的机器是在他们(或网络管理员)的排他控制之下,而不是我们的。但我承认,看到管理员发布关于行为不端程序的开发人员的帖子总是很有趣......(如果你不是那个错误的人,那就是)
  • 碰巧,我是一名企业系统管理员,我更喜欢软件来创建它需要运行的异常。如果我不希望他们这样做,我只需通过组策略禁用本地创建的异常 - 但是我必须弄清楚异常是什么并为每个应用程序手动输入它们。那是我不需要的额外工作。对于典型的最终用户来说,情况更糟,因为这意味着他们需要帮助才能使程序正常运行。应用程序应该首先询问,不幸的是很多人没有这样做,但它们不应该只让用户找出问题所在。
  • 相关:我更喜欢在首次运行时安装软件的正确安装程序。但是,这对于插件并不总是可行的,因为您必须遵循父软件的约定。在这种情况下,拥有一个可以手动运行或在首次运行时自动运行的单独安装工具听起来是个好主意。
猜你喜欢
  • 1970-01-01
  • 2012-02-17
  • 1970-01-01
  • 1970-01-01
  • 2016-01-29
  • 2014-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多