从 Python 脚本中请求 UAC 提升?

Posted

技术标签:

【中文标题】从 Python 脚本中请求 UAC 提升?【英文标题】:Request UAC elevation from within a Python script? 【发布时间】:2010-09-12 22:41:39 【问题描述】:

我希望我的 Python 脚本在 Vista 上复制文件。当我从普通的cmd.exe 窗口运行它时,不会产生错误,但不会复制文件。如果我运行 cmd.exe "as administator" 然后运行我的脚本,它工作正常。

这是有道理的,因为用户帐户控制 (UAC) 通常会阻止许多文件系统操作。

有没有一种方法可以让我在 Python 脚本中调用 UAC 提升请求(那些对话框会说“某某应用程序需要管理员访问权限,可以吗?”)

如果这不可能,有没有办法让我的脚本至少检测到它没有被提升,从而可以优雅地失败?

【问题讨论】:

***.com/a/1445547/1628132 按照这个答案,您可以使用 py2exe 从 .py 脚本创建一个 .exe 并使用名为 'uac_info' 的标志,这是一个非常简洁的解决方案 【参考方案1】:

似乎暂时无法提升应用程序权限以执行特定任务。 Windows 需要在程序启动时知道应用程序是否需要某些特权,并且会在应用程序执行任何需要这些特权的任务时要求用户确认。有两种方法可以做到这一点:

    编写一个清单文件,告诉 Windows 应用程序可能需要一些权限 从另一个程序中以提升的权限运行应用程序

twoarticles 更详细地解释了它是如何工作的。

如果您不想为 CreateElevatedProcess API 编写一个讨厌的 ctypes 包装器,我会做的是使用代码项目文章中解释的 ShellExecuteEx 技巧(Pywin32 带有一个用于 ShellExecute 的包装器)。如何?像这样的:

当你的程序启动时,它会检查它是否有管理员权限,如果没有,它会使用 ShellExecute 技巧自行运行并立即退出,如果有,它会执行手头的任务。

当您将程序描述为“脚本”时,我想这足以满足您的需求。

干杯。

【讨论】:

感谢这些链接,它们对我发现很多关于 UAC 的东西非常有用。 您可能需要注意的是,您可以使用 os.startfile($EXECUTABLE, "runas") 在不使用 PyWin32 的情况下执行 ShellExecute(我在安装它时遇到了问题)。 @Mike - 但runas 提出了一个新提示。并且 startfile 不接受 $EXECUTABLE. 的命令行参数 我添加了另一个答案,该技术的完整实现应该能够添加到任何 python 脚本的开头。 第二个链接的文章是“MSDN 杂志 2007 年 1 月”中的“Least Privilege: Teach Your Apps To Play Nicely With Windows Vista User Account Control”,但此问题现在仅作为 @987654325 提供@文件。【参考方案2】:

如果您的脚本总是需要管理员权限,那么:

runas /user:Administrator "python your_script.py"

【讨论】:

小心,提升!= 以管理员身份运行 我是 python 新手...你能告诉我该代码放在哪里吗? @RahatIslamKhan:打开命令提示符窗口并将其放在:命令以管理员用户身份运行your_script.py。确保你理解@Kugel's comment。【参考方案3】:

这可能无法完全回答您的问题,但您也可以尝试使用 Elevate Command Powertoy 以使用提升的 UAC 权限运行脚本。

http://technet.microsoft.com/en-us/magazine/2008.06.elevation.aspx

我认为如果你使用它,它看起来就像'提升 python yourscript.py'

【讨论】:

【参考方案4】:

我花了一点时间才让 dguaraglia 的答案起作用,所以为了节省其他人的时间,这是我为实现这个想法所做的:

import os
import sys
import win32com.shell.shell as shell
ASADMIN = 'asadmin'

if sys.argv[-1] != ASADMIN:
    script = os.path.abspath(sys.argv[0])
    params = ' '.join([script] + sys.argv[1:] + [ASADMIN])
    shell.ShellExecuteEx(lpVerb='runas', lpFile=sys.executable, lpParameters=params)
    sys.exit(0)

【讨论】:

这似乎提升然后退出......如果我输入一些打印语句,它们不会第二次执行 @JoranBeasley,您将看不到任何输出。 ShellExecuteEx 不会将其 STDOUT 发布回原始 shell。在这方面,调试将是……具有挑战性的。但提升特权的伎俩绝对有效。 @TimKeating,ActiveState 有一个能让调试更容易的秘诀:Use DebugView utility with standard python logging 在同一个控制台中获取输出似乎是不可能的,但是使用 ShellExecuteEx 的参数 nShow=5,将打开一个新的命令窗口,其中包含提升脚本的输出。 对于引用,您可以使用subprocess.list2cmdline 正确地进行。【参考方案5】:

您可以在某处创建快捷方式并作为目标使用: python yourscript.py 然后在属性和高级选择以管理员身份运行。

当用户执行快捷方式时,它会要求他们提升应用程序。

【讨论】:

【参考方案6】:

认识到这个问题是几年前提出的,我认为 frmdstryr 使用他的模块 pywinutils 在github 上提供了一个更优雅的解决方案:

