如何在Windows上以提升的权限运行脚本

88

我正在编写一个pyqt应用程序,需要执行管理员任务。 我希望以提升的权限启动我的脚本。 我知道这个问题在SO或其他论坛上被问了很多次。 但是人们建议的解决方案是查看此SO问题: Request UAC elevation from within a Python script?

然而,我无法执行链接中给出的示例代码。 我已将此代码放置在主文件顶部并尝试执行它。

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)
print "I am root now."

实际上它请求提升权限,但打印行永远不会执行。有人能帮我成功运行上面的代码吗。


4
移除 sys.exit(0) 并将 print 放在 if 块内。 - Leonardo.Z
谢谢。那个有效。如果您能将其发布为答案,我会接受它作为答案。 - sundar_ima
1
我的第一个注释中有一个错误。打印语句的位置是正确的,在将其放入if块内后,当脚本由asadmin命令运行时,它将不会被执行。 - Leonardo.Z
14个回答

112

截至2023年2月19日的更新

下面脚本的更新现在已经由同一作者制作成了Python包。您可以从PyPi安装它,PyPi的链接为https://pypi.org/project/pyuac/,源代码/主页位于https://github.com/Preston-Landers/pyuac。使用以下命令进行安装:

pip install pyuac
pip install pypiwin32

该包的直接使用方法是:

import pyuac

def main():
    print("Do stuff here that requires being run as an admin.")
    # The window will disappear as soon as the program exits!
    input("Press enter to close the window. >")

if __name__ == "__main__":
    if not pyuac.isUserAdmin():
        print("Re-launching as admin!")
        pyuac.runAsAdmin()
    else:        
        main()  # Already an admin here.

或者,如果您想使用装饰器:

from pyuac import main_requires_admin

@main_requires_admin
def main():
    print("Do stuff here that requires being run as an admin.")
    # The window will disappear as soon as the program exits!
    input("Press enter to close the window. >")

if __name__ == "__main__":
    main()

原始答案

谢谢大家的回复。我使用Preston Landers在2010年编写的模块/脚本让我的脚本正常工作了。在浏览了两天互联网后,我找到了这个脚本。它深藏在pywin32邮件列表中。使用此脚本可以更轻松地检查用户是否为管理员,如果不是,可以请求UAC /管理员权限。它提供了单独的窗口输出以显示代码正在执行的内容。此脚本还包括如何使用代码的示例。为了惠及所有正在寻找Windows上UAC的人,请查看此代码。可以像下面这样从您的主要脚本中使用:

import admin

if not admin.isUserAdmin():
    admin.runAsAdmin()

实际的代码(在模块中)是:

#!/usr/bin/env python
# -*- coding: utf-8; mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vim: fileencoding=utf-8 tabstop=4 expandtab shiftwidth=4

# (C) COPYRIGHT © Preston Landers 2010
# Released under the same license as Python 2.6.5

 
import sys, os, traceback, types
 
def isUserAdmin():
   
    if os.name == 'nt':
        import ctypes
        # WARNING: requires Windows XP SP2 or higher!
        try:
            return ctypes.windll.shell32.IsUserAnAdmin()
        except:
            traceback.print_exc()
            print "Admin check failed, assuming not an admin."
            return False
    elif os.name == 'posix':
        # Check for root on Posix
        return os.getuid() == 0
    else:
        raise RuntimeError, "Unsupported operating system for this module: %s" % (os.name,)
   
