Richiedi l'elevazione del controllo dell'account utente da uno script Python?


91

Voglio che il mio script Python copi i file su Vista. Quando lo eseguo da un normalecmd.exe finestra , non vengono generati errori, ma i file NON vengono copiati. Se eseguo cmd.exe"come amministratore" e poi eseguo il mio script, funziona bene.

Ciò ha senso poiché il controllo dell'account utente (UAC) normalmente impedisce molte azioni del file system.

C'è un modo in cui posso, dall'interno di uno script Python, invocare una richiesta di elevazione dell'UAC (quelle finestre di dialogo che dicono qualcosa come "questa o quella app richiede l'accesso come amministratore, va bene?")

Se ciò non è possibile, c'è un modo in cui il mio script può almeno rilevare che non è elevato in modo che possa fallire con grazia?


3
stackoverflow.com/a/1445547/1628132 seguendo questa risposta crei un .exe dallo script .py usando py2exe e usando un flag chiamato 'uac_info' è una soluzione piuttosto ordinata
foxcoreg

Risposte:


96

A partire dal 2017, un metodo semplice per ottenere questo risultato è il seguente:

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)

Se stai usando Python 2.x, dovresti sostituire l'ultima riga per:

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

Si noti inoltre che se è stato convertito script Python in un file eseguibile (utilizzando strumenti come py2exe, cx_freeze, pyinstaller), allora si dovrebbe usare sys.argv[1:]al posto disys.argv nel quarto parametro.

Alcuni dei vantaggi qui sono:

  • Non sono richieste librerie esterne. Utilizza solo ctypese sysdalla libreria standard.
  • Funziona sia su Python 2 che su Python 3.
  • Non è necessario modificare le risorse del file né creare un file manifest.
  • Se non aggiungi il codice sotto l'istruzione if / else, il codice non verrà mai eseguito due volte.
  • È possibile ottenere il valore di ritorno della chiamata API nell'ultima riga ed eseguire un'azione se non riesce (codice <= 32). Controlla i possibili valori di ritorno qui .
  • È possibile modificare il metodo di visualizzazione del processo generato modificando il sesto parametro.

La documentazione per la chiamata ShellExecute sottostante è disponibile qui .


9
Ho dovuto usare istanze Unicode come parametri per ShellExecuteW (come u'runas 'e unicode (sys.executable)) per farlo funzionare.
Janosch

6
@Janosch, è perché stai usando Python 2.x, mentre il mio codice è in Python 3 (dove tutte le stringhe sono trattate come unicode). Ma è bene menzionare, grazie!
Martín De la Fuente

2
@ Martin se eseguo questo codice dalla riga di comando di Windows in questo modo: "python yourcode.py" si apre solo python.exe. C'è un modo per risolverlo?
user2978216

1
@ user2978216 Ho avuto lo stesso problema. Nella riga si ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, "", None, 1) sys.executablerisolve solo nell'interprete python (ad esempio C:\Python27\Python.exe) La soluzione è aggiungere lo script in esecuzione come argomento (sostituzione ""). ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)Si noti inoltre, per questo al lavoro in python 2.x, tutti gli argomenti di stringa devono essere unicode (vale a dire u"runas", unicode(sys.executable)e unicode(__file__))
Javier Ubillos

2
@HrvojeT Entrambi ShellExecuteWe ShellExecuteAsono chiamate alla ShellExecutefunzione nell'API di Windows. Il primo obbliga le stringhe ad essere in formato Unicode e il secondo è utilizzato con il formato ANSI
Martín De la Fuente

69

Mi ci è voluto un po 'per far funzionare la risposta di dguaraglia, quindi nell'interesse di far risparmiare tempo agli altri, ecco cosa ho fatto per implementare questa idea:

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
questo sembra elevarsi e poi uscire ... se inserisco alcune dichiarazioni stampate non vengono eseguite una seconda volta
Joran Beasley

6
@JoranBeasley, non vedrai alcun output. ShellExecuteEx non invia il suo STDOUT alla shell di origine. A questo proposito, il debugging sarà ... impegnativo. Ma il trucco dell'elevazione dei privilegi funziona sicuramente.
Tim Keating

1
@TimKeating, ActiveState ha una ricetta che dovrebbe rendere il debug un po 'più semplice: usa l'utilità DebugView con la registrazione standard di Python
samwyse

1
sembra impossibile ottenere l'output nella stessa console, ma con l'argomento nShow = 5 di ShellExecuteEx, si aprirà una nuova finestra di comando con l'output dello script elevato.
Emil Styrke

2
Per la citazione, puoi usare subprocess.list2cmdlineper farlo correttamente.
coderforlife

29