摘录:

import pythoncom
from win32com.shell import shell,shellcon

def copy(src,dst,flags=shellcon.FOF_NOCONFIRMATION):
    """ Copy files using the built in Windows File copy dialog

    Requires absolute paths. Does NOT create root destination folder if it doesn't exist.
    Overwrites and is recursive by default 
    @see http://msdn.microsoft.com/en-us/library/bb775799(v=vs.85).aspx for flags available
    """
    # @see IFileOperation
    pfo = pythoncom.CoCreateInstance(shell.CLSID_FileOperation,None,pythoncom.CLSCTX_ALL,shell.IID_IFileOperation)

    # Respond with Yes to All for any dialog
    # @see http://msdn.microsoft.com/en-us/library/bb775799(v=vs.85).aspx
    pfo.SetOperationFlags(flags)

    # Set the destionation folder
    dst = shell.SHCreateItemFromParsingName(dst,None,shell.IID_IShellItem)

    if type(src) not in (tuple,list):
        src = (src,)

    for f in src:
        item = shell.SHCreateItemFromParsingName(f,None,shell.IID_IShellItem)
        pfo.CopyItem(item,dst) # Schedule an operation to be performed

    # @see http://msdn.microsoft.com/en-us/library/bb775780(v=vs.85).aspx
    success = pfo.PerformOperations()

    # @see sdn.microsoft.com/en-us/library/bb775769(v=vs.85).aspx
    aborted = pfo.GetAnyOperationsAborted()
    return success is None and not aborted    

这利用了 COM 界面,并通过熟悉的对话框提示自动指示需要管理员权限,如果您正在复制到需要管理员权限的目录,您会看到该对话框提示,并且还在复制操作期间提供典型的文件进度对话框。

【讨论】:

【参考方案7】:

Jorenko 上述工作的一个变体允许提升的进程使用相同的控制台(但请参阅下面的评论):

def spawn_as_administrator():
    """ Spawn ourself with administrator rights and wait for new process to exit
        Make the new process use the same console as the old one.
          Raise Exception() if we could not get a handle for the new re-run the process
          Raise pywintypes.error() if we could not re-spawn
        Return the exit code of the new process,
          or return None if already running the second admin process. """
    #pylint: disable=no-name-in-module,import-error
    import win32event, win32api, win32process
    import win32com.shell.shell as shell
    if '--admin' in sys.argv:
        return None
    script = os.path.abspath(sys.argv[0])
    params = ' '.join([script] + sys.argv[1:] + ['--admin'])
    SEE_MASK_NO_CONSOLE = 0x00008000
    SEE_MASK_NOCLOSE_PROCESS = 0x00000040
    process = shell.ShellExecuteEx(lpVerb='runas', lpFile=sys.executable, lpParameters=params, fMask=SEE_MASK_NO_CONSOLE|SEE_MASK_NOCLOSE_PROCESS)
    hProcess = process['hProcess']
    if not hProcess:
        raise Exception("Could not identify administrator process to install drivers")
    # It is necessary to wait for the elevated process or else
    #  stdin lines are shared between 2 processes: they get one line each
    INFINITE = -1
    win32event.WaitForSingleObject(hProcess, INFINITE)
    exitcode = win32process.GetExitCodeProcess(hProcess)
    win32api.CloseHandle(hProcess)
    return exitcode

【讨论】:

对不起。相同的控制台选项 (SEE_MASK_NO_CONSOLE) 仅在您已经被提升时才有效。我的错。【参考方案8】:

截至 2017 年,实现这一目标的简单方法如下:

import ctypes, sys

def is_admin():
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False

if is_admin():
    # Code of your program here
else:
    # Re-run the program with admin rights
    ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)

如果您使用的是 Python 2.x,则应将最后一行替换为:

ctypes.windll.shell32.ShellExecuteW(None, u"runas", unicode(sys.executable), unicode(" ".join(sys.argv)), None, 1)

还请注意,如果您将 python 脚本转换为可执行文件(使用 py2execx_freezepyinstaller 等工具),那么您应该在第四个参数中使用 sys.argv[1:] 而不是 sys.argv

这里的一些优点是:

