ขอการยกระดับ UAC จากภายในสคริปต์ Python หรือไม่


98

ฉันต้องการให้สคริปต์ Python ของฉันคัดลอกไฟล์บน Vista เมื่อฉันเรียกใช้จากcmd.exeหน้าต่างปกติจะไม่มีการสร้างข้อผิดพลาด แต่ไฟล์จะไม่ถูกคัดลอก ถ้าฉันเรียกใช้cmd.exe"ในฐานะผู้ดูแลระบบ" จากนั้นเรียกใช้สคริปต์ของฉันก็ใช้ได้ดี

สิ่งนี้สมเหตุสมผลเนื่องจากโดยปกติการควบคุมบัญชีผู้ใช้ (UAC) จะป้องกันการทำงานของระบบไฟล์จำนวนมาก

มีวิธีใดบ้างที่ฉันสามารถเรียกใช้คำขอยกระดับ UAC จากภายในสคริปต์ Python (กล่องโต้ตอบที่ระบุว่า "เช่นนั้นและแอปดังกล่าวต้องการการเข้าถึงระดับผู้ดูแลระบบหรือไม่")

หากเป็นไปไม่ได้มีวิธีอย่างน้อยสคริปต์ของฉันสามารถตรวจพบว่าไม่ได้ยกระดับเพื่อให้ล้มเหลวอย่างสง่างามหรือไม่?


3
stackoverflow.com/a/1445547/1628132ตามคำตอบนี้คุณสร้าง. exe จากสคริปต์. py โดยใช้ py2exe และใช้แฟล็กที่เรียกว่า 'uac_info' มันเป็นวิธีแก้ปัญหาที่ค่อนข้างเรียบร้อย
foxcoreg

คำตอบ:


105

ในปี 2560 วิธีง่ายๆในการบรรลุเป้าหมายนี้มีดังต่อไปนี้:

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 (โดยที่สตริงทั้งหมดจะถือว่าเป็น Unicodes) แต่เป็นการดีที่จะกล่าวถึงขอบคุณ!
Martín De la Fuente

2
@Martin ถ้าฉันเรียกใช้รหัสนี้จากบรรทัดคำสั่งของ Windows เช่นนี้ "python yourcode.py" มันก็เปิด python.exe มีวิธีแก้ไขไหม
user2978216

1
@ user2978216 ฉันมีปัญหาเดียวกัน ในบรรทัดctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, "", None, 1) sys.executableแก้ไขเฉพาะ python interpreter (เช่นC:\Python27\Python.exe) วิธีแก้ปัญหาคือการเพิ่มสคริปต์ที่รันเป็นอาร์กิวเมนต์ (การจำลองซ้ำ"") ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)นอกจากนี้ยังทราบว่าสำหรับการทำงานในหลาม 2.x ข้อโต้แย้งสตริงทุกคนต้อง Unicode (เช่นu"runas", unicode(sys.executable)และunicode(__file__))
ฮาเวียร์ Ubillos

2
@HrvojeT ทั้งสองShellExecuteWและShellExecuteAเป็นการเรียกใช้ShellExecuteฟังก์ชันใน Windows API ก่อนหน้านี้กำหนดให้สตริงอยู่ในรูปแบบ Unicode และส่วนหลังจะใช้กับรูปแบบ ANSI
Martín De la Fuente

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
นี่ดูเหมือนจะยกระดับแล้วออก ... ถ้าฉันใส่คำสั่งพิมพ์พวกเขาจะไม่ถูกดำเนินการเป็นครั้งที่สอง
Joran Beasley

6
@JoranBeasley คุณจะไม่เห็นผลลัพธ์ใด ๆ ShellExecuteEx จะไม่โพสต์ STDOUT กลับไปที่เชลล์ต้นทาง ในแง่นั้นการดีบักจะ ... ท้าทาย แต่เคล็ดลับการยกสิทธิ์ได้ผลแน่นอน
Tim Keating

1
@TimKeating, ActiveState มีสูตรที่ควรทำให้การดีบักง่ายขึ้นเล็กน้อย: ใช้ยูทิลิตี้ DebugView กับการบันทึก Python มาตรฐาน
samwyse

1
ดูเหมือนจะเป็นไปไม่ได้ที่จะรับเอาต์พุตในคอนโซลเดียวกัน แต่ด้วยอาร์กิวเมนต์ nShow = 5 ถึง ShellExecuteEx หน้าต่างคำสั่งใหม่จะเปิดขึ้นพร้อมกับเอาต์พุตจากสคริปต์ที่ยกระดับ
Emil Styrke

2
สำหรับ quoting คุณสามารถใช้subprocess.list2cmdlineทำอย่างถูกต้อง
coderforlife

29

