Demander une élévation UAC à partir d'un script Python?

91

Je veux que mon script Python copie les fichiers sous Vista. Lorsque je l'exécute à partir d'une cmd.exefenêtre normale , aucune erreur n'est générée, mais les fichiers ne sont PAS copiés. Si je lance cmd.exe"en tant qu'administrateur" puis que j'exécute mon script, cela fonctionne très bien.

Cela a du sens car le contrôle de compte d'utilisateur (UAC) empêche normalement de nombreuses actions du système de fichiers.

Existe-t-il un moyen, à partir d'un script Python, d'appeler une demande d'élévation UAC (ces boîtes de dialogue qui disent quelque chose comme "telle ou telle application a besoin d'un accès administrateur, est-ce correct?")

Si ce n'est pas possible, y a-t-il un moyen pour mon script de détecter au moins qu'il n'est pas élevé pour qu'il puisse échouer correctement?

jwfearn
la source
3
stackoverflow.com/a/1445547/1628132 suite à cette réponse, vous créez un .exe à partir du script .py en utilisant py2exe et en utilisant un indicateur appelé 'uac_info', c'est une solution
plutôt intéressante

Réponses:

96

Depuis 2017, une méthode simple pour y parvenir est la suivante:

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)

Si vous utilisez Python 2.x, vous devez remplacer la dernière ligne pour:

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

Notez également que si vous vous avez converti script python dans un fichier exécutable ( en utilisant des outils tels que py2exe, cx_freeze, pyinstaller) alors vous devriez utiliser au sys.argv[1:]lieu de sys.argvla quatrième paramètre.

Certains des avantages ici sont:

  • Aucune bibliothèque externe requise. Il utilise uniquement ctypeset sysde la bibliothèque standard.
  • Fonctionne à la fois sur Python 2 et Python 3.
  • Il n'est pas nécessaire de modifier les ressources du fichier ni de créer un fichier manifeste.
  • Si vous n'ajoutez pas de code ci-dessous l'instruction if / else, le code ne sera jamais exécuté deux fois.
  • Vous pouvez obtenir la valeur de retour de l'appel API dans la dernière ligne et effectuer une action en cas d'échec (code <= 32). Vérifiez les valeurs de retour possibles ici .
  • Vous pouvez changer la méthode d'affichage du processus généré en modifiant le sixième paramètre.

La documentation de l'appel ShellExecute sous-jacent est disponible ici .

Martín De la Fuente
la source
9
J'ai dû utiliser des instances Unicode comme paramètres pour ShellExecuteW (comme u'runas 'et unicode (sys.executable)) pour que cela fonctionne.
Janosch
6
@Janosch, c'est parce que vous utilisez Python 2.x, alors que mon code est en Python 3 (où toutes les chaînes sont traitées comme des unicodes). Mais il est bon de le mentionner, merci!
Martín De la Fuente
2
@Martin si j'exécute ce code à partir de la ligne de commande Windows comme ceci: "python yourcode.py" il ouvre juste python.exe. Y a-t-il un moyen de résoudre ce problème?
user2978216
1
@ user2978216 J'ai eu le même problème. Dans la ligne ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, "", None, 1) sys.executablerésout uniquement l'interpréteur python (par exemple C:\Python27\Python.exe) La solution est d'ajouter le script en cours d'exécution en tant qu'argument (replacting ""). ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)Notez également que pour que cela fonctionne en python 2.x, tous les arguments de chaîne doivent être unicode (c'est u"runas"-à- dire unicode(sys.executable)et unicode(__file__))
Javier Ubillos
2
@HrvojeT Les deux, ShellExecuteWet ShellExecuteAsont des appels à la ShellExecutefonction dans l'API Windows. Le premier oblige les chaînes à être au format unicode et le second est utilisé avec le format ANSI
Martín De la Fuente
69

Il m'a fallu un peu de temps pour que la réponse de dguaraglia fonctionne, donc dans l'intérêt de gagner du temps aux autres, voici ce que j'ai fait pour mettre en œuvre cette idée:

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)
Jorenko
la source
1
cela semble juste s'élever puis quitter ... si je mets des instructions d'impression, elles ne sont pas exécutées une deuxième fois
Joran Beasley
6
@JoranBeasley, vous ne verrez aucune sortie. ShellExecuteEx ne publie pas son STDOUT dans le shell d'origine. À cet égard, le débogage sera ... difficile. Mais le truc de la levée de privilèges fonctionne définitivement.
Tim Keating
1
@TimKeating, ActiveState a une recette qui devrait rendre le débogage un peu plus facile: utilisez l'utilitaire DebugView avec la journalisation Python standard
samwyse
1
il semble impossible d'obtenir la sortie dans la même console, mais avec l'argument nShow = 5 à ShellExecuteEx, une nouvelle fenêtre de commande s'ouvrira avec la sortie du script élevé.
Emil Styrke
2
Pour le devis, vous pouvez utiliser subprocess.list2cmdlinepour le faire correctement.
coderforlife
29

Il semble qu'il n'y ait aucun moyen d'élever les privilèges de l'application pendant un certain temps pour que vous puissiez effectuer une tâche particulière. Windows a besoin de savoir au démarrage du programme si l'application nécessite certains privilèges et demandera à l'utilisateur de confirmer quand l'application exécute des tâches nécessitant ces privilèges. Il y a deux façons de faire ça:

  1. Écrivez un fichier manifeste qui indique à Windows que l'application peut nécessiter certains privilèges
  2. Exécutez l'application avec des privilèges élevés depuis un autre programme

