如何在 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 上以提升的权限运行脚本的主要内容,如果未能解决你的问题,请参考以下文章