quest UAC elevation from within a Python script?
我希望我的Python腳本能夠在Vista上復制文件。 當我從普通的cmd.exe窗口運行它時,不會生成錯誤,但不會復制文件。 如果我運行cmd.exe"作為管理員"然后運行我的腳本,它工作正常。
這是有道理的,因為用戶帳戶控制(UAC)通常會阻止許多文件系統操作。
有沒有辦法可以在Python腳本中調用UAC提升請求(這些對話框說"像這樣的應用程序需要管理員訪問權限,這樣可以嗎?")
如果那是不可能的,那么我的腳本是否有一種方法可以至少檢測到它沒有被提升以便它可以優雅地失敗?
- stackoverflow.com/a/1445547/1628132在這個答案之后你使用py2exe從.py腳本創建一個.exe並使用一個名為'uac_info'的標志它是非常簡潔的解決方案
截至2017年,實現這一目標的簡單方法如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 |
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, __file__, None, 1) |
如果您使用的是Python 2.x,那么您應該替換最后一行:
1
|
ctypes.
windll.shell32.ShellExecuteW(None, u"runas", unicode(sys.executable), unicode(__file__), None, 1)
|
另請注意,如果您將python腳本轉換為可執行文件(使用py2exe,cx_freeze,pyinstaller等工具),則應將第四個參數替換為空字符串("")。
這里的一些優點是:
- 不需要外部庫(也不是用於Windows擴展的Python)。它僅使用標准庫中的ctypes。
- 適用於Python 2和Python 3。
- 無需修改文件資源也無需創建清單文件。
- 如果你沒有在if / else語句下面添加代碼,那么代碼將不會被執行兩次。
- 如果用戶拒絕UAC提示,您可以輕松地將其修改為具有特殊行為。
- 您可以指定修改第四個參數的參數。
- 您可以指定修改第六個參數的顯示方法。
底層ShellExecute調用的文檔在這里。
- 我不得不使用unicode實例作為ShellExecuteW的參數(如u'runas'和unicode(sys.executable))來運行它。
- @Janosch,那是因為你使用的是Python 2.x,而我的代碼是在Python 3中(其中所有字符串都被視為unicodes)。但是值得一提,謝謝!
- @Martin如果我從Windows命令行運行這樣的代碼:"python yourcode.py"它只是打開python.exe。有辦法解決嗎?
- @ user2978216我遇到了同樣的問題。在行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和ShellExecute有什么區別?
- 謝謝@JavierUbillos,我剛剛編輯了答案
- @HrvojeT兩者,ShellExecuteW和ShellExecuteA都是對Windows API中ShellExecute函數的調用。前者要求字符串采用unicode格式,后者使用ANSI格式
- 謝謝。你能告訴我為什么用pyinstaller而不是__file__制作可執行文件時需要空字符串""?
- 使用py2exe或cx_freeze創建可執行文件,變量__file__不存在。我不是在這里使用空變量,而是用sys.argv[0]替換__file__
- @HrvojeT運行python腳本(.py)時,sys.executable解析為PYTHONPATH(e.x C:\Python34\python.exe),因此您需要將文件名作為參數傳遞。運行轉換為可執行文件的腳本時,sys.executable會解析為可執行文件本身,因此不需要參數。此外,由於@deajan提及__file__變量甚至沒有在最后一種情況下定義,所以你不應該使用它。
- 謝謝。為什么不在腳本中使用sys.argv [0],而不是od文件?
- 我的回答是假設我們實際上並不關心參數。所以,在python腳本中使用sys.argv[0]或__file__都可以。如果您將其轉換為可執行文件,則可以使用"","anyrandomstring",sys.argv[0],它也可以正常工作(但不是__file__,因為它未定義)。現在,如果你關心參數,你應該使用"".join(sys.argv)作為python腳本,使用"".join(sys.argv[1:])作為轉換后的腳本。 @HrvojeT
- 請有人告訴我為什么需要try / except塊。
- 是否有可能解析退出狀態?
- @ solstice333是的,確實如此。您可以在文檔中查看,ShellExecuteW返回一個整數。如果它等於或大於32,那么一切都沒問題,否則你就出錯了。
- @MartnDelaFuente你看到了並試了一下。在我的ShellExecuteW'ed過程中,我退出1.當我在父進程中使用返回值時,當我期望小於或等於32時,它會讓我回到42.忘記在我之前的評論中提到它。
- 當正在運行的應用程序是使用python -m module而不是腳本運行的python模塊時,您將如何使這項工作?
我花了一點時間讓dguaraglia的答案正常工作,所以為了節省他人的時間,這就是我為實現這個想法而采取的措施:
1
2 3 4 5 6 7 8 9 10 |
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) |
- 這似乎提升然后退出...如果我輸入一些打印語句,他們不會再次執行
- 這是我見過的最酷的技巧之一。
- @JoranBeasley,你不會看到任何輸出。 ShellExecuteEx不會將其STDOUT發布回原始shell。在這方面,調試將是......具有挑戰性。但特權提升技巧肯定有效。
- 完美適合我的需求,謝謝!
- @TimKeating,ActiveState有一個配方,應該使調試更容易:使用DebugView實用程序與標准python日志記錄
- 似乎不可能在同一個控制台中獲取輸出,但是使用參數nShow = 5到ShellExecuteEx,將使用提升腳本的輸出打開一個新的命令窗口。
- 如果子將其輸出重定向到提供的PIPE,則可能是可能的。一些鏈接stackoverflow.com/questions/4093252/
- 一些建議的改進:使用bool(windll.advpack.IsNTAdmin(0, None))來檢測當前它是否為admin(使用來自ctypes import windll)。此外,您的params創建還應包括正確的引用,以便帶有空格的參數和腳本可以工作。引用每一個是一個好的開始('"'+'""'.join(...)+'"')但是為了更好,你還需要檢測參數中的嵌入式引號。
- 對於引用,您可以使用subprocess.list2cmdline來正確執行此操作。
- 謝謝,我只需要以管理員身份運行單個子流程,這樣就完美了:import win32com.shell.shell as shell shell.ShellExecuteEx(lpVerb='runas', lpFile='net', lpParameters='start service', nShow=5) assert res['hInstApp'] > 32 Microsoft win32-ShellExecuteEx Docs
似乎沒有辦法提升應用程序權限一段時間來執行特定任務。 Windows需要在程序開始時知道應用程序是否需要某些特權,並要求用戶確認應用程序何時執行需要這些特權的任務。有兩種方法可以做到這一點:
- 編寫一個清單文件,告訴Windows應用程序可能需要一些權限
- 從另一個程序內部使用提升的權限運行應用程序
這兩篇文章更詳細地解釋了它是如何工作的。
如果您不想為CreateElevatedProcess API編寫令人討厭的ctypes包裝器,我會使用代碼項目文章中解釋的ShellExecuteEx技巧(Pywin32帶有ShellExecute的包裝器)。怎么樣?像這樣的東西:
當你的程序啟動時,它會檢查它是否具有管理員權限,如果它沒有使用ShellExecute技巧自行運行並立即退出,如果是,則執行手頭的任務。
當您將程序描述為"腳本"時,我認為這足以滿足您的需求。
干杯。
- 感謝這些鏈接,它們對我找到很多關於UAC的東西非常有用。
- 您可能需要注意的一點是,您可以使用os.startfile($ EXECUTABLE,"runas")在沒有PyWin32的情況下執行ShellExecute(我在安裝它時遇到了問題)。
- @Mike - 但是runas會帶來一個新的提示。並且startfile不接受$EXECUTABLE.的命令行參數
- 我添加了另一個答案,該技術的完整實現應該可以添加到任何python腳本的開頭。
- 第二個鏈接的文章是"最低權限:使用Windows Vista用戶帳戶控制教你的應用程序","MSDN Magazine 2007年1月",但此問題現在僅作為.chm文件提供。
認識到這個問題是在幾年前被問到的,我認為frmdstryr使用他的模塊pyminutils在github上提供了一個更優雅的解決方案:
摘抄:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
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接口,並自動指示熟悉的對話框提示需要管理員權限,如果您要復制到需要管理員權限的目錄中,您將看到該提示,並在復制操作期間提供典型的文件進度對話框。
- 請在此處查看@frmdstryr的答案:stackoverflow.com/a/19989764/281545
以下示例以MARTIN DE LA FUENTE SAAVEDRA的出色工作和接受的答案為基礎。特別是,引入了兩個枚舉。第一個允許輕松指定如何打開提升的程序,第二個允許在需要輕松識別錯誤時提供幫助。請注意,如果您希望將所有命令行參數傳遞給新進程,則sys.argv[0]應該替換為函數調用:subprocess.list2cmdline(sys.argv)。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
#! /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() |
這可能無法完全回答您的問題,但您也可以嘗試使用Elevate Command Powertoy以使用提升的UAC權限運行腳本。
http://technet.microsoft.com/en-us/magazine/2008.06.elevation.aspx
我想如果你使用它看起來像'提升python yourscript.py'
這主要是對Jorenko的答案的升級,它允許在Windows中使用帶空格的參數,但在Linux上也應該運行得相當好:)
此外,將使用cx_freeze或py2exe,因為我們不使用__file__但sys.argv[0]作為可執行文件
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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:]) |
Jorenko上面工作的變體允許提升的進程使用相同的控制台(但請參閱下面的評論):
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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 |
- 抱歉。相同的控制台選項(SEE_MASK_NO_CONSOLE)僅在您已經提升時才有效。我的錯。
您可以在某處創建快捷方式並作為目標使用:
python yourscript.py
然后在屬性和高級選擇下以管理員身份運行。
當用戶執行快捷方式時,它將要求他們提升應用程序。
如果您的腳本始終需要管理員權限,那么:
1
|
runas /
user:Administrator"python your_script.py"
|
- 小心,提升!=以管理員身份運行
- 我是python的新手......你能告訴我在哪里放這個代碼嗎?
- @RahatIslamKhan:打開命令提示符窗口並將其放在以下位置:該命令以管理員用戶身份運行your_script.py。確保你理解@ Kugel的評論。