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

Posted

技术标签:

【中文标题】如何在 Windows 上以提升的权限运行脚本【英文标题】:How to run script with elevated privilege on windows 【发布时间】:2013-11-09 10:36:43 【问题描述】:

我正在编写一个需要执行管理任务的 pyqt 应用程序。我宁愿以提升权限开始我的脚本。我知道这个问题在 SO 或其他论坛中被问过很多次。但是人们建议的解决方案是看看这个 SO question 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."

它实际上要求提升权限,但打印行永远不会被执行。有人可以帮助我成功运行上述代码。提前致谢。

【问题讨论】:

删除sys.exit(0)并将print放在if块内 谢谢。那行得通。如果您可以作为答案发布,我会接受。 我的第一条评论有错误。 print语句的位置是对的,放到if块里面后,asadmin命令运行脚本时就不会执行了。 【参考方案1】:

谢谢大家的回复。我的脚本与 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())

【讨论】:

非常感谢您的回答。我遇到的问题是我的 Qt GUI 在使用 shellexecuteEX 执行时没有完全显示,您使用 showcommand 的广泛命令帮助我使其工作。谢谢! :) 谢谢,必须添加 nShow 和 fMask 参数才能使用 Qt gui。 你能在 pywin32 邮件列表中发布代码来自的链接吗?? 这是在 github 上为 python3 gist.github.com/sylvainpelissier/… 分叉的相同脚本 @HrvojeT 谢谢你!我花了很长时间才弄清楚如何以管理员身份运行 python 进程【参考方案2】:

在 answer you took the code from 的 cmets 中,有人说 ShellExecuteEx 不会将其 STDOUT 发布回原始 shell。所以你不会看到“我现在是 root”,即使代码可能工作正常。

不要打印东西,而是尝试写入文件:

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 zetcode.com/gui/pyqt4/firstprograms 测试了代码 但这会提示您获得许可。有什么办法可以跳过这个 @deenbandhu 如果您可以跳过提示,那么曾经编写的每个病毒都会这样做,并且您会不断受到感染。这是一个永远不会消失的功能。【参考方案3】:

我找到了一个非常简单的方法来解决这个问题。

    python.exe 创建快捷方式 将快捷方式目标更改为C:\xxx\...\python.exe your_script.py 点击快捷方式属性面板中的“高级...”,然后点击“以管理员身份运行”选项

我不确定这些选项的拼写是否正确,因为我使用的是中文版的Windows。

【讨论】:

方式也可以解释为:以管理员身份运行python,然后执行脚本。然后自动所有脚本都将具有管理员访问权限。【参考方案4】:

这是一个带有标准输出重定向的解决方案:

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()

【讨论】:

这导致了一个错误,python 3.8 ``` File ".\main.py", line 26, in elevate with open(outpath, 'w+', 0) as outfile: ValueError: can '没有无缓冲的文本 I/O```【参考方案5】:

这是一个只需要 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, )

【讨论】:

您好,它确实要求提升,但是一旦我单击“是”,由于某种奇怪的原因,程序会运行两次。对此有何意见? 它运行了两次,因为它第一次是以管理员身份启动程序的新实例。如果他的函数没有返回 True,你需要退出程序。或者,您可以稍微改变一下以启动一个不同的程序,使用这个只是一个“启动板”。 谢谢!有没有办法在原始终端中获取新进程的输出而不打开额外的窗口?【参考方案6】:
    制作批处理文件 用引号添加python.exe“(这里是你的py文件)” 保存批处理文件 右键单击,然后单击以管理员身份运行

【讨论】:

【参考方案7】:

我可以确认 delphifirst 的解决方案有效,并且是解决以提升的权限运行 python 脚本问题的最简单、最简单的解决方案。

我创建了 python 可执行文件 (python.exe) 的快捷方式,然后通过在调用 python.exe 后添加我的脚本名称来修改快捷方式。接下来,我在快捷方式的“兼容性选项卡”上选中了“以管理员身份运行”。执行快捷方式时,系统会提示您以管理员身份运行脚本的权限。

我的特定 python 应用程序是一个安装程序。该程序允许安装和卸载另一个 python 应用程序。在我的例子中,我创建了两个快捷方式,一个名为“appname install”,另一个名为“appname uninstall”。两个快捷方式之间的唯一区别是 python 脚本名称后面的参数。在安装程序版本中,参数是“安装”。在卸载版本中,参数是“卸载”。安装程序脚本中的代码评估提供的参数并根据需要调用适当的函数(安装或卸载)。

