Надіслати запит на підвищення рівня UAC із сценарію Python?


91

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

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

Чи є спосіб, як я можу, з сценарію Python, викликати запит на підвищення UAC (ті діалоги, в яких написано щось на кшталт "такий і такий додаток потребує доступу адміністратора, це нормально?")

Якщо це неможливо, чи є спосіб, за допомогою якого мій скрипт може принаймні виявити, що він не підвищений, щоб він міг вийти з ладу?


3
stackoverflow.com/a/1445547/1628132 слідом за цією відповіддю ви створюєте .exe із скрипта .py за допомогою py2exe та використовуючи прапор під назвою 'uac_info', це досить акуратне рішення
foxcoreg

Відповіді:


96

Станом на 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)), щоб запустити це.
Janosch

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__))
Хав'єр Убіллос

2
@HrvojeT І те, ShellExecuteWі інше - ShellExecuteAце виклики ShellExecuteфункції в API Windows. Перший зобов’язує рядки бути у форматі unicode, а другий використовується у форматі ANSI
Martín De la Fuente

69

Мені знадобилося трохи часу, щоб відповідь 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 відкриється нове вікно команд із вихідними даними з підвищеного сценарію.
Emil Styrke

2
Для цитування ви можете використовувати, subprocess.list2cmdlineщоб зробити це належним чином.
coderforlife

29

Здається, немає можливості на деякий час підвищити привілеї програми для виконання певного завдання. На початку програми Windows повинна знати, чи вимагає програма певних привілеїв, і попросить користувача підтвердити, коли програма виконує будь-які завдання, які потребують цих привілеїв. Це можна зробити двома способами:

  1. Напишіть файл маніфесту, який повідомляє Windows, що програма може вимагати деяких привілеїв
  2. Запустіть програму з підвищеними привілеями всередині іншої програми

Ці дві статті значно детальніше пояснюють, як це працює.

Що б я зробив, якщо ви не хочете писати неприємну обгортку ctypes для API CreateElevatedProcess, я використовую трюк ShellExecuteEx, пояснений у статті Проект коду (Pywin32 постачається з обгорткою для ShellExecute). Як? Щось на зразок цього:

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

Описуючи свою програму як "сценарій", я гадаю, цього достатньо для ваших потреб.

Ура.


Дякую за ці посилання, вони були дуже корисні для мене, коли я дізнався багато про UAC речі.
Колен

4
Щось, на що ви можете звернути увагу, це те, що ви можете робити ShellExecute без PyWin32 (у мене були проблеми з встановленням) за допомогою os.startfile ($ EXECUTABLE, "runas").
Mike McQuaid

@Mike - але все ж runasвикликає нову підказку. І стартовий файл не приймає аргументи командного рядка до$EXECUTABLE.
Шрідхар Ратнакумар,

Я додав ще одну відповідь із повною реалізацією цієї техніки, яку слід було б додати до початку будь-якого сценарію python.
Йоренко

Стаття до другого посилання була "Найменший привілей: Навчіть свої програми грати добре за допомогою керування обліковими записами користувачів Windows Vista" у "Журналі MSDN за січень 2007 р.", Але це видання тепер доступне лише у вигляді .chmфайлу.
Пітер

6

Просто додайте цю відповідь на випадок, якщо інших шукає тут Google Search, як я. Я використовував elevateмодуль у своєму скрипті Python та сценарій, виконаний із правами адміністратора в Windows 10.

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


Гей, я спробував використовувати elevateмодуль, і я отримую помилку "Система не може отримати доступ до файлу", будь-яка ідея, чому це може статися?
paxos1977

@ paxos1977 Чи можете ви опублікувати фрагмент коду, який демонструє цю помилку? Дякую!
Ірвінг Мой,

4

Наступний приклад ґрунтується на чудовій роботі та прийнятій відповіді 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

Визнаючи це запитання, яке було задано багато років тому, я думаю , що frmdstryr пропонує на github більш елегантне рішення за допомогою його модуля 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

Це може не повністю відповісти на ваше запитання, але ви також можете спробувати скористатися командою Elevate Powertoy, щоб запустити сценарій із підвищеними правами UAC.

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

Я думаю, якщо ви використовуєте це, це буде виглядати як 'elevate python yourscript.py'


2

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

Коли користувач виконає ярлик, він попросить його підняти програму.


1

Якщо ваш сценарій завжди вимагає прав адміністратора, виконайте наведені нижче дії.

runas /user:Administrator "python your_script.py"

15
обережно, висота! = працює як адміністратор
Kugel

Я новачок у 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) працює лише в тому випадку, якщо ви вже підняті. Моє ліжко.
Бервін,

1

В основному це оновлення для відповіді Jorenko в, що дозволяє використовувати параметри з пробілами в 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.