Żądać podniesienia uprawnień UAC z poziomu skryptu Python?


96

Chcę, aby mój skrypt w Pythonie kopiował pliki w systemie Vista. Kiedy uruchamiam go z normalnego cmd.exeokna, żadne błędy nie są generowane, ale pliki NIE są kopiowane. Jeśli uruchomię cmd.exe„jako administrator”, a następnie uruchomię skrypt, wszystko działa dobrze.

Ma to sens, ponieważ Kontrola konta użytkownika (UAC) zwykle zapobiega wielu działaniom w systemie plików.

Czy istnieje sposób, w jaki mogę, z poziomu skryptu Python, wywołać żądanie podniesienia uprawnień UAC (te okna dialogowe, które mówią coś w rodzaju „taka a taka aplikacja wymaga dostępu administratora, czy to jest w porządku?”)

Jeśli nie jest to możliwe, czy istnieje sposób, w jaki mój skrypt może przynajmniej wykryć, że nie jest podniesiony, aby mógł z wdziękiem zawieść?


3
stackoverflow.com/a/1445547/1628132 po tej odpowiedzi utworzysz plik .exe ze skryptu .py używając py2exe i używając flagi o nazwie „uac_info” to całkiem zgrabne rozwiązanie
foxcoreg

Odpowiedzi:


102

Od 2017 r. Prostą metodą osiągnięcia tego jest:

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)

Jeśli używasz Pythona 2.x, powinieneś zamienić ostatnią linię na:

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

Należy również pamiętać, że jeśli ci skrypt Pythona konwertowane do pliku wykonywalnego (za pomocą narzędzi takich jak py2exe, cx_freeze, pyinstaller), należy użyć sys.argv[1:]zamiast sys.argvw czwartym parametrem.

Oto niektóre z zalet:

  • Nie są wymagane żadne biblioteki zewnętrzne. Używa tylko ctypesi sysze standardowej biblioteki.
  • Działa na Pythonie 2 i Pythonie 3.
  • Nie ma potrzeby modyfikowania zasobów plików ani tworzenia pliku manifestu.
  • Jeśli nie dodasz kodu poniżej instrukcji if / else, kod nigdy nie zostanie wykonany dwukrotnie.
  • Możesz pobrać wartość zwracaną przez wywołanie API w ostatnim wierszu i podjąć akcję, jeśli się nie powiedzie (kod <= 32). Sprawdzić możliwych wartości zwracanych tutaj .
  • Możesz zmienić metodę wyświetlania utworzonego procesu, modyfikując szósty parametr.

Dokumentacja dotycząca podstawowego wywołania ShellExecute jest tutaj .


9
Musiałem użyć instancji Unicode jako parametrów dla ShellExecuteW (takich jak u'runas 'i unicode (sys.executable)), aby to uruchomić.
Janosch

6
@Janosch, to dlatego, że używasz Pythona 2.x, podczas gdy mój kod jest w Pythonie 3 (gdzie wszystkie ciągi znaków są traktowane jako unicodes). Ale warto wspomnieć, dzięki!
Martín De la Fuente

2
@Martin, jeśli uruchamiam ten kod z wiersza poleceń systemu Windows w następujący sposób: „python yourcode.py”, po prostu otwiera się python.exe. Czy jest jakiś sposób to naprawić?
user2978216

1
@ user2978216 Miałem ten sam problem. W wierszu ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, "", None, 1) sys.executablerozwiązuje się tylko interpreter Pythona (np. C:\Python27\Python.exe) Rozwiązaniem jest dodanie uruchomionego skryptu jako argumentu (replacting ""). ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)Należy również pamiętać, aby to działało w Pythonie 2.x, wszystkie argumenty strun muszą być Unicode (tj u"runas", unicode(sys.executable)i unicode(__file__))
Javier Ubillos

2
@HrvojeT Oba ShellExecuteWi ShellExecuteAsą wywołaniami ShellExecutefunkcji w interfejsie API systemu Windows. Pierwsza z nich zobowiązuje ciągi do formatu unicode, a druga jest używana z formatem ANSI
Martín De la Fuente

71

Zajęło mi trochę czasu, zanim odpowiedź dguaraglii zadziałała, więc aby zaoszczędzić czas innym, oto co zrobiłem, aby wdrożyć ten pomysł:

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
to po prostu wydaje się być podniesione, a następnie zakończone ... jeśli wstawię jakieś instrukcje drukowania, nie zostaną wykonane po raz drugi
Joran Beasley

6
@JoranBeasley, nie zobaczysz żadnych wyników. ShellExecuteEx nie wysyła swojego STDOUT z powrotem do pierwotnej powłoki. Pod tym względem debugowanie będzie ... trudne. Ale sztuczka polegająca na podnoszeniu przywilejów zdecydowanie działa.
Tim Keating,

1
@TimKeating, ActiveState ma przepis, który powinien nieco ułatwić debugowanie: użyj narzędzia DebugView ze standardowym rejestrowaniem w języku Python
samwyse

1
wydaje się niemożliwe uzyskanie danych wyjściowych w tej samej konsoli, ale z argumentem nShow = 5 do ShellExecuteEx, otworzy się nowe okno poleceń z danymi wyjściowymi ze skryptu z podwyższonym poziomem uprawnień.
Emil Styrke

2
Do wyceny możesz użyć, subprocess.list2cmdlineaby zrobić to poprawnie.
coderforlife

29