def runAsAdmin(cmdLine=None, wait=True):
 
    if os.name != 'nt':
        raise RuntimeError, "This function is only implemented on Windows."
   
    import win32api, win32con, win32event, win32process
    from win32com.shell.shell import ShellExecuteEx
    from win32com.shell import shellcon
   
    python_exe = sys.executable
 
    if cmdLine is None:
        cmdLine = [python_exe] + sys.argv
    elif type(cmdLine) not in (types.TupleType,types.ListType):
        raise ValueError, "cmdLine is not a sequence."
    cmd = '"%s"' % (cmdLine[0],)
    # XXX TODO: isn't there a function or something we can call to massage command line params?
    params = " ".join(['"%s"' % (x,) for x in cmdLine[1:]])
    cmdDir = ''
    showCmd = win32con.SW_SHOWNORMAL
    #showCmd = win32con.SW_HIDE
    lpVerb = 'runas'  # causes UAC elevation prompt.
   
    # print "Running", cmd, params
 
    # ShellExecute() doesn't seem to allow us to fetch the PID or handle
    # of the process, so we can't get anything useful from it. Therefore
    # the more complex ShellExecuteEx() must be used.
 
    # procHandle = win32api.ShellExecute(0, lpVerb, cmd, params, cmdDir, showCmd)
 
    procInfo = ShellExecuteEx(nShow=showCmd,
                              fMask=shellcon.SEE_MASK_NOCLOSEPROCESS,
                              lpVerb=lpVerb,
                              lpFile=cmd,
                              lpParameters=params)
 
    if wait:
        procHandle = procInfo['hProcess']    
        obj = win32event.WaitForSingleObject(procHandle, win32event.INFINITE)
        rc = win32process.GetExitCodeProcess(procHandle)
        #print "Process handle %s returned code %s" % (procHandle, rc)
    else:
        rc = None
 
    return rc
 
def test():
    rc = 0
    if not isUserAdmin():
        print "You're not an admin.", os.getpid(), "params: ", sys.argv
        #rc = runAsAdmin(["c:\\Windows\\notepad.exe"])
        rc = runAsAdmin()
    else:
        print "You are an admin!", os.getpid(), "params: ", sys.argv
        rc = 0
    x = raw_input('Press Enter to exit.')
    return rc
 
 
if __name__ == "__main__":
    sys.exit(test())

非常感谢您的回答。我遇到了一个问题,当使用shellexecuteEX执行我的Qt GUI时,它没有完全显示出来,您提供的详细命令和showcommand帮助我解决了这个问题。谢谢! :) - Ecno92
非常好,我正在尝试弄清楚如何编写我的应用程序的安装程序。 - Elvis Teixeira
1
你能在pywin32邮件列表中发布代码来源的链接吗? - Mr_and_Mrs_D
11
以下是同一个脚本在 Github 上针对 Python3 的分支链接:https://gist.github.com/sylvainpelissier/ff072a6759082590a4fe8f7e070a4952 - Hrvoje T
1
@HrvojeT 谢谢你!我花了很长时间才弄清楚如何以管理员身份运行Python进程。 - takanuva15
显示剩余4条评论

14

在你借鉴代码的答案评论中,有人说ShellExecuteEx不会将其STDOUT发送回原始shell。因此,即使代码可能运行良好,你也看不到"I am root now"。

尝试写入文件而不是打印内容:

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)
with open("somefilename.txt", "w") as out:
    print >> out, "i am root"

然后在文件中查找。


但它根本没有弹出我的PyQt窗口。使用ZetCode测试了代码http://zetcode.com/gui/pyqt4/firstprograms/。 - sundar_ima
但是这会提示授权。有没有办法可以跳过它? - deenbandhu
2
@deenbandhu 如果你可以跳过提示,那么每个编写的病毒都会这样做,你将不断被感染。这是一个永远不会消失的功能。 - Mark Ransom

12

我发现了一个非常简单的解决方案。

  1. 创建 python.exe 的快捷方式
  2. 将快捷方式目标更改为类似于 C:\xxx\...\python.exe your_script.py 的内容
  3. 在快捷方式属性面板中点击“高级”,并勾选“以管理员身份运行”选项

由于我使用的是中文版 Windows,所以这些选项的拼写是否正确我不确定。


4
这种方法也可以解释为:以管理员身份运行您的Python,然后执行脚本。然后自动所有脚本都将具有管理员访问权限。 - Deepak Yadav
这并没有回答问题,即如何在脚本内请求UAC提升。这需要用户采取行动,而不仅仅是接受/拒绝弹出的额外权限提示框。 - LightCC

6

以下是只需要 ctypes 模块的解决方案。支持使用 pyinstaller 封装的程序。

#!python
# coding: utf-8
import sys
import ctypes

def run_as_admin(argv=None, debug=False):
    shell32 = ctypes.windll.shell32
    if argv is None and shell32.IsUserAnAdmin():
        return True

    if argv is None:
        argv = sys.argv
    if hasattr(sys, '_MEIPASS'):
        # Support pyinstaller wrapped program.
        arguments = map(unicode, argv[1:])
    else:
        arguments = map(unicode, argv)
    argument_line = u' '.join(arguments)
    executable = unicode(sys.executable)
    if debug:
        print 'Command line: ', executable, argument_line
    ret = shell32.ShellExecuteW(None, u"runas", executable, argument_line, None, 1)
    if int(ret) <= 32:
        return False
    return None


