How to run python script with elevated privilege on windows

前端 未结 11 1386
臣服心动
臣服心动 2020-11-22 07:34

I am writing a pyqt application which require to execute admin task. I would prefer to start my script with elevate privilege. I am aware that this question is asked many ti

相关标签:
11条回答
  • 2020-11-22 07:59

    I wanted a more enhanced version so I ended up with a module which allows: UAC request if needed, printing and logging from nonprivileged instance (uses ipc and a network port) and some other candies. usage is just insert elevateme() in your script: in nonprivileged it listen for privileged print/logs and then exits returning false, in privileged instance it returns true immediately. Supports pyinstaller.

    prototype:

    # xlogger : a logger in the server/nonprivileged script
    # tport : open port of communication, 0 for no comm [printf in nonprivileged window or silent]
    # redir : redirect stdout and stderr from privileged instance
    #errFile : redirect stderr to file from privileged instance
    def elevateme(xlogger=None, tport=6000, redir=True, errFile=False):
    

    winadmin.py

    #!/usr/bin/env python
    # -*- coding: utf-8; mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
    # vim: fileencoding=utf-8 tabstop=4 expandtab shiftwidth=4
    
    # (C) COPYRIGHT © Preston Landers 2010
    # (C) COPYRIGHT © Matteo Azzali 2020
    # Released under the same license as Python 2.6.5/3.7
    
    
    import sys, os
    from traceback import print_exc
    from multiprocessing.connection import Listener, Client
    import win32event #win32com.shell.shell, win32process
    import builtins as __builtin__ # python3
    
    # debug suffixes for remote printing
    dbz=["","","",""] #["J:","K:", "G:", "D:"]
    LOGTAG="LOGME:"
    
    wrconn = None
    
    
    #fake logger for message sending
    class fakelogger:
        def __init__(self, xlogger=None):
            self.lg = xlogger
        def write(self, a):
            global wrconn
            if wrconn is not None:
                wrconn.send(LOGTAG+a)
            elif self.lg is not None:
                self.lg.write(a)
            else:
                print(LOGTAG+a)
            
    
    class Writer():
        wzconn=None
        counter = 0
        def __init__(self, tport=6000,authkey=b'secret password'):
            global wrconn
            if wrconn is None:
                address = ('localhost', tport)
                try:
                    wrconn = Client(address, authkey=authkey)
                except:
                    wrconn = None
                wzconn = wrconn
                self.wrconn = wrconn
            self.__class__.counter+=1
            
        def __del__(self):
            self.__class__.counter-=1
            if self.__class__.counter == 0 and wrconn is not None:
                import time
                time.sleep(0.1) # slows deletion but is enough to print stderr
                wrconn.send('close')
                wrconn.close()
        
        def sendx(cls, mesg):
            cls.wzconn.send(msg)
            
        def sendw(self, mesg):
            self.wrconn.send(msg)
            
    
    #fake file to be passed as stdout and stderr
    class connFile():
        def __init__(self, thekind="out", tport=6000):
            self.cnt = 0
            self.old=""
            self.vg=Writer(tport)
            if thekind == "out":
                self.kind=sys.__stdout__
            else:
                self.kind=sys.__stderr__
            
        def write(self, *args, **kwargs):
            global wrconn
            global dbz
            from io import StringIO # # Python2 use: from cStringIO import StringIO
            mystdout = StringIO()
            self.cnt+=1
            __builtin__.print(*args, **kwargs, file=mystdout, end = '')
            
            #handles "\n" wherever it is, however usually is or string or \n
            if "\n" not in mystdout.getvalue():
                if mystdout.getvalue() != "\n":
                    #__builtin__.print("A:",mystdout.getvalue(), file=self.kind, end='')
                    self.old += mystdout.getvalue()
                else:
                    #__builtin__.print("B:",mystdout.getvalue(), file=self.kind, end='')
                    if wrconn is not None:
                        wrconn.send(dbz[1]+self.old)
                    else:
                        __builtin__.print(dbz[2]+self.old+ mystdout.getvalue(), file=self.kind, end='')
                        self.kind.flush()
                    self.old=""
            else:
                    vv = mystdout.getvalue().split("\n")
                    #__builtin__.print("V:",vv, file=self.kind, end='')
                    for el in vv[:-1]:
                        if wrconn is not None:
                            wrconn.send(dbz[0]+self.old+el)
                            self.old = ""
                        else:
                            __builtin__.print(dbz[3]+self.old+ el+"\n", file=self.kind, end='')
                            self.kind.flush()
                            self.old=""
                    self.old=vv[-1]
    
        def open(self):
            pass
        def close(self):
            pass
        def flush(self):
            pass
            
            
    def isUserAdmin():
        if os.name == 'nt':
            import ctypes
            # WARNING: requires Windows XP SP2 or higher!
            try:
                return ctypes.windll.shell32.IsUserAnAdmin()
            except:
                traceback.print_exc()
                print ("Admin check failed, assuming not an admin.")
                return False
        elif os.name == 'posix':
            # Check for root on Posix
            return os.getuid() == 0
        else:
            print("Unsupported operating system for this module: %s" % (os.name,))
            exit()
            #raise (RuntimeError, "Unsupported operating system for this module: %s" % (os.name,))
    
    def runAsAdmin(cmdLine=None, wait=True, hidden=False):
    
        if os.name != 'nt':
            raise (RuntimeError, "This function is only implemented on Windows.")
    
        import win32api, win32con, win32process
        from win32com.shell.shell import ShellExecuteEx
    
        python_exe = sys.executable
        arb=""
        if cmdLine is None:
            cmdLine = [python_exe] + sys.argv
        elif not isinstance(cmdLine, (tuple, list)):
            if isinstance(cmdLine, (str)):
                arb=cmdLine
                cmdLine = [python_exe] + sys.argv
                print("original user", arb)
            else:
                raise( ValueError, "cmdLine is not a sequence.")
        cmd = '"%s"' % (cmdLine[0],)
    
        params = " ".join(['"%s"' % (x,) for x in cmdLine[1:]])
        if len(arb) > 0:
            params += " "+arb
        cmdDir = ''
        if hidden:
            showCmd = win32con.SW_HIDE
        else:
            showCmd = win32con.SW_SHOWNORMAL
        lpVerb = 'runas'  # causes UAC elevation prompt.
    
        # print "Running", cmd, params
    
        # ShellExecute() doesn't seem to allow us to fetch the PID or handle
        # of the process, so we can't get anything useful from it. Therefore
        # the more complex ShellExecuteEx() must be used.
    
        # procHandle = win32api.ShellExecute(0, lpVerb, cmd, params, cmdDir, showCmd)
    
        procInfo = ShellExecuteEx(nShow=showCmd,
                                  fMask=64,
                                  lpVerb=lpVerb,
                                  lpFile=cmd,
                                  lpParameters=params)
    
        if wait:
            procHandle = procInfo['hProcess']    
            obj = win32event.WaitForSingleObject(procHandle, win32event.INFINITE)
            rc = win32process.GetExitCodeProcess(procHandle)
            #print "Process handle %s returned code %s" % (procHandle, rc)
        else:
            rc = procInfo['hProcess']
    
        return rc
    
    
    # xlogger : a logger in the server/nonprivileged script
    # tport : open port of communication, 0 for no comm [printf in nonprivileged window or silent]
    # redir : redirect stdout and stderr from privileged instance
    #errFile : redirect stderr to file from privileged instance
    def elevateme(xlogger=None, tport=6000, redir=True, errFile=False):
        global dbz
        if not isUserAdmin():
            print ("You're not an admin.", os.getpid(), "params: ", sys.argv)
    
            import getpass
            uname = getpass.getuser()
            
            if (tport> 0):
                address = ('localhost', tport)     # family is deduced to be 'AF_INET'
                listener = Listener(address, authkey=b'secret password')
            rc = runAsAdmin(uname, wait=False, hidden=True)
            if (tport> 0):
                hr = win32event.WaitForSingleObject(rc, 40)
                conn = listener.accept()
                print ('connection accepted from', listener.last_accepted)
                sys.stdout.flush()
                while True:
                    msg = conn.recv()
                    # do something with msg
                    if msg == 'close':
                        conn.close()
                        break
                    else:
                        if msg.startswith(dbz[0]+LOGTAG):
                            if xlogger != None:
                                xlogger.write(msg[len(LOGTAG):])
                            else:
                                print("Missing a logger")
                        else:
                            print(msg)
                        sys.stdout.flush()
                listener.close()
            else: #no port connection, its silent
                WaitForSingleObject(rc, INFINITE);
            return False
        else:
            #redirect prints stdout on  master, errors in error.txt
            print("HIADM")
            sys.stdout.flush()
            if (tport > 0) and (redir):
                vox= connFile(tport=tport)
                sys.stdout=vox
                if not errFile:
                    sys.stderr=vox
                else:
                    vfrs=open("errFile.txt","w")
                    sys.stderr=vfrs
                
                #print("HI ADMIN")
            return True
    
    
    def test():
        rc = 0
        if not isUserAdmin():
            print ("You're not an admin.", os.getpid(), "params: ", sys.argv)
            sys.stdout.flush()
            #rc = runAsAdmin(["c:\\Windows\\notepad.exe"])
            rc = runAsAdmin()
        else:
            print ("You are an admin!", os.getpid(), "params: ", sys.argv)
            rc = 0
        x = raw_input('Press Enter to exit.')
        return rc
        
    if __name__ == "__main__":
        sys.exit(test())
    
    0 讨论(0)
  • 2020-11-22 08:01

    Here is a solution with an stdout redirection:

    def elevate():
        import ctypes, win32com.shell.shell, win32event, win32process
        outpath = r'%s\%s.out' % (os.environ["TEMP"], os.path.basename(__file__))
        if ctypes.windll.shell32.IsUserAnAdmin():
            if os.path.isfile(outpath):
                sys.stderr = sys.stdout = open(outpath, 'w', 0)
            return
        with open(outpath, 'w+', 0) as outfile:
            hProc = win32com.shell.shell.ShellExecuteEx(lpFile=sys.executable, \
                lpVerb='runas', lpParameters=' '.join(sys.argv), fMask=64, nShow=0)['hProcess']
            while True:
                hr = win32event.WaitForSingleObject(hProc, 40)
                while True:
                    line = outfile.readline()
                    if not line: break
                    sys.stdout.write(line)
                if hr != 0x102: break
        os.remove(outpath)
        sys.stderr = ''
        sys.exit(win32process.GetExitCodeProcess(hProc))
    
    if __name__ == '__main__':
        elevate()
        main()
    
    0 讨论(0)
  • 2020-11-22 08:01

    Here is a solution which needed ctypes module only. Support pyinstaller wrapped program.

    #!python
    # coding: utf-8
    import sys
    import ctypes
    
    def run_as_admin(argv=None, debug=False):
        shell32 = ctypes.windll.shell32
        if argv is None and shell32.IsUserAnAdmin():
            return True
    
        if argv is None:
            argv = sys.argv
        if hasattr(sys, '_MEIPASS'):
            # Support pyinstaller wrapped program.
            arguments = map(unicode, argv[1:])
        else:
            arguments = map(unicode, argv)
        argument_line = u' '.join(arguments)
        executable = unicode(sys.executable)
        if debug:
            print 'Command line: ', executable, argument_line
        ret = shell32.ShellExecuteW(None, u"runas", executable, argument_line, None, 1)
        if int(ret) <= 32:
            return False
        return None
    
    
    if __name__ == '__main__':
        ret = run_as_admin()
        if ret is True:
            print 'I have admin privilege.'
            raw_input('Press ENTER to exit.')
        elif ret is None:
            print 'I am elevating to admin privilege.'
            raw_input('Press ENTER to exit.')
        else:
            print 'Error(ret=%d): cannot elevate privilege.' % (ret, )
    
    0 讨论(0)
  • 2020-11-22 08:03

    I found a very easy solution to this problem.

    1. Create a shortcut for python.exe
    2. Change the shortcut target into something like C:\xxx\...\python.exe your_script.py
    3. Click "advance..." in the property panel of the shortcut, and click the option "run as administrator"

    I'm not sure whether the spells of these options are right, since I'm using Chinese version of Windows.

    0 讨论(0)
  • 2020-11-22 08:05

    Make sure you have python in path,if not,win key + r, type in "%appdata%"(without the qotes) open local directory, then go to Programs directory ,open python and then select your python version directory. Click on file tab and select copy path and close file explorer.

    Then do win key + r again, type control and hit enter. search for environment variables. click on the result, you will get a window. In the bottom right corner click on environmental variables. In the system side find path, select it and click on edit. In the new window, click on new and paste the path in there. Click ok and then apply in the first window. Restart your PC. Then do win + r for the last time, type cmd and do ctrl + shift + enter. Grant the previliges and open file explorer, goto your script and copy its path. Go back into cmd , type in "python" and paste the path and hit enter. Done

    0 讨论(0)
  • 2020-11-22 08:07

    I can confirm that the solution by delphifirst works and is the easiest, simplest solution to the problem of running a python script with elevated privileges.

    I created a shortcut to the python executable (python.exe) and then modified the shortcut by adding my script's name after the call to python.exe. Next I checked "run as administrator" on the "compatibility tab" of the shortcut. When the shortcut is executed, you get a prompt asking permission to run the script as an administrator.

    My particular python application was an installer program. The program allows installing and uninstalling another python app. In my case I created two shortcuts, one named "appname install" and the other named "appname uninstall". The only difference between the two shortcuts is the argument following the python script name. In the installer version the argument is "install". In the uninstall version the argument is "uninstall". Code in the installer script evaluates the argument supplied and calls the appropriate function (install or uninstall) as needed.

    I hope my explanation helps others more quickly figure out how to run a python script with elevated privileges.

    0 讨论(0)
提交回复
热议问题