Solicitar elevação UAC de dentro de um script Python?


91

Quero que meu script Python copie arquivos no Vista. Ao executá-lo em uma cmd.exejanela normal , nenhum erro é gerado, mas os arquivos NÃO são copiados. Se eu executar cmd.exe"como administrador" e depois executar meu script, ele funcionará bem.

Isso faz sentido, já que o Controle de Conta de Usuário (UAC) normalmente impede muitas ações do sistema de arquivos.

Existe uma maneira de, de dentro de um script Python, invocar uma solicitação de elevação UAC (aquelas caixas de diálogo que dizem algo como "tal e tal aplicativo precisa de acesso de administrador, está certo?")

Se isso não for possível, há uma maneira de meu script pelo menos detectar que não está elevado para que possa falhar normalmente?


3
stackoverflow.com/a/1445547/1628132 seguindo esta resposta, você cria um .exe a partir do script .py usando py2exe e usando um sinalizador chamado 'uac_info' é uma solução muito legal
foxcoreg

Respostas:


96

A partir de 2017, um método fácil de conseguir isso é o seguinte:

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)

Se estiver usando Python 2.x, você deve substituir a última linha por:

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

Observe também que se você converteu você script python em um arquivo executável (usando ferramentas como py2exe, cx_freeze, pyinstaller), então você deve usar sys.argv[1:]em vez de sys.argvno quarto parâmetro.

Algumas das vantagens aqui são:

  • Nenhuma biblioteca externa necessária. Ele só usa ctypese sysda biblioteca padrão.
  • Funciona em Python 2 e Python 3.
  • Não há necessidade de modificar os recursos do arquivo nem de criar um arquivo de manifesto.
  • Se você não adicionar o código abaixo da instrução if / else, o código nunca será executado duas vezes.
  • Você pode obter o valor de retorno da chamada de API na última linha e executar uma ação se ela falhar (código <= 32). Verifique os possíveis valores de retorno aqui .
  • Você pode alterar o método de exibição do processo gerado modificando o sexto parâmetro.

A documentação da chamada ShellExecute subjacente está aqui .


8
Tive que usar instâncias unicode como parâmetros para ShellExecuteW (como u'runas 'e unicode (sys.executable)) para fazê-lo funcionar.
Janosch de

6
@Janosch, isso é porque você está usando Python 2.x, enquanto meu código está em Python 3 (onde todas as strings são tratadas como unicodes). Mas é bom falar, obrigado!
Martín De la Fuente de

2
@Martin se estou executando este código da linha de comando do Windows como este: "python yourcode.py", ele apenas abre python.exe. Existe uma maneira de consertar isso?
user2978216

1
@ user2978216 Eu tive o mesmo problema. Na linha ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, "", None, 1) sys.executableresolve apenas o interpretador python (por exemplo C:\Python27\Python.exe). A solução é adicionar o script em execução como um argumento (substituindo ""). ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)Observe também que, para que isso funcione no python 2.x, todos os argumentos de string precisam ser Unicode (ou seja u"runas", unicode(sys.executable)e unicode(__file__))
Javier Ubillos

2
@HrvojeT Ambos ShellExecuteWe ShellExecuteAsão chamadas para a ShellExecutefunção na API do Windows. O primeiro obriga as strings a estarem no formato unicode e o segundo é usado no formato ANSI
Martín De la Fuente,

69

Demorei um pouco para fazer a resposta de dguaraglia funcionar, então, no interesse de economizar o tempo de outras pessoas, aqui está o que fiz para implementar essa ideia:

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
isso só parece aumentar e depois sair ... se eu inserir algumas instruções de impressão, elas não serão executadas uma segunda vez
Joran Beasley

6
@JoranBeasley, você não verá nenhuma saída. ShellExecuteEx não posta seu STDOUT de volta no shell de origem. Nesse sentido, a depuração será ... desafiadora. Mas o truque de içar privilégios definitivamente funciona.
Tim Keating

1
@TimKeating, ActiveState tem uma receita que deve tornar a depuração um pouco mais fácil: use o utilitário DebugView com registro de python padrão
samwyse

1
parece impossível obter a saída no mesmo console, mas com o argumento nShow = 5 para ShellExecuteEx, uma nova janela de comando será aberta com a saída do script elevado.
Emil Styrke

2
Para as citações, você pode usar subprocess.list2cmdlinepara fazê-lo corretamente.
coderforlife

29

