Yêu cầu độ cao UAC từ bên trong tập lệnh Python?


91

Tôi muốn tập lệnh Python của mình sao chép các tệp trên Vista. Khi tôi chạy nó từ một cmd.execửa sổ bình thường , không có lỗi nào được tạo ra, nhưng các tệp KHÔNG được sao chép. Nếu tôi chạy cmd.exe"với tư cách quản trị viên" và sau đó chạy tập lệnh của mình, nó hoạt động tốt.

Điều này có ý nghĩa vì Kiểm soát tài khoản người dùng (UAC) thường ngăn nhiều hành động của hệ thống tệp.

Có cách nào tôi có thể, từ trong tập lệnh Python, gọi một yêu cầu nâng cấp UAC (những hộp thoại có nội dung như "ứng dụng như vậy và ứng dụng đó cần quyền truy cập của quản trị viên, điều này có được không?")

Nếu điều đó là không thể, có cách nào ít nhất là tập lệnh của tôi có thể phát hiện ra rằng nó không được nâng cao để nó có thể bị lỗi một cách duyên dáng không?


3
stackoverflow.com/a/1445547/1628132 sau câu trả lời này bạn tạo một .exe từ kịch bản py sử dụng py2exe và sử dụng một lá cờ gọi là 'uac_info' đó là giải pháp khá gọn gàng
foxcoreg

Câu trả lời:


96

Kể từ năm 2017, một phương pháp dễ dàng để đạt được điều này là:

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)

Nếu bạn đang sử dụng Python 2.x, thì bạn nên thay thế dòng cuối cùng cho:

ctypes.windll.shell32.ShellExecuteW(None, u"runas", unicode(sys.executable), unicode(" ".join(sys.argv)), None, 1)

Cũng lưu ý rằng nếu bạn chuyển đổi bạn script python vào một tập tin thực thi (sử dụng các công cụ như py2exe, cx_freeze, pyinstaller) sau đó bạn nên sử dụng sys.argv[1:]thay vì sys.argvtrong tham số thứ tư.

Một số lợi thế ở đây là:

  • Không cần thư viện bên ngoài. Nó chỉ sử dụng ctypessystừ thư viện tiêu chuẩn.
  • Hoạt động trên cả Python 2 và Python 3.
  • Không cần phải sửa đổi tài nguyên tệp cũng như tạo tệp kê khai.
  • Nếu bạn không thêm mã bên dưới câu lệnh if / else, mã sẽ không bao giờ được thực thi hai lần.
  • Bạn có thể lấy giá trị trả về của lệnh gọi API ở dòng cuối cùng và thực hiện hành động nếu nó không thành công (mã <= 32). Kiểm tra các giá trị trả về có thể có ở đây .
  • Bạn có thể thay đổi phương thức hiển thị của quá trình sinh sản bằng cách sửa đổi tham số thứ sáu.

Tài liệu về lệnh gọi ShellExecute cơ bản có ở đây .


9
Tôi đã phải sử dụng các phiên bản unicode làm tham số cho ShellExecuteW (như u'runas 'và unicode (sys.executable)) để chạy nó.
Janosch

6
@Janosch, đó là vì bạn đang sử dụng Python 2.x, trong khi mã của tôi là Python 3 (nơi tất cả các chuỗi được coi là mã đơn nguyên). Nhưng nó là tốt để đề cập, cảm ơn!
Martín De la Fuente

2
@Martin nếu tôi đang chạy mã này từ dòng lệnh Windows như sau: "python yourcode.py" nó chỉ mở python.exe. Có cách nào khắc phục nó không?
user2978216

1
@ user2978216 Tôi gặp vấn đề tương tự. Trong dòng ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, "", None, 1) sys.executablegiải quyết chỉ trình thông dịch python (ví dụ C:\Python27\Python.exe:) Giải pháp là thêm tập lệnh đang chạy làm đối số (thay thế ""). ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)Cũng lưu ý, đối với này để làm việc trong python 2.x, tất cả các đối số chuỗi cần phải unicode (ví dụ u"runas", unicode(sys.executable)unicode(__file__))
Javier Ubillos

