Запросить повышение UAC из Python script?

Я хочу, чтобы мой Python script копировал файлы в Vista. Когда я запускаю его из обычного окна cmd.exe, никаких ошибок не генерируется, но файлы НЕ копируются. Если я запустил cmd.exe "как администратор", а затем запустил мой script, он отлично работает.

Это имеет смысл, поскольку User Account Control (UAC) обычно предотвращает многие действия файловой системы.

Есть ли способ, изнутри Python script, вызывать запрос на повышение UAC (те диалоги, которые говорят что-то вроде "такое-то приложение нуждается в доступе администратора, это нормально?" )

Если это невозможно, есть ли способ, по которому мой script может, по крайней мере, обнаружить, что он не повышен, чтобы он мог терпеть неудачу изящно?

Ответ 1

По состоянию на 2017 год, легкий способ достичь этого заключается в следующем:

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)

Если вы используете Python 2.x, вы должны заменить последнюю строку:

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

Также обратите внимание, что если вы преобразовали скрипт python в исполняемый файл (используя такие инструменты, как py2exe, cx_freeze, pyinstaller), вы должны заменить четвертый параметр на пустую строку ("").

Вот некоторые из преимуществ:

  • Никаких внешних библиотек не требуется (а также расширение Python для Windows). Он использует только ctypes из стандартной библиотеки.
  • Работает как на Python 2, так и на Python 3.
  • Нет необходимости изменять файловые ресурсы или создавать файл манифеста.
  • Если вы не добавляете код ниже, если /else, код никогда не будет выполняться дважды.
  • Вы можете легко изменить его, чтобы иметь особое поведение, если пользователь отклоняет приглашение UAC.
  • Вы можете указать аргументы, изменяющие четвертый параметр.
  • Вы можете указать способ отображения, изменяющий шестой параметр.

Документация для основного вызова ShellExecute приведена здесь.

Ответ 2

Мне потребовалось немного времени, чтобы получить ответ dguaraglia, поэтому в интересах спасения других времен, вот что я сделал для реализации этой идеи:

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)

Ответ 3

Похоже, что некорректно поднять привилегии приложения для выполнения конкретной задачи. Windows должна знать в начале программы, требует ли приложение определенных прав, и попросит пользователя подтвердить, когда приложение выполняет любые задачи, требующие этих привилегий. Существует два способа сделать это:

  • Записать файл манифеста, который сообщает Windows, для приложения могут потребоваться некоторые привилегии
  • Запустите приложение с повышенными привилегиями из другой программы

Этот два статьи объясняют гораздо подробнее, как это работает.

Что бы я сделал, если вы не хотите писать неприятную оболочку ctypes для API CreateElevatedProcess, используется трюк ShellExecuteEx, описанный в статье Code Project (Pywin32 поставляется с оболочкой для ShellExecute). Как? Что-то вроде этого:

Когда ваша программа запускается, она проверяет, есть ли у нее права администратора, если она не запускается сама с помощью трюка ShellExecute и немедленно выходит, если это так, она выполняет задачу под рукой.

Как вы описываете свою программу как "script", я полагаю, что этого достаточно для ваших нужд.

Приветствия.

Ответ 4

Признание этого вопроса было задано несколько лет назад, я думаю, что более элегантное решение предлагается на github с помощью frmdstryr с помощью его модуля pyminutils:

Выдержки:

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    

Это использует интерфейс COM и автоматически указывает, что необходимы привилегии администратора со знакомой диалоговой подсказкой, которую вы увидите, если бы вы копировали в каталог, где необходимы привилегии администратора, а также предоставляет типичный диалог хода файла во время операции копирования.

Ответ 5

Следующий пример основан на превосходной работе Мартина де ла Фуенте Сааведры и принятом ответе. В частности, введены два перечисления. Первый позволяет легко определить способ открытия программы с повышенными правами, а второй помогает, когда необходимо легко идентифицировать ошибки. Обратите внимание, что если вы хотите, чтобы все аргументы командной строки были переданы новому процессу, sys.argv[0] вероятно, следует заменить вызовом функции: subprocess.list2cmdline(sys.argv).

#! /usr/bin/env python3
import ctypes
import enum
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()

Ответ 6

Это может не полностью ответить на ваш вопрос, но вы также можете попробовать использовать команду Elevate Powertoy для запуска script с повышенными привилегиями UAC.

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

Я думаю, что если вы его используете, это будет выглядеть как "поднять python yourscript.py"

Ответ 7

Если ваш script всегда требует привилегий администратора, то:

runas /user:Administrator "python your_script.py"

Ответ 8

Вы можете сделать ярлык где-нибудь и использовать цель: python yourscript.py затем по свойствам и расширенному выбору запускается как администратор.

Когда пользователь выполняет ярлык, он попросит их поднять приложение.

Ответ 9

Вариант работы Jorenko выше позволяет приподнятому процессу использовать ту же консоль (но см. мой комментарий ниже):

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

Ответ 10

Это, в основном, обновление до ответа Джоренко, позволяющее использовать параметры с пробелами в Windows, но также должно хорошо работать на Linux :) Также будет работать с cx_freeze или py2exe, поскольку мы не используем __file__ но sys.argv[0] как исполняемый файл

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:])