从 Python 脚本中请求 UAC 升级?

我想让我的 Python 脚本复制 Vista 上的文件。当我运行它从一个正常的 cmd.exe窗口,没有生成错误,但文件没有被复制。如果我运行 cmd.exe“作为管理员”,然后运行我的脚本,它工作得很好。

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

Is there a way I can, from within a Python script, invoke a UAC elevation request (those dialogs that say something like "such and such app needs admin access, is this OK?")

如果这是不可能的,是否有一种方法,我的脚本可以至少检测到它没有提高,以便它可以优雅地失败?

134840 次浏览

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

  1. 编写一个清单文件,告诉 Windows 应用程序可能需要某些特权
  2. 从另一个程序内部以提高的特权运行应用程序

这个 物品更详细地解释了它是如何工作的。

如果您不想为 CreateElevatedProcess API 编写一个令人讨厌的 ctype 包装器,那么我将使用代码项目文章中解释的 ShellExecuteEx 技巧(Pywin32附带了 ShellExecute 的包装器)。怎么做到的?就像这样:

When your program starts, it checks if it has Administrator privileges, if it doesn't it runs itself using the ShellExecute trick and exits immediately, if it does, it performs the task at hand.

正如您将您的程序描述为一个“脚本”,我想这就足够满足您的需要了。

Cheers.

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

runas /user:Administrator "python your_script.py"

这可能不能完全回答你的问题,但是你也可以尝试使用升级命令能力玩具,以运行脚本与升级 UAC 特权。

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

我认为如果你使用它,它会看起来像’提升 python 你的脚本。 py’

我花了一点时间才让 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)

你可以在某个地方作为目标使用快捷方式: python yourscript.py 然后在属性和高级选择下作为管理员运行。

When the user executes the shortcut it will ask them to elevate the application.

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

节选:

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 接口,并自动指示需要管理员权限,如果您要将其复制到需要管理员权限的目录中,您将看到熟悉的对话框提示,并且还将在复制操作期间提供典型的文件进度对话框。

A variation on Jorenko's work above allows the elevated process to use the same console (but see my comment below):

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

截至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)

If you are using Python 2.x, then you should replace the last line for:

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

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

这里的一些优势是:

  • No external libraries required. It only uses ctypes and sys from standard library.
  • 同时适用于 Python2和 Python3。
  • 不需要修改文件资源或创建清单文件。
  • 如果不在 If/else 语句下面添加代码,代码将不会执行两次。
  • 您可以在最后一行中获得 API 调用的返回值,并在调用失败时执行操作(code < = 32)。检查返回值 给你
  • You can change the display method of the spawned process modifying the sixth parameter.

Documentation for the underlying ShellExecute call is here.

下面的例子建立在 MARTIN DE LA FUENTE SAAVEDRA’s优秀的工作和公认的答案。特别地,引入了两个枚举。第一种方法可以方便地指定如何打开提升的程序,第二种方法可以在需要容易地识别错误时提供帮助。请注意,如果您希望将所有命令行参数传递给新进程,那么 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()

这主要是对 Jorenko 的回答的升级,它允许在 Windows 中使用带有空格的参数,但在 Linux 上也应该能够很好地工作:) 另外,由于我们不使用 __file__,而是使用 sys.argv[0]作为可执行文件,因此将使用 cx _ zen 或 py2exe

[编辑] Disclaimer: The code in this post is outdated. 我已经将海拔代码作为一个 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:])

只是添加这个答案,以防其他人像我一样被谷歌搜索引导到这里。 我在 Python 脚本中使用了 elevate模块,在 Windows10中使用管理员特权执行脚本。

Https://pypi.org/project/elevate/

对于一行程序,将代码放在需要 UAC 的地方。

请求 UAC,如果失败,继续运行:

import ctypes, sys


ctypes.windll.shell32.IsUserAnAdmin() or ctypes.windll.shell32.ShellExecuteW(
None, "runas", sys.executable, " ".join(sys.argv), None, 1) > 32 and exit()




请求联合太空中心,如果失败,撤退:

import ctypes, sys


ctypes.windll.shell32.IsUserAnAdmin() or (ctypes.windll.shell32.ShellExecuteW(
None, "runas", sys.executable, " ".join(sys.argv), None, 1) > 32, exit())

功能风格:

# Created by BaiJiFeiLong@gmail.com at 2022/6/24
import ctypes
import sys




def request_uac_or_skip():
ctypes.windll.shell32.IsUserAnAdmin() or ctypes.windll.shell32.ShellExecuteW(
None, "runas", sys.executable, " ".join(sys.argv), None, 1) > 32 and sys.exit()




def request_uac_or_exit():
ctypes.windll.shell32.IsUserAnAdmin() or (ctypes.windll.shell32.ShellExecuteW(
None, "runas", sys.executable, " ".join(sys.argv), None, 1) > 32, sys.exit())