Parece que não há como elevar os privilégios do aplicativo por um tempo para que você execute uma tarefa específica. O Windows precisa saber no início do programa se o aplicativo requer certos privilégios e pedirá ao usuário para confirmar quando o aplicativo executa qualquer tarefa que precise desses privilégios. Existem duas maneiras de fazer isso:

  1. Escreva um arquivo de manifesto que informe ao Windows que o aplicativo pode exigir alguns privilégios
  2. Execute o aplicativo com privilégios elevados de dentro de outro programa

Estes dois artigos explicam com muito mais detalhes como isso funciona.

O que eu faria, se você não quiser escrever um invólucro ctypes desagradável para a API CreateElevatedProcess, é usar o truque ShellExecuteEx explicado no artigo do Projeto de código (Pywin32 vem com um invólucro para ShellExecute). Quão? Algo assim:

Quando o seu programa é iniciado, ele verifica se tem privilégios de Administrador, se não ele se executa usando o truque ShellExecute e sai imediatamente, se tiver, executa a tarefa em questão.

Conforme você descreve seu programa como um "script", suponho que seja o suficiente para suas necessidades.

Felicidades.


Obrigado por esses links, eles foram muito úteis para eu descobrir muito sobre as coisas do UAC.
Colen de

4
Algo que você pode querer observar sobre isso é que você pode fazer ShellExecute sem PyWin32 (tive problemas para instalá-lo) usando os.startfile ($ EXECUTABLE, "runas").
Mike McQuaid

@Mike - mas runastraz um novo prompt. E startfile não aceita argumentos de linha de comando para$EXECUTABLE.
Sridhar Ratnakumar

Eu adicionei outra resposta com uma implementação completa dessa técnica que deve ser capaz de ser adicionada ao início de qualquer script Python.
Jorenko

O artigo para o segundo link era "Menor privilégio: Ensine seus aplicativos a funcionar bem com o controle de conta de usuário do Windows Vista" na "MSDN Magazine Janeiro 2007", mas agora esta edição está disponível apenas como .chmarquivo.
Peter

6

Basta adicionar esta resposta caso outros sejam direcionados aqui pela Pesquisa Google como eu. Usei o elevatemódulo em meu script Python e o script executado com privilégios de administrador no Windows 10.

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


Ei, eu tentei usar o elevatemódulo e estou recebendo o erro "O arquivo não pode ser acessado pelo sistema", alguma idéia de por que isso aconteceria?
paxos1977

@ paxos1977 Você pode postar um snippet de código que demonstre esse erro? Obrigado!
Irving Moy

4

O exemplo a seguir baseia-se no excelente trabalho e na resposta aceita de MARTIN DE LA FUENTE SAAVEDRA . Em particular, duas enumerações são introduzidas. O primeiro permite a fácil especificação de como um programa elevado deve ser aberto e o segundo ajuda quando os erros precisam ser facilmente identificados. Por favor, note que se você quiser que todos os argumentos de linha de comando passados para o novo processo, sys.argv[0]provavelmente deve ser substituída por uma chamada de função: 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

Reconhecendo que essa pergunta foi feita anos atrás, acho que uma solução mais elegante é oferecida no github por frmdstryr usando seu módulo pywinutils:

Excerto:

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    

Isso utiliza a interface COM e indica automaticamente que os privilégios de administrador são necessários com o prompt de diálogo familiar que você veria se estivesse copiando para um diretório onde os privilégios de administrador são necessários e também fornece a caixa de diálogo de progresso de arquivo típica durante a operação de cópia.



2

Você pode criar um atalho em algum lugar e, como destino, usar: python yourscript.py e, em seguida, em propriedades e seleção avançada, execute como administrador.

Quando o usuário executa o atalho, ele pede para elevar o aplicativo.


1

Se o seu script sempre requer privilégios de administrador:

runas /user:Administrator "python your_script.py"

14
cuidado, elevação! = executando como administrador
Kugel

Eu sou novo em python ... você pode me dizer onde vou colocar esse código?
Rahat Islam Khan

@RahatIslamKhan: Abra uma janela de Prompt de Comando e coloque-a onde: o comando é executado your_script.pycomo um usuário Administrador. Certifique-se de entender o comentário de @Kugel .
jfs

1

Uma variação do trabalho de Jorenko acima permite que o processo elevado use o mesmo console (mas veja meu comentário abaixo):

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

Desculpe. a mesma opção de console (SEE_MASK_NO_CONSOLE) só funciona se você já estiver elevado. Foi mal.
Berwyn

1

Esta é principalmente uma atualização para a resposta de Jorenko, que permite usar parâmetros com espaços no Windows, mas também deve funcionar bastante bem no Linux :) Além disso, funcionará com cx_freeze ou py2exe já que não usamos, __file__mas sys.argv[0]como executáveis

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:])
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.