2
@HrvojeT Cả ShellExecuteWShellExecuteAđều là lệnh gọi ShellExecutehàm trong Windows API. Các khoản nào bắt buộc cựu các dây phải ở định dạng unicode và sau này được sử dụng với định dạng ANSI
Martín De la Fuente

69

Tôi mất một chút thời gian để câu trả lời của dguaraglia có hiệu quả, vì vậy, vì lợi ích của việc tiết kiệm thời gian cho người khác, đây là những gì tôi đã làm để thực hiện ý tưởng này:

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)

1
này chỉ có vẻ để nâng cao và sau đó thoát ra ... nếu tôi đặt trong một số báo cáo in họ không nhận được thực hiện một lần thứ hai
Joran Beasley

6
@JoranBeasley, bạn sẽ không thấy bất kỳ đầu ra nào. ShellExecuteEx không đăng STDOUT của nó trở lại shell gốc. Về mặt đó, việc gỡ lỗi sẽ rất ... thách thức. Nhưng thủ thuật nâng cao đặc quyền chắc chắn hoạt động.
Tim Keating

1
@TimKeating, ActiveState có một công thức mà nên gỡ lỗi một chút dễ dàng hơn: Sử dụng DebugView tiện ích với tiêu chuẩn python logging
samwyse

1
Có vẻ như không thể lấy đầu ra trong cùng một bảng điều khiển, nhưng với đối số nShow = 5 cho ShellExecuteEx, một cửa sổ lệnh mới sẽ mở ra với đầu ra từ tập lệnh nâng cao.
Emil Styrke

2
Đối với phần trích dẫn, bạn có thể sử dụng subprocess.list2cmdlineđể thực hiện đúng cách.
coderforlife

29

Có vẻ như không có cách nào để nâng cao các đặc quyền của ứng dụng trong một thời gian để bạn thực hiện một tác vụ cụ thể. Khi khởi động chương trình, Windows cần biết ứng dụng có yêu cầu một số đặc quyền hay không và sẽ yêu cầu người dùng xác nhận khi ứng dụng thực hiện bất kỳ tác vụ nào cần các đặc quyền đó. Có hai cách để làm điều này:

  1. Viết tệp kê khai cho Windows biết rằng ứng dụng có thể yêu cầu một số đặc quyền
  2. Chạy ứng dụng với các đặc quyền nâng cao từ bên trong một chương trình khác

Đây hai bài báo giải thích trong nhiều chi tiết hơn cách thức hoạt động này.

Điều tôi nên làm, nếu bạn không muốn viết một trình bao bọc ctypes khó chịu cho API CreateEleisedProcess, là sử dụng thủ thuật ShellExecuteEx được giải thích trong bài viết Code Project (Pywin32 đi kèm với trình bao bọc cho ShellExecute). Làm sao? Một cái gì đó như thế này:

Khi chương trình của bạn khởi động, nó sẽ kiểm tra xem nó có đặc quyền của Administrator hay không, nếu không, nó sẽ tự chạy bằng thủ thuật ShellExecute và thoát ngay lập tức, nếu có, nó sẽ thực hiện nhiệm vụ trong tầm tay.

Khi bạn mô tả chương trình của mình như một "kịch bản", tôi cho rằng như vậy là đủ cho nhu cầu của bạn.

Chúc mừng.


Cảm ơn những liên kết đó, chúng rất hữu ích cho tôi khi tìm hiểu rất nhiều về nội dung UAC.
Colen 10/09/09

4
Một điều bạn có thể muốn lưu ý về điều này là bạn có thể thực hiện ShellExecute mà không cần PyWin32 (tôi đã gặp sự cố khi cài đặt nó) bằng cách sử dụng os.startfile ($ EXECUTABLE, "runas").
Mike McQuaid

@Mike - nhưng runasđưa ra lời nhắc mới. Và startfile không chấp nhận đối số dòng lệnh để$EXECUTABLE.
Sridhar Ratnakumar

Tôi đã thêm một câu trả lời khác với việc triển khai đầy đủ kỹ thuật này có thể được thêm vào phần đầu của bất kỳ tập lệnh python nào.
Jorenko,