ดูเหมือนว่าจะไม่มีทางยกระดับสิทธิ์ของแอปพลิเคชันสักระยะหนึ่งเพื่อให้คุณทำงานบางอย่างได้ Windows จำเป็นต้องทราบเมื่อเริ่มต้นโปรแกรมว่าแอปพลิเคชันต้องการสิทธิ์บางอย่างหรือไม่และจะขอให้ผู้ใช้ยืนยันเมื่อแอปพลิเคชันทำงานใด ๆ ที่ต้องการสิทธิ์เหล่านั้น มีสองวิธีในการดำเนินการนี้:

  1. เขียนไฟล์รายการที่บอก Windows ว่าแอปพลิเคชันอาจต้องการสิทธิ์บางอย่าง
  2. เรียกใช้แอปพลิเคชันด้วยสิทธิ์ที่ได้รับการยกระดับจากภายในโปรแกรมอื่น

นี้สอง บทความอธิบายในรายละเอียดมากขึ้นวิธีการทำงานนี้

ฉันจะทำอย่างไรถ้าคุณไม่ต้องการเขียน ctypes wrapper ที่น่ารังเกียจสำหรับ CreateElevatedProcess API ให้ใช้เคล็ดลับ ShellExecuteEx ที่อธิบายในบทความ Code Project (Pywin32 มาพร้อมกับ wrapper สำหรับ ShellExecute) อย่างไร? สิ่งนี้:

เมื่อโปรแกรมของคุณเริ่มต้นโปรแกรมจะตรวจสอบว่ามีสิทธิ์ของผู้ดูแลระบบหรือไม่หากไม่ได้รันเองโดยใช้เคล็ดลับ ShellExecute และออกทันทีหากเป็นเช่นนั้นโปรแกรมจะทำงานในมือ

ในขณะที่คุณอธิบายโปรแกรมของคุณเป็น "สคริปต์" ฉันคิดว่าเพียงพอสำหรับความต้องการของคุณ

ไชโย


ขอบคุณสำหรับลิงก์เหล่านี้ซึ่งมีประโยชน์มากสำหรับฉันในการค้นหาข้อมูลมากมายเกี่ยวกับ UAC
Colen

4
สิ่งที่คุณอาจต้องการทราบก็คือคุณสามารถทำ ShellExecute ได้โดยไม่ต้องใช้ PyWin32 (ฉันมีปัญหาในการติดตั้ง) โดยใช้ os.startfile ($ EXECUTABLE, "runas")
Mike McQuaid

@ ไมค์ - แต่runasจะแสดงข้อความแจ้งใหม่ และ startfile ไม่ยอมรับอาร์กิวเมนต์บรรทัดคำสั่งถึง$EXECUTABLE.
Sridhar Ratnakumar

ฉันได้เพิ่มคำตอบอื่นด้วยการใช้งานเทคนิคนี้เต็มรูปแบบซึ่งควรจะสามารถเพิ่มลงในจุดเริ่มต้นของสคริปต์ python ได้
Jorenko

บทความไปยังลิงก์ที่สองคือ "สิทธิ์น้อยที่สุด: สอนแอปของคุณให้เล่นได้ดีด้วยการควบคุมบัญชีผู้ใช้ Windows Vista" ใน "นิตยสาร MSDN มกราคม 2550" แต่ตอนนี้มีให้ใช้งานใน.chmรูปแบบไฟล์เท่านั้น
ปีเตอร์

7

เพียงแค่เพิ่มคำตอบนี้ในกรณีที่ Google Search นำทางผู้อื่นมาที่นี่เช่นเดียวกับฉัน ฉันใช้elevateโมดูลในสคริปต์ Python ของฉันและสคริปต์ที่เรียกใช้ด้วยสิทธิ์ของผู้ดูแลระบบใน Windows 10

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


สวัสดีฉันลองใช้elevateโมดูลและได้รับข้อผิดพลาด "ระบบไม่สามารถเข้าถึงไฟล์ได้" มีความคิดว่าทำไมถึงเกิดขึ้น
paxos1977

@ paxos1977 คุณสามารถโพสต์ข้อมูลโค้ดที่แสดงข้อผิดพลาดนั้นได้หรือไม่? ขอบคุณ!
Irving Moy

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

สิ่งนี้อาจไม่ตอบคำถามของคุณอย่างสมบูรณ์ แต่คุณสามารถลองใช้ Elevate Command Powertoy เพื่อเรียกใช้สคริปต์ด้วยสิทธิ์ 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
ระวังความสูง! = ทำงานในฐานะผู้ดูแลระบบ
Kugel

ฉันยังใหม่กับ python ... บอกได้ไหมว่าฉันจะใส่รหัสนั้นที่ไหน
Rahat Islam Khan

@RahatIslamKhan: เปิดหน้าต่างพร้อมรับคำสั่งและวางไว้ที่: คำสั่งทำงานyour_script.pyในฐานะผู้ใช้ผู้ดูแลระบบ ตรวจสอบให้แน่ใจว่าคุณเข้าใจ@ คิดเห็น
jfs

1

รูปแบบในงานของ 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

ขออภัย. ตัวเลือกคอนโซลเดียวกัน (SEE_MASK_NO_CONSOLE) จะใช้ได้เฉพาะเมื่อคุณได้รับการยกระดับแล้ว ความผิดฉันเอง.
Berwyn

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:])
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.