Wygląda na to, że nie ma sposobu, aby na chwilę podnieść uprawnienia aplikacji, aby wykonać określone zadanie. System Windows musi wiedzieć na początku programu, czy aplikacja wymaga określonych uprawnień, i poprosi użytkownika o potwierdzenie, kiedy aplikacja wykonuje zadania wymagające tych uprawnień. Można to zrobić na dwa sposoby:

  1. Napisz plik manifestu, który powie systemowi Windows, że aplikacja może wymagać pewnych uprawnień
  2. Uruchom aplikację z podwyższonymi uprawnieniami z poziomu innego programu

Te dwa artykuły wyjaśniają bardziej szczegółowo, jak to działa.

Jeśli nie chcesz pisać paskudnego opakowania ctypes dla interfejsu API CreateElevatedProcess, użyj sztuczki ShellExecuteEx wyjaśnionej w artykule Code Project (Pywin32 jest dostarczany z opakowaniem dla ShellExecute). W jaki sposób? Coś takiego:

Kiedy program się uruchamia, sprawdza, czy ma uprawnienia administratora, jeśli nie, uruchamia się sam za pomocą sztuczki ShellExecute i natychmiast kończy pracę, jeśli tak, wykonuje zadanie.

Ponieważ opisujesz swój program jako „skrypt”, przypuszczam, że to wystarczy na Twoje potrzeby.

Twoje zdrowie.


Dzięki za te linki, były one bardzo przydatne dla mnie, aby dowiedzieć się wiele o sprawach związanych z UAC.
Colen

4
Coś, na co warto zwrócić uwagę, to fakt, że możesz wykonać ShellExecute bez PyWin32 (miałem problemy z zainstalowaniem go), używając os.startfile ($ EXECUTABLE, "runas").
Mike McQuaid

@Mike - ale runaswyświetla nowy monit. A plik startowy nie akceptuje argumentów wiersza poleceń do$EXECUTABLE.
Sridhar Ratnakumar

Dodałem kolejną odpowiedź z pełną implementacją tej techniki, którą można dodać na początku dowolnego skryptu w Pythonie.
Jorenko

Artykuł prowadzący do drugiego łącza brzmiał „Najmniej przywilejów: naucz swoje aplikacje dobrze grać z kontrolą konta użytkownika systemu Windows Vista” w „MSDN Magazine styczeń 2007”, ale ten numer jest teraz dostępny tylko w postaci .chmpliku.
Peter

6

Po prostu dodaję tę odpowiedź na wypadek, gdyby inni zostali skierowani tutaj przez wyszukiwarkę Google, tak jak ja. Użyłem elevatemodułu w moim skrypcie Python i skrypcie wykonanym z uprawnieniami administratora w systemie Windows 10.

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


Hej, próbowałem użyć elevatemodułu i otrzymuję błąd „System nie może uzyskać dostępu do pliku”, czy masz jakieś pomysły, dlaczego tak się stało?
paxos1977

@ paxos1977 Czy możesz opublikować fragment kodu, który demonstruje ten błąd? Dzięki!
Irving Moy

5

Poniższy przykład opiera się na doskonałej pracy MARTIN DE LA FUENTE SAAVEDRA i zaakceptowanej odpowiedzi. W szczególności wprowadzono dwa wyliczenia. Pierwsza pozwala na łatwe określenie sposobu otwierania programu z podwyższonym poziomem uprawnień, a druga pomaga, gdy trzeba łatwo zidentyfikować błędy. Pamiętaj, że jeśli chcesz, aby wszystkie argumenty wiersza polecenia przekazywane do nowego procesu, sys.argv[0]prawdopodobnie powinien zostać zastąpiony przez wywołanie funkcji: 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

Uznając to pytanie został poproszony lat temu, myślę, że bardziej eleganckie rozwiązanie jest oferowane na github przez frmdstryr wykorzystując swoje pywinutils module:

Fragment:

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    

Wykorzystuje to interfejs COM i automatycznie wskazuje, że potrzebne są uprawnienia administratora, w znanym oknie dialogowym, które można zobaczyć, gdyby kopiowano do katalogu, w którym wymagane są uprawnienia administratora, a także wyświetla typowe okno dialogowe postępu pliku podczas operacji kopiowania.



2

Możesz gdzieś zrobić skrót i jako cel użyć: python yourscript.py, a następnie we właściwościach i wybierz zaawansowane, uruchom jako administrator.

Gdy użytkownik wykona skrót, poprosi go o podniesienie poziomu aplikacji.


1

Jeśli twój skrypt zawsze wymaga uprawnień administratora, to:

runas /user:Administrator "python your_script.py"

15
uwaga, elewacja! = działa jako administrator
Kugel

Jestem nowy w Pythonie ... czy możesz mi powiedzieć, gdzie mam umieścić ten kod?
Rahat Islam Khan

@RahatIslamKhan: Otwórz okno wiersza polecenia i umieść je tam, gdzie: polecenie działa your_script.pyjako użytkownik administratora. Upewnij się, że rozumiesz komentarz @ Kugel .
jfs

1

Odmiana powyższej pracy Jorenko umożliwia podwyższonemu procesowi korzystanie z tej samej konsoli (ale zobacz mój komentarz poniżej):

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

Przepraszam. ta sama opcja konsoli (SEE_MASK_NO_CONSOLE) działa tylko wtedy, gdy masz już wyższy poziom uprawnień. Mój błąd.
Berwyn

1

Jest to głównie aktualizacja odpowiedzi Jorenko, która pozwala na użycie parametrów ze spacjami w Windows, ale powinna również działać całkiem dobrze na Linuksie :) Również będzie działać z cx_freeze lub py2exe, ponieważ nie używamy, __file__ale sys.argv[0]jako wykonywalne

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:])
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.