Ces deux articles expliquent plus en détail comment cela fonctionne.

Ce que je ferais, si vous ne voulez pas écrire un wrapper ctypes méchant pour l'API CreateElevatedProcess, c'est utiliser l'astuce ShellExecuteEx expliquée dans l'article Code Project (Pywin32 est livré avec un wrapper pour ShellExecute). Comment? Quelque chose comme ça:

Lorsque votre programme démarre, il vérifie s'il dispose des privilèges d'administrateur, s'il ne le fait pas, il s'exécute à l'aide de l'astuce ShellExecute et quitte immédiatement, s'il le fait, il exécute la tâche en cours.

Comme vous décrivez votre programme comme un "script", je suppose que cela suffit à vos besoins.

À votre santé.

dguaraglia
la source
Merci pour ces liens, ils m'ont été très utiles pour en savoir beaucoup sur les choses UAC.
Colen
4
Quelque chose que vous voudrez peut-être noter à ce sujet est que vous pouvez faire ShellExecute sans PyWin32 (j'ai eu des problèmes pour l'installer) en utilisant os.startfile ($ EXECUTABLE, "runas").
Mike McQuaid
@Mike - mais runasaffiche une nouvelle invite cependant. Et startfile n'accepte pas les arguments de ligne de commande pour$EXECUTABLE.
Sridhar Ratnakumar
J'ai ajouté une autre réponse avec une implémentation complète de cette technique qui devrait pouvoir être ajoutée au début de tout script python.
Jorenko
L'article du deuxième lien était «Privilège le moins important: apprenez à vos applications à bien jouer avec le contrôle de compte d'utilisateur de Windows Vista» dans «MSDN Magazine janvier 2007», mais ce numéro n'est désormais disponible que sous forme de .chmfichier.
Peter
6

Il suffit d'ajouter cette réponse au cas où d'autres seraient dirigés ici par la recherche Google comme je l'étais. J'ai utilisé le elevatemodule dans mon script Python et le script exécuté avec les privilèges d'administrateur sous Windows 10.

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

Irving Moy
la source
Hé, j'ai essayé d'utiliser le elevatemodule et j'obtiens l'erreur "Le fichier n'est pas accessible par le système", des idées pourquoi cela se produirait-il?
paxos1977
@ paxos1977 Pouvez-vous publier un extrait de code illustrant cette erreur? Merci!
Irving Moy
4

L'exemple suivant s'appuie sur l' excellent travail et la réponse acceptée de MARTIN DE LA FUENTE SAAVEDRA . En particulier, deux énumérations sont introduites. Le premier permet de spécifier facilement la manière dont un programme surélevé doit être ouvert, et le second aide lorsque les erreurs doivent être facilement identifiées. S'il vous plaît noter que si vous voulez que tous les arguments de ligne de commande passée au nouveau processus, sys.argv[0]devrait probablement être remplacé par un appel de fonction: 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()
Noctis Skytower
la source
4

Reconnaissant que cette question a été posée il y a des années, je pense qu'une solution plus élégante est proposée sur github par frmdstryr en utilisant son module pywinutils:

Extrait:

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    

Cela utilise l'interface COM et indique automatiquement que les privilèges d'administrateur sont nécessaires avec l'invite de dialogue familière que vous verriez si vous copiez dans un répertoire où les privilèges d'administrateur sont requis et fournit également la boîte de dialogue de progression de fichier typique pendant l'opération de copie.

KenV99
la source
2

Cela peut ne pas répondre complètement à votre question, mais vous pouvez également essayer d'utiliser la commande Elevate Powertoy afin d'exécuter le script avec des privilèges UAC élevés.

http://technet.microsoft.com/en-us/magazine/2008.06.elevation.aspx

Je pense que si vous l'utilisez, cela ressemblerait à `` élever python yourscript.py ''

TinyGrasshopper
la source
2

Vous pouvez créer un raccourci quelque part et utiliser comme cible: python yourscript.py puis sous propriétés et sélectionnez avancé en tant qu'administrateur.

Lorsque l'utilisateur exécute le raccourci, il lui demandera d'élever l'application.

magasins officiels
la source
1

Si votre script nécessite toujours les privilèges d'un administrateur, alors:

runas /user:Administrator "python your_script.py"
jfs
la source
15
attention, élévation! = en cours d'exécution en tant qu'administrateur
Kugel
Je suis nouveau sur python ... pouvez-vous me dire où vais-je mettre ce code?
Rahat Islam Khan
@RahatIslamKhan: Ouvrez une fenêtre d'invite de commandes et placez-la où: la commande s'exécute en your_script.pytant qu'utilisateur administrateur. Assurez-vous de comprendre le commentaire de @ Kugel .
jfs
1

Une variante du travail de Jorenko ci-dessus permet au processus élevé d'utiliser la même console (mais voir mon commentaire ci-dessous):

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
Berwyn
la source
Désolé. la même option de console (SEE_MASK_NO_CONSOLE) ne fonctionne que si vous êtes déjà élevé. Ma faute.
Berwyn
1

Il s'agit principalement d'une mise à jour de la réponse de Jorenko, qui permet d'utiliser des paramètres avec des espaces dans Windows, mais devrait également fonctionner assez bien sous Linux :) De plus, fonctionnera avec cx_freeze ou py2exe puisque nous n'utilisons pas __file__mais sys.argv[0]comme exécutable

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:])
Orsiris de Jong
la source