Bài viết đến liên kết thứ hai là "Đặc quyền ít nhất: Dạy ứng dụng của bạn chơi độc đáo với quyền kiểm soát tài khoản người dùng Windows Vista" trong "Tạp chí MSDN tháng 1 năm 2007", nhưng vấn đề này hiện chỉ có sẵn dưới dạng .chmtệp.
Peter

6

Chỉ thêm câu trả lời này trong trường hợp những người khác được Google Tìm kiếm hướng đến đây như tôi. Tôi đã sử dụng elevatemô-đun trong tập lệnh Python của mình và tập lệnh được thực thi với Đặc quyền của quản trị viên trong Windows 10.

https://pypi.org/project/elevate/


Này, tôi đã thử sử dụng elevatemô-đun và nhận được lỗi "Hệ thống không thể truy cập tệp", bất kỳ ý kiến ​​nào tại sao điều đó sẽ xảy ra?
paxos1977

@ paxos1977 Bạn có thể đăng đoạn mã giải thích lỗi đó không? Cảm ơn!
Irving Moy

4

Ví dụ sau được xây dựng dựa trên thành quả xuất sắc của MARTIN DE LA FUENTE SAAVEDRA và câu trả lời được chấp nhận. Đặc biệt, hai bảng liệt kê được giới thiệu. Cách thứ nhất cho phép đặc tả dễ dàng cách mở một chương trình nâng cao, và cách thứ hai giúp khi cần dễ dàng xác định lỗi. Xin lưu ý rằng nếu bạn muốn tất cả các đối số dòng lệnh truyền cho các quy trình mới, sys.argv[0]có lẽ nên được thay thế bằng một cuộc gọi chức năng: 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()

4

Nhận ra câu hỏi này đã được hỏi nhiều năm trước, tôi nghĩ rằng một giải pháp thanh lịch hơn được cung cấp trên github bởi frmdstryr bằng cách sử dụng mô-đun pywinutils của anh ấy:

Trích:

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    

Điều này sử dụng giao diện COM và tự động chỉ ra rằng cần có đặc quyền quản trị viên bằng lời nhắc hộp thoại quen thuộc mà bạn sẽ thấy nếu bạn đang sao chép vào thư mục cần có đặc quyền quản trị viên và cũng cung cấp hộp thoại tiến trình tệp điển hình trong quá trình sao chép.



2

Bạn có thể tạo một lối tắt ở đâu đó và như mục tiêu sử dụng: python yourcript.py sau đó trong thuộc tính và chọn nâng cao chạy với tư cách quản trị viên.

Khi người dùng thực hiện phím tắt, nó sẽ yêu cầu họ nâng cấp ứng dụng.


1

Nếu tập lệnh của bạn luôn yêu cầu đặc quyền của Quản trị viên thì:

runas /user:Administrator "python your_script.py"

15
cẩn thận, elevation! = đang chạy với tư cách quản trị viên
Kugel

Tôi mới sử dụng python ... bạn có thể cho tôi biết tôi sẽ đặt mã đó ở đâu không?
Rahat Islam Khan,

@RahatIslamKhan: Mở cửa sổ Command Prompt và đặt nó ở vị trí: lệnh chạy your_script.pyvới tư cách người dùng Quản trị viên. Đảm bảo rằng bạn hiểu nhận xét của @ Kugel .
jfs

1

Một biến thể về công việc của Jorenko ở trên cho phép quá trình nâng cao sử dụng cùng một bảng điều khiển (nhưng hãy xem nhận xét của tôi bên dưới):

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

Lấy làm tiếc. cùng một tùy chọn bảng điều khiển (SEE_MASK_NO_CONSOLE) chỉ hoạt động nếu bạn đã được nâng cao. Lỗi của tôi.
Berwyn

1

Đây chủ yếu là bản nâng cấp cho câu trả lời của Jorenko, cho phép sử dụng các tham số có dấu cách trong Windows, nhưng cũng sẽ hoạt động khá tốt trên Linux :) Ngoài ra, sẽ hoạt động với cx_freeze hoặc py2exe vì chúng ta không sử dụng __file__nhưng sys.argv[0]dưới dạng tệp thực thi

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:])
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.