我希望我的Python脚本在Vista上复制文件。当我从普通cmd.exe
窗口运行它时,不会生成任何错误,但不会复制文件。如果我以cmd.exe
“管理员身份”运行,然后运行我的脚本,则可以正常运行。
这是有道理的,因为用户帐户控制(UAC)通常会阻止许多文件系统操作。
我有没有办法从Python脚本中调用UAC提升请求(这些对话框中会说“诸如此类应用需要管理员访问权限,这样可以吗?”)
如果这不可能,那么我的脚本是否可以至少检测到未提升的脚本,从而可以正常地失败?
我希望我的Python脚本在Vista上复制文件。当我从普通cmd.exe
窗口运行它时,不会生成任何错误,但不会复制文件。如果我以cmd.exe
“管理员身份”运行,然后运行我的脚本,则可以正常运行。
这是有道理的,因为用户帐户控制(UAC)通常会阻止许多文件系统操作。
我有没有办法从Python脚本中调用UAC提升请求(这些对话框中会说“诸如此类应用需要管理员访问权限,这样可以吗?”)
如果这不可能,那么我的脚本是否可以至少检测到未提升的脚本,从而可以正常地失败?
Answers:
截至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脚本到一个可执行文件(使用工具,如py2exe
,cx_freeze
,pyinstaller
),那么你应该使用sys.argv[1:]
,而不是sys.argv
在第四个参数。
这里的一些优点是:
ctypes
和sys
来自标准库。底层ShellExecute调用的文档在此处。
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__)
)
ShellExecuteW
并且ShellExecuteA
是调用ShellExecute
Windows API中的功能。前者责成字符串是Unicode格式,后者使用ANSI格式
我花了一些时间使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)
subprocess.list2cmdline
它来正确执行。
似乎暂时无法提升应用程序特权,以供您执行特定任务。Windows需要在程序开始时知道应用程序是否需要某些特权,并会要求用户确认应用程序何时执行需要这些特权的任何任务。有两种方法可以做到这一点:
如果您不想为CreateElevatedProcess API编写讨厌的ctypes包装器,那么我将使用代码项目文章中介绍的ShellExecuteEx技巧(Pywin32随附ShellExecute的包装器)。怎么样?像这样:
当您的程序启动时,它会检查它是否具有管理员权限,如果不是,则使用ShellExecute技巧自行运行并立即退出,如果有,它将执行手头的任务。
当您将程序描述为“脚本”时,我想这足以满足您的需求。
干杯。
runas
会弹出一个新提示。并且startfile不接受以下命令行参数$EXECUTABLE.
.chm
文件提供。
以下示例基于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()
认识到这个问题是在几年前提出的,我认为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界面,并通过熟悉的对话框提示自动指示需要管理员权限,您会看到该对话框提示您是否正在复制到需要管理员权限的目录中,并且还会在复制操作期间提供典型的文件进度对话框。
这可能无法完全回答您的问题,但是您也可以尝试使用Elevate Command Powertoy来以提升的UAC特权运行脚本。
http://technet.microsoft.com/zh-cn/magazine/2008.06.elevation.aspx
我认为,如果使用它,它将看起来像“提升python yourscript.py”
如果您的脚本始终需要管理员权限,则:
runas /user:Administrator "python your_script.py"
上面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
这主要是对Jorenko答案的升级,它允许在Windows中使用带空格的参数,但在Linux上也应能很好地工作:)此外,由于我们不使用__file__
而是sys.argv[0]
作为可执行文件使用,所以它将与cx_freeze或py2exe一起使用
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:])