不需要外部库。它只使用标准库中的ctypessys。 适用于 Python 2 和 Python 3。 无需修改文件资源,也无需创建清单文件。 如果您不在 if/else 语句下面添加代码,则代码永远不会被执行两次。 您可以在最后一行获取 API 调用的返回值,并在失败时执行操作(代码 here。 您可以通过修改第六个参数来更改衍生进程的显示方式。

底层 ShellExecute 调用的文档是 here。

【讨论】:

我必须使用 unicode 实例作为 ShellExecuteW 的参数(如 u'runas' 和 unicode(sys.executable))才能运行。 @Janosch,那是因为您使用的是 Python 2.x,而我的代码在 Python 3 中(所有字符串都被视为 unicodes)。不过值得一提,谢谢! @Martin 如果我从 Windows 命令行运行此代码,如下所示:“python yourcode.py”它只会打开 python.exe。有办法解决吗? @user2978216 我遇到了同样的问题。在ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, "", None, 1)sys.executable 的行中,只解析为python 解释器(例如C:\Python27\Python.exe)解决方案是将运行脚本添加为参数(替换"")。 ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1) 另请注意,为了在 python 2.x 中工作,所有字符串参数都必须是 unicode(即 u"runas"unicode(sys.executable)unicode(__file__) @HrvojeT ShellExecuteWShellExecuteA 都是对 Windows API 中的 ShellExecute 函数的调用。前者要求字符串为 unicode 格式,后者与 ANSI 格式一起使用【参考方案9】:

以下示例建立在 MARTIN DE LA FUENTE SAAVEDRA 出色的工作和公认的答案之上。特别介绍了两个枚举。第一个允许轻松指定如何打开提升的程序,第二个有助于在需要轻松识别错误时提供帮助。请注意,如果您希望将所有命令行参数传递给新进程,sys.argv[0] 可能应该替换为函数调用:subprocess.list2cmdline(sys.argv)

#! /usr/bin/env python3
import ctypes
import enum
import subprocess
import sys

# Reference:
# msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx


# noinspection SpellCheckingInspection
class SW(enum.IntEnum):
    HIDE = 0
    MAXIMIZE = 3
    MINIMIZE = 6
    RESTORE = 9
    SHOW = 5
    SHOWDEFAULT = 10
    SHOWMAXIMIZED = 3
    SHOWMINIMIZED = 2
    SHOWMINNOACTIVE = 7
    SHOWNA = 8
    SHOWNOACTIVATE = 4
    SHOWNORMAL = 1


class ERROR(enum.IntEnum):
    ZERO = 0
    FILE_NOT_FOUND = 2
    PATH_NOT_FOUND = 3
    BAD_FORMAT = 11
    ACCESS_DENIED = 5
    ASSOC_INCOMPLETE = 27
    DDE_BUSY = 30
    DDE_FAIL = 29
    DDE_TIMEOUT = 28
    DLL_NOT_FOUND = 32
    NO_ASSOC = 31
    OOM = 8
    SHARE = 26


def bootstrap():
    if ctypes.windll.shell32.IsUserAnAdmin():
        main()
    else:
       # noinspection SpellCheckingInspection
        hinstance = ctypes.windll.shell32.ShellExecuteW(
            None,
            'runas',
            sys.executable,
            subprocess.list2cmdline(sys.argv),
            None,
            SW.SHOWNORMAL
        )
        if hinstance <= 32:
            raise RuntimeError(ERROR(hinstance))


def main():
    # Your Code Here
    print(input('Echo: '))


if __name__ == '__main__':
    bootstrap()

【讨论】:

【参考方案10】:

这主要是对 Jorenko 答案的升级,它允许在 Windows 中使用带空格的参数,但在 Linux 上也应该可以很好地工作 :) 此外,将与 cx_freeze 或 py2exe 一起使用,因为我们不使用 __file__ 而是使用 sys.argv[0] 作为可执行文件

[编辑] 免责声明:本文中的代码已过时。 我已将海拔代码作为 python 包发布。 使用pip install command_runner安装

用法:

from command_runner.elevate import elevate

def main():
    """My main function that should be elevated"""
    print("Who's the administrator, now ?")

if __name__ == '__main__':
    elevate(main)

[/编辑]

import sys,ctypes,platform

def is_admin():
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        raise False

if __name__ == '__main__':

    if platform.system() == "Windows":
        if is_admin():
            main(sys.argv[1:])
        else:
            # Re-run the program with admin rights, don't use __file__ since py2exe won't know about it
            # Use sys.argv[0] as script path and sys.argv[1:] as arguments, join them as lpstr, quoting each parameter or spaces will divide parameters
            lpParameters = ""
            # Litteraly quote all parameters which get unquoted when passed to python
            for i, item in enumerate(sys.argv[0:]):
                lpParameters += '"' + item + '" '
            try:
                ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, lpParameters , None, 1)
            except:
                sys.exit(1)
    else:
        main(sys.argv[1:])

【讨论】:

【参考方案11】:

只需添加此答案,以防其他人像我一样被 Google 搜索定向到此处。 我在 Python 脚本中使用了 elevate 模块,并在 Windows 10 中使用管理员权限执行了该脚本。

https://pypi.org/project/elevate/

【讨论】:

嘿,我尝试使用elevate 模块,但出现“系统无法访问文件”错误,您知道为什么会发生这种情况吗? @paxos1977 你能发布一个代码sn-p来演示这个错误吗?谢谢! 简单而简洁的解决方案 - 在 Windows 10 中运行良好 (Version 10.0.19043.1415)

以上是关于从 Python 脚本中请求 UAC 提升?的主要内容,如果未能解决你的问题,请参考以下文章

如果路径受到保护,请求 Windows Vista UAC 提升?

Desktop Bridge allowElevation 受限功能无法正常工作,UAC 后出现错误“请求的操作需要提升”

UAC提升不允许拖放[重复]

绕过 UAC 以允许从非提升源拖放到我们提升的应用程序

从使用更安全令牌创建的进程调用时,RunAs Verb 不显示 UAC 弹出窗口且不提升

Vista/7 UAC:如何降低进程权限