Sembra che non ci sia modo di elevare i privilegi dell'applicazione per un po 'per eseguire un'attività particolare. Windows deve sapere all'inizio del programma se l'applicazione richiede determinati privilegi e chiederà all'utente di confermare quando l'applicazione esegue le attività che richiedono tali privilegi. Ci sono due modi per farlo:

  1. Scrivi un file manifest che comunichi a Windows che l'applicazione potrebbe richiedere alcuni privilegi
  2. Eseguire l'applicazione con privilegi elevati dall'interno di un altro programma

Questi due articoli spiegano in modo molto più dettagliato come funziona.

Quello che farei, se non vuoi scrivere un cattivo wrapper ctypes per l'API CreateElevatedProcess, è usare il trucco ShellExecuteEx spiegato nell'articolo Code Project (Pywin32 viene fornito con un wrapper per ShellExecute). Come? Qualcosa come questo:

Quando il tuo programma si avvia, controlla se ha i privilegi di amministratore, in caso contrario si esegue da solo usando il trucco di ShellExecute ed esce immediatamente, se lo fa, esegue l'attività in questione.

Poiché descrivi il tuo programma come uno "script", suppongo che sia sufficiente per le tue esigenze.

Saluti.


Grazie per quei collegamenti, sono stati molto utili per me che ho scoperto molte cose sull'UAC.
Colen

4
Qualcosa che potresti voler notare su questo è che puoi fare ShellExecute senza PyWin32 (ho avuto problemi a installarlo) usando os.startfile ($ EXECUTABLE, "runas").
Mike McQuaid

@ Mike - ma runasfa apparire un nuovo prompt. E startfile non accetta argomenti della riga di comando per$EXECUTABLE.
Sridhar Ratnakumar

Ho aggiunto un'altra risposta con un'implementazione completa di questa tecnica che dovrebbe essere in grado di essere aggiunta all'inizio di qualsiasi script Python.
Jorenko

L'articolo al secondo collegamento era "Privilegio minimo: insegna alle tue app a giocare bene con il controllo dell'account utente di Windows Vista" in "MSDN Magazine gennaio 2007", ma questo numero è ora disponibile solo come .chmfile.
Peter

6

Aggiungo solo questa risposta nel caso in cui altri vengano indirizzati qui dalla Ricerca Google come me. Ho usato il elevatemodulo nel mio script Python e lo script eseguito con privilegi di amministratore in Windows 10.

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


Ehi, ho provato a usare il elevatemodulo e ricevo l'errore "Impossibile accedere al file dal sistema", qualche idea sul perché sarebbe successo?
paxos1977

@ paxos1977 Puoi pubblicare uno snippet di codice che dimostri quell'errore? Grazie!
Irving Moy

4

Il seguente esempio si basa sull'eccellente lavoro e sulla risposta accettata di MARTIN DE LA FUENTE SAAVEDRA . In particolare, vengono introdotte due enumerazioni. Il primo consente di specificare facilmente come deve essere aperto un programma con privilegi elevati e il secondo aiuta quando è necessario identificare facilmente gli errori. Si prega di notare che se si desidera che tutti gli argomenti della riga di comando passati al nuovo processo, sys.argv[0]dovrebbe probabilmente essere sostituita con una chiamata di funzione: 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

Riconoscendo questa domanda è stata posta anni fa, penso che una soluzione più elegante sia offerta su GitHub da frmdstryr usando il suo modulo pywinutils:

Estratto:

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    

Questo utilizza l'interfaccia COM e indica automaticamente che i privilegi di amministratore sono necessari con la familiare finestra di dialogo che vedresti se stavi copiando in una directory in cui sono richiesti i privilegi di amministratore e fornisce anche la tipica finestra di dialogo di avanzamento del file durante l'operazione di copia.



2

Puoi creare una scorciatoia da qualche parte e come destinazione usa: python yourscript.py quindi in proprietà e seleziona avanzata esegui come amministratore.

Quando l'utente esegue il collegamento gli chiederà di elevare l'applicazione.


1

Se il tuo script richiede sempre i privilegi di amministratore, allora:

runas /user:Administrator "python your_script.py"

15
attento, elevazione! = in esecuzione come amministratore
Kugel

Sono nuovo in Python ... puoi dirmi dove metterò quel codice?
Rahat Islam Khan

@RahatIslamKhan: apri una finestra del prompt dei comandi e mettila dove: il comando viene eseguito your_script.pycome utente amministratore. Assicurati di comprendere il commento di @ Kugel .
jfs

1

Una variazione sul lavoro di Jorenko sopra consente al processo elevato di utilizzare la stessa console (ma vedi il mio commento sotto):

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

Scusate. la stessa opzione della console (SEE_MASK_NO_CONSOLE) funziona solo se sei già elevato. Colpa mia.
Berwyn

1

Questo è principalmente un aggiornamento alla risposta di Jorenko, che consente di utilizzare parametri con spazi in Windows, ma dovrebbe funzionare abbastanza bene anche su Linux :) Inoltre, funzionerà con cx_freeze o py2exe poiché non li usiamo __file__ma sys.argv[0]come eseguibili

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:])
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.