if __name__ == '__main__':
    ret = run_as_admin()
    if ret is True:
        print 'I have admin privilege.'
        raw_input('Press ENTER to exit.')
    elif ret is None:
        print 'I am elevating to admin privilege.'
        raw_input('Press ENTER to exit.')
    else:
        print 'Error(ret=%d): cannot elevate privilege.' % (ret, )

你好,它确实要求提升权限,但是一旦我点击“是”,由于某种奇怪的原因,程序会运行两次。对此有什么建议吗? - tomSurge
1
它运行两次,因为第一次它作为管理员启动程序的新实例。如果该函数不返回True,则需要退出程序。或者,您可以稍微更改一下,使用此程序作为“发射台”来启动另一个程序。 - BuvinJ
谢谢!有没有办法在原始终端中获取新进程的输出,而不是打开另一个窗口? - Niklas R

5

这是一种使用 stdout 重定向的解决方案:

def elevate():
    import ctypes, win32com.shell.shell, win32event, win32process
    outpath = r'%s\%s.out' % (os.environ["TEMP"], os.path.basename(__file__))
    if ctypes.windll.shell32.IsUserAnAdmin():
        if os.path.isfile(outpath):
            sys.stderr = sys.stdout = open(outpath, 'w', 0)
        return
    with open(outpath, 'w+', 0) as outfile:
        hProc = win32com.shell.shell.ShellExecuteEx(lpFile=sys.executable, \
            lpVerb='runas', lpParameters=' '.join(sys.argv), fMask=64, nShow=0)['hProcess']
        while True:
            hr = win32event.WaitForSingleObject(hProc, 40)
            while True:
                line = outfile.readline()
                if not line: break
                sys.stdout.write(line)
            if hr != 0x102: break
    os.remove(outpath)
    sys.stderr = ''
    sys.exit(win32process.GetExitCodeProcess(hProc))

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

1
这导致了一个错误,Python 3.8 File ".\main.py", line 26, in elevate with open(outpath, 'w+', 0) as outfile: ValueError: can't have unbuffered text I/O - CircleOnCircles

4
  1. 制作批处理文件
  2. 添加Python.exe "(你的.py文件在这里)",用引号括起来
  3. 保存批处理文件
  4. 右键单击,然后选择以管理员身份运行

4
值得一提的是,如果您打算使用 PyInstaller打包您的应用程序,并希望避免自己支持该功能,则可以传递--uac-admin--uac-uiaccess参数以请求启动时的UAC提升。

1
使用pyuac,它是由Preston Landers创建的原始管理脚本的更新版本。Python项目链接:https://pypi.org/project/pyuac/ Github链接:https://github.com/Preston-Landers/pyuac 这对我很有效。
from pyuac import main_requires_admin

@main_requires_admin
def main():
    print("Do stuff here that requires being run as an admin.")
    # The window will disappear as soon as the program exits!
    input("Press enter to close the window. >")

if __name__ == "__main__":
    main()

1
JetBrains的WinElevator(签名的elevator.exe和launcher.exe 可在这里找到)允许您生成一个子进程,请求提升的特权,同时保持stdin/stdout/stderr不变:
import ctypes
import subprocess
import sys

if not ctypes.windll.shell32.IsUserAnAdmin():
    print("not an admin, restarting...")
    subprocess.run(["launcher.exe", sys.executable, *sys.argv])
else:
    print("I'm an admin now.")

> python example.py
not an admin, restarting...
# UAC prompt is shown
I'm an admin now.

1
这对我有用:

这对我有用:


import win32com.client as client

required_command = "cmd" # Enter your command here

required_password = "Simple1" # Enter your password here

def run_as(required_command, required_password):
    shell = client.Dispatch("WScript.shell")
    shell.Run(f"runas /user:administrator {required_command}")
    time.sleep(1)
    shell.SendKeys(f"{required_password}\r\n", 0)


if __name__ = '__main__':
    run_as(required_command, required_password)

以下是我在上面代码中使用的参考文献: https://win32com.goermezer.de/microsoft/windows/controlling-applications-via-sendkeys.html https://www.oreilly.com/library/view/python-cookbook/0596001673/ch07s16.html

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接