web-dev-qa-db-fra.com

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

Je veux que mon script Python pour copier des fichiers sur Vista. Lorsque je l'exécute à partir d'un cmd.exe fenêtre, 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 exécutez mon script, cela fonctionne très bien.

Cela est logique 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 OK?"))

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é afin qu'il puisse échouer gracieusement?

78
jwfearn

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, __file__, 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(__file__), None, 1)

Notez également que si vous avez converti votre script python en un fichier exécutable (à l'aide d'outils tels que py2exe, cx_freeze, pyinstaller), vous devez remplacer le quatrième paramètre pour une chaîne vide ("").

Voici quelques avantages:

  • Aucune bibliothèque externe requise (ni Python pour l'extension Windows). Elle utilise uniquement ctypes de la bibliothèque standard.
  • Fonctionne sur les deux 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 en dessous de l'instruction if/else, le code ne sera jamais exécuté deux fois.
  • Vous pouvez facilement le modifier pour avoir un comportement spécial si l'utilisateur rejette l'invite UAC.
  • Vous pouvez spécifier des arguments modifiant le quatrième paramètre.
  • Vous pouvez spécifier la méthode d'affichage en modifiant le sixième paramètre.

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

72

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, 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)
64
Jorenko

Il semble qu'il n'y ait aucun moyen d'élever les privilèges d'application pendant un certain temps pour que vous puissiez effectuer une tâche particulière. Windows doit savoir au début du programme si l'application requiert certains privilèges et demandera à l'utilisateur de confirmer quand l'application effectue des tâches dont a besoin 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

Ceci deuxarticles explique de manière beaucoup plus détaillée comment cela fonctionne.

Ce que je ferais, si vous ne voulez pas écrire un wrapper ctypes désagréable pour l'API CreateElevatedProcess, est d'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 de privilèges d'administrateur, s'il ne s'exécute pas lui-même à l'aide de l'astuce ShellExecute et se ferme immédiatement, s'il le fait, il exécute la tâche à accomplir.

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

À votre santé.

29
dguaraglia

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 pyminutils:

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 verrez 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.

4
KenV99

L'exemple suivant s'appuie sur MARTIN DE LA FUENTE SAAVEDRA excellent travail et réponse acceptée. En particulier, deux énumérations sont introduites. Le premier permet de spécifier facilement comment un programme élevé doit être ouvert, et le second aide lorsque les erreurs doivent être facilement identifiées. Veuillez noter que si vous souhaitez que tous les arguments de ligne de commande soient transmis 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 __== '__main__':
    bootstrap()
3
Noctis Skytower

Cela peut ne pas répondre complètement à votre question, mais vous pouvez également essayer d'utiliser le Powertoy de commande Elevate 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 à 'elevate python yourscript.py'

2
TinyGrasshopper

Une variation sur le 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
1
Berwyn

Vous pouvez créer un raccourci quelque part et en tant que cible, utilisez: python yourscript.py, puis sous Propriétés et sélectionnez Avancé, exécutez en tant qu'administrateur.

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

1
officialhopsof

Il s'agit principalement d'une mise à niveau de la réponse de Jorenko, qui permet d'utiliser des paramètres avec des espaces dans Windows, mais devrait également fonctionner assez bien sur Linux :) En outre, fonctionnera avec cx_freeze ou py2exe car 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 __== '__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:])
1
Orsiris de Jong

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

runas /user:Administrator "python your_script.py"
1
jfs