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


94

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

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

Можно ли из сценария Python вызвать запрос на повышение прав UAC (в тех диалоговых окнах, которые говорят что-то вроде «такому-то приложению нужен доступ администратора, это нормально?»)

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


3
stackoverflow.com/a/1445547/1628132 после этого ответа вы создаете .exe из сценария .py, используя py2exe и используя флаг под названием «uac_info», это довольно
изящное

Ответы:


100

По состоянию на 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, " ".join(sys.argv), None, 1)

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

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

Также обратите внимание , что если вы конвертированы вам питон скрипт в исполняемый файл ( с помощью таких инструментов , как py2exe, cx_freeze, pyinstaller) , то вы должны использовать sys.argv[1:]вместо sys.argvв четвертом параметре.

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

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

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


9
Мне пришлось использовать экземпляры Unicode в качестве параметров для ShellExecuteW (например, u'runas 'и unicode (sys.executable)), чтобы запустить это.
Янош

6
@Janosch, это потому, что вы используете Python 2.x, а мой код находится на Python 3 (где все строки обрабатываются как юникоды). Но стоит упомянуть, спасибо!
Мартин Де ла Фуэнте

2
@Martin, если я запускаю этот код из командной строки Windows, например: «python yourcode.py», он просто открывает python.exe. Есть способ исправить это?
user2978216

1
@ user2978216 У меня была такая же проблема. В строке ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, "", None, 1) sys.executableразрешается только интерпретатор python (например C:\Python27\Python.exe). Решение состоит в том, чтобы добавить запущенный скрипт в качестве аргумента (замена ""). ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)Также обратите внимание, что для этого , чтобы работать в Python 2.x, все строковые аргументы должны быть Unicode (то есть u"runas", unicode(sys.executable)а unicode(__file__))
Хавьер Ubillos

2
@HrvojeT Оба, ShellExecuteWи ShellExecuteAявляются вызовами ShellExecuteфункции в Windows API. Первый обязывает строки быть в формате Unicode, а второй используется с форматом ANSI
Мартин Де ла Фуэнте,

71

Мне потребовалось некоторое время, чтобы получить ответ от 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)

1
кажется, что это просто поднимается, а затем выхожу ... если я вставлю несколько операторов печати, они не будут выполнены во второй раз
Джоран Бисли

6
@JoranBeasley, вы не увидите никаких результатов. ShellExecuteEx не отправляет свой STDOUT обратно в исходную оболочку. В этом отношении отладка будет ... сложной задачей. Но уловка с повышением привилегий определенно работает.
Тим Китинг

1
@TimKeating, у ActiveState есть рецепт, который должен немного упростить отладку: используйте утилиту DebugView со стандартным
ведением

1
кажется невозможным получить вывод в той же консоли, но с аргументом nShow = 5 для ShellExecuteEx откроется новое командное окно с выводом из сценария с повышенными правами.
Эмиль Стырке,

2
Для цитирования вы можете использовать, subprocess.list2cmdlineчтобы сделать это правильно.
coderforlife

29

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

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

В этих двух статьях более подробно объясняется, как это работает.

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

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

Поскольку вы описываете свою программу как «сценарий», я полагаю, что этого достаточно для ваших нужд.

Ура.


Спасибо за эти ссылки, они были очень полезны для меня, когда я узнал много нового о UAC.
Colen,

4
Обратите внимание на то, что ShellExecute можно выполнять без PyWin32 (у меня были проблемы с его установкой) с помощью os.startfile ($ EXECUTABLE, "runas").
Майк Маккуэйд,

@Mike - но runasвыводит новую подсказку. И StartFile не принимает аргументы командной строки$EXECUTABLE.
Шридхар Ratnakumar

Я добавил еще один ответ с полной реализацией этого метода, который должен быть добавлен в начало любого скрипта Python.
Jorenko

Статья по второй ссылке была «Наименьшие привилегии: научите свои приложения правильно играть с контролем учетных записей Windows Vista» в «Журнале MSDN, январь 2007», но теперь этот выпуск доступен только в виде .chmфайла.
Питер

6

Просто добавляю этот ответ на тот случай, если другие направляются сюда поиском Google, как и я. Я использовал elevateмодуль в своем скрипте Python и скрипт, выполненный с правами администратора в Windows 10.

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


Привет, я пробовал использовать elevateмодуль и получаю ошибку «Файл не доступен для системы», есть идеи, почему это могло произойти?
paxos1977

@ paxos1977 Можете ли вы опубликовать фрагмент кода, демонстрирующий эту ошибку? Благодарность!
Ирвинг Мой,

5

Следующий пример основан на отличной работе MARTIN DE LA FUENTE SAAVEDRA и принятом ответе. В частности, вводятся два перечисления. Первый позволяет легко указать, как следует открывать программу с повышенными правами, а второй помогает, когда необходимо легко идентифицировать ошибки. Обратите внимание , что если вы хотите , чтобы все аргументы командной строки , передаваемые в новый процесс, sys.argv[0]вероятно , следует заменить вызов функции: 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

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

Отрывок:

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 и автоматически указывается, что необходимы права администратора, с помощью знакомого диалогового окна, которое вы могли бы увидеть, если бы вы копировали в каталог, где требуются права администратора, а также предоставляет типичный диалог выполнения файла во время операции копирования.


2

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

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

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


2

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

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


1

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

runas /user:Administrator "python your_script.py"

15
осторожно, высота! = работает от имени администратора
Кугель

Я новичок в Python ... вы можете сказать мне, где я могу разместить этот код?
Рахат Ислам Хан

@RahatIslamKhan: откройте окно командной строки и поместите его туда: команда запускается your_script.pyот имени администратора. Убедитесь, что вы поняли комментарий @Kugel .
jfs

1

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

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

Сожалею. тот же параметр консоли (SEE_MASK_NO_CONSOLE) работает, только если вы уже повышены. Виноват.
Berwyn

1

В основном это обновление ответа Йоренко, которое позволяет использовать параметры с пробелами в 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:])
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.