我希望我的解释能帮助其他人更快地弄清楚如何以提升的权限运行 python 脚本。

【讨论】:

【参考方案8】:

如果你的工作目录不同于你可以使用 lpDirectory

    procInfo = ShellExecuteEx(nShow=showCmd,
                          lpVerb=lpVerb,
                          lpFile=cmd,
                          lpDirectory= unicode(direc),
                          lpParameters=params)

如果更改路径不是一个理想的选择,将会派上用场 删除 python 3.X 的 unicode

【讨论】:

【参考方案9】:

确保路径中有python,如果没有,win键+r,输入“%appdata%”(不带引号)打开本地目录,然后进入程序目录,打开python然后选择你的python版本目录.单击文件选项卡并选择复制路径并关闭文件资源管理器。

然后再次执行 win 键 + r,输入 control 并回车。搜索环境变量。点击结果,你会得到一个窗口。在右下角单击环境变量。在系统端查找路径中,选择它并单击编辑。 在新窗口中,单击新建并将路径粘贴到那里。单击确定,然后在第一个窗口中应用。重新启动您的电脑。然后最后一次执行 win + r,输入 cmd 并执行 ctrl + shift + enter。授予权限并打开文件资源管理器,转到您的脚本并复制其路径。返回 cmd ,输入“python”并粘贴路径并回车。完成

【讨论】:

【参考方案10】:

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

【讨论】:

【参考方案11】:

我想要一个更增强的版本,所以我最终得到了一个模块,它允许: 如果需要,UAC 请求,从非特权实例(使用 ipc 和网络端口)和其他一些糖果打印和记录。用法只是在脚本中插入 elevateme() :在非特权中,它侦听特权打印/日志,然后退出返回 false,在特权实例中,它立即返回 true。 支持pyinstaller。

原型:

# xlogger : a logger in the server/nonprivileged script
# tport : open port of communication, 0 for no comm [printf in nonprivileged window or silent]
# redir : redirect stdout and stderr from privileged instance
#errFile : redirect stderr to file from privileged instance
def elevateme(xlogger=None, tport=6000, redir=True, errFile=False):

winadmin.py

#!/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
# (C) COPYRIGHT © Matteo Azzali 2020
# Released under the same license as Python 2.6.5/3.7


import sys, os
from traceback import print_exc
from multiprocessing.connection import Listener, Client
import win32event #win32com.shell.shell, win32process
import builtins as __builtin__ # python3

# debug suffixes for remote printing
dbz=["","","",""] #["J:","K:", "G:", "D:"]
LOGTAG="LOGME:"

wrconn = None


#fake logger for message sending
class fakelogger:
    def __init__(self, xlogger=None):
        self.lg = xlogger
    def write(self, a):
        global wrconn
        if wrconn is not None:
            wrconn.send(LOGTAG+a)
        elif self.lg is not None:
            self.lg.write(a)
        else:
            print(LOGTAG+a)
        

class Writer():
    wzconn=None
    counter = 0
    def __init__(self, tport=6000,authkey=b'secret password'):
        global wrconn
        if wrconn is None:
            address = ('localhost', tport)
            try:
                wrconn = Client(address, authkey=authkey)
            except:
                wrconn = None
            wzconn = wrconn
            self.wrconn = wrconn
        self.__class__.counter+=1
        
    def __del__(self):
        self.__class__.counter-=1
        if self.__class__.counter == 0 and wrconn is not None:
            import time
            time.sleep(0.1) # slows deletion but is enough to print stderr
            wrconn.send('close')
            wrconn.close()
    
    def sendx(cls, mesg):
        cls.wzconn.send(msg)
        
    def sendw(self, mesg):
        self.wrconn.send(msg)
        

#fake file to be passed as stdout and stderr
class connFile():
    def __init__(self, thekind="out", tport=6000):
        self.cnt = 0
        self.old=""
        self.vg=Writer(tport)
        if thekind == "out":
            self.kind=sys.__stdout__
        else:
            self.kind=sys.__stderr__
        
    def write(self, *args, **kwargs):
        global wrconn
        global dbz
        from io import StringIO # # Python2 use: from cStringIO import StringIO
        mystdout = StringIO()
        self.cnt+=1
        __builtin__.print(*args, **kwargs, file=mystdout, end = '')
        
        #handles "\n" wherever it is, however usually is or string or \n
        if "\n" not in mystdout.getvalue():
            if mystdout.getvalue() != "\n":
                #__builtin__.print("A:",mystdout.getvalue(), file=self.kind, end='')
                self.old += mystdout.getvalue()
            else:
                #__builtin__.print("B:",mystdout.getvalue(), file=self.kind, end='')
                if wrconn is not None:
                    wrconn.send(dbz[1]+self.old)
                else:
                    __builtin__.print(dbz[2]+self.old+ mystdout.getvalue(), file=self.kind, end='')
                    self.kind.flush()
                self.old=""
        else:
                vv = mystdout.getvalue().split("\n")
                #__builtin__.print("V:",vv, file=self.kind, end='')
                for el in vv[:-1]:
                    if wrconn is not None:
                        wrconn.send(dbz[0]+self.old+el)
                        self.old = ""
                    else:
                        __builtin__.print(dbz[3]+self.old+ el+"\n", file=self.kind, end='')
                        self.kind.flush()
                        self.old=""
                self.old=vv[-1]

    def open(self):
        pass
    def close(self):
        pass
    def flush(self):
        pass
        
        
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:
        print("Unsupported operating system for this module: %s" % (os.name,))
        exit()
        #raise (RuntimeError, "Unsupported operating system for this module: %s" % (os.name,))

def runAsAdmin(cmdLine=None, wait=True, hidden=False):

    if os.name != 'nt':
        raise (RuntimeError, "This function is only implemented on Windows.")

    import win32api, win32con, win32process
    from win32com.shell.shell import ShellExecuteEx

    python_exe = sys.executable
    arb=""
    if cmdLine is None:
        cmdLine = [python_exe] + sys.argv
    elif not isinstance(cmdLine, (tuple, list)):
        if isinstance(cmdLine, (str)):
            arb=cmdLine
            cmdLine = [python_exe] + sys.argv
            print("original user", arb)
        else:
            raise( ValueError, "cmdLine is not a sequence.")
    cmd = '"%s"' % (cmdLine[0],)

    params = " ".join(['"%s"' % (x,) for x in cmdLine[1:]])
    if len(arb) > 0:
        params += " "+arb
    cmdDir = ''
    if hidden:
        showCmd = win32con.SW_HIDE
    else:
        showCmd = win32con.SW_SHOWNORMAL
    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=64,
                              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 = procInfo['hProcess']

    return rc


# xlogger : a logger in the server/nonprivileged script
# tport : open port of communication, 0 for no comm [printf in nonprivileged window or silent]
# redir : redirect stdout and stderr from privileged instance
#errFile : redirect stderr to file from privileged instance
def elevateme(xlogger=None, tport=6000, redir=True, errFile=False):
    global dbz
    if not isUserAdmin():
        print ("You're not an admin.", os.getpid(), "params: ", sys.argv)

        import getpass
        uname = getpass.getuser()
        
        if (tport> 0):
            address = ('localhost', tport)     # family is deduced to be 'AF_INET'
            listener = Listener(address, authkey=b'secret password')
        rc = runAsAdmin(uname, wait=False, hidden=True)
        if (tport> 0):
            hr = win32event.WaitForSingleObject(rc, 40)
            conn = listener.accept()
            print ('connection accepted from', listener.last_accepted)
            sys.stdout.flush()
            while True:
                msg = conn.recv()
                # do something with msg
                if msg == 'close':
                    conn.close()
                    break
                else:
                    if msg.startswith(dbz[0]+LOGTAG):
                        if xlogger != None:
                            xlogger.write(msg[len(LOGTAG):])
                        else:
                            print("Missing a logger")
                    else:
                        print(msg)
                    sys.stdout.flush()
            listener.close()
        else: #no port connection, its silent
            WaitForSingleObject(rc, INFINITE);
        return False
    else:
        #redirect prints stdout on  master, errors in error.txt
        print("HIADM")
        sys.stdout.flush()
        if (tport > 0) and (redir):
            vox= connFile(tport=tport)
            sys.stdout=vox
            if not errFile:
                sys.stderr=vox
            else:
                vfrs=open("errFile.txt","w")
                sys.stderr=vfrs
            
            #print("HI ADMIN")
        return True


def test():
    rc = 0
    if not isUserAdmin():
        print ("You're not an admin.", os.getpid(), "params: ", sys.argv)
        sys.stdout.flush()
        #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())

【讨论】:

【参考方案12】:

这对我有用:


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

【讨论】:

以上是关于如何在 Windows 上以提升的权限运行脚本的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ctypes 运行具有提升的 UAC 权限的脚本?

如何提升WINDOWS XP管理权限

如何提升windows7的权限

如何提升windows7的权限

如何以管理员身份从 Windows 命令行运行命令?

各位好,那位能帮我解决一下权限问题。