123

I want my Python script to copy files on Vista. When I run it from a normal cmd.exe window, no errors are generated, yet the files are NOT copied. If I run cmd.exe "as administator" and then run my script, it works fine.

This makes sense since User Account Control (UAC) normally prevents many file system actions.

Is there a way I can, from within a Python script, invoke a UAC elevation request (those dialogs that say something like "such and such app needs admin access, is this OK?")

If that's not possible, is there a way my script can at least detect that it is not elevated so it can fail gracefully?

Peter O.
  • 32,158
  • 14
  • 82
  • 96
jwfearn
  • 28,781
  • 28
  • 95
  • 122
  • 4
    http://stackoverflow.com/a/1445547/1628132 following this answer you create a .exe from the .py script using py2exe and using an flag called 'uac_info' it's pretty neat solution – foxcoreg Aug 30 '12 at 10:41

12 Answers12

152

As of 2017, an easy method to achieve this is the following:

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)

If you are using Python 2.x, then you should replace the last line for:

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

Also note that if you converted you python script into an executable file (using tools like py2exe, cx_freeze, pyinstaller) then you should use sys.argv[1:] instead of sys.argv in the fourth parameter.

Some of the advantages here are:

  • No external libraries required. It only uses ctypes and sys from standard library.
  • Works on both Python 2 and Python 3.
  • There is no need to modify the file resources nor creating a manifest file.
  • If you don't add code below if/else statement, the code won't ever be executed twice.
  • You can get the return value of the API call in the last line and take an action if it fails (code <= 32). Check possible return values here.
  • You can change the display method of the spawned process modifying the sixth parameter.

Documentation for the underlying ShellExecute call is here.

Martín De la Fuente
  • 6,155
  • 4
  • 27
  • 28
  • 10
    I had to use unicode instances as parameters for ShellExecuteW (like u'runas' and unicode(sys.executable)) to get this running. – Janosch Feb 15 '17 at 17:54
  • 7
    @Janosch, that's because you are using Python 2.x, while my code is in Python 3 (where all strings are treated as unicodes). But it is good to mention, thanks! – Martín De la Fuente Feb 16 '17 at 19:22
  • 2
    @Martin if I'm running this code from Windows command line like this: "python yourcode.py" it just opens python.exe. Is there a way fix it? – user2978216 Dec 15 '17 at 10:23
  • 2
    @user2978216 I had the same issue. In the line `ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, "", None, 1)` `sys.executable` resolves to only the python interpreter (e.g. `C:\Python27\Python.exe`) The solution is to add the running script as an argument (replacting `""`). `ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)` Also note, for this to work in python 2.x, all string arguments need to be unicode (i.e. `u"runas"`, `unicode(sys.executable)` and `unicode(__file__)`) – Javier Ubillos Dec 30 '17 at 23:41
  • 1
    What is the difference between ShellExecuteW and ShellExecute? – Hrvoje T Mar 11 '18 at 00:16
  • 2
    @HrvojeT Both, `ShellExecuteW` and `ShellExecuteA` are calls to the `ShellExecute` function in the Windows API. The former obligates the strings to be in unicode format and the latter is used with ANSI format – Martín De la Fuente Apr 09 '18 at 00:35
  • Thanks. Can you tell me why empty string is needed `""` when making executables with pyinstaller instead of `__file__`? – Hrvoje T Apr 10 '18 at 07:52
  • 1
    Making an executable file using py2exe or cx_freeze, the variable ```__file__``` doesn't exist. Instead of using an empty variable here, I'd replace ```__file__``` with ```sys.argv[0]``` – Orsiris de Jong Apr 10 '18 at 14:11
  • 1
    @HrvojeT When running a python script (.py), `sys.executable` resolves to `PYTHONPATH` (e.x `C:\Python34\python.exe`) so you need to pass the filename as a parameter. When running a script that was converted into an executable file, `sys.executable` resolves to the executable file itself, so no parameter is required. Also, as @deajan mentions `__file__` variable is not even defined in the last case so you shouldn’t use it. – Martín De la Fuente Apr 10 '18 at 14:36
  • Thanks. Why not using sys.argv[0] in script too, instead od __file__? – Hrvoje T Apr 10 '18 at 14:52
  • Please someone tell me why the try/except block is required. – Jakub Bláha Jul 25 '18 at 08:55
  • is it possible to parse exit status from this? – solstice333 Dec 11 '18 at 09:14
  • @solstice333 Yes, it is. As you can check in the documentation, `ShellExecuteW` returns an integer. If it is equal or above 32, then everything went ok, else you had an error. – Martín De la Fuente Dec 11 '18 at 21:27
  • @MartínDelaFuente ya I saw that and tried it. In my `ShellExecuteW`'ed process, I exit 1. And when I consume the return value in the parent process, it gives me back 42 when I was expecting less than or equal to 32. Forgot to mention that in my earlier comment. – solstice333 Dec 11 '18 at 23:37
  • How would you make this work when the running app is a python module ran with `python -m module` and not a script? – Appleshell Jan 05 '19 at 22:51
  • @MartínDelaFuente, unfortunately for me this does not work. Once it is coming to the line where you switch to run as admin it pop ups UAC and asks if I allow Python 3.7 to make changes on this PC. Any idea? – Beliaev Maksim Jul 20 '20 at 13:54
  • @BeliaevMaksim This is exactly what we are trying to achieve, pop up the UAC prompt (check the OP question) – Martín De la Fuente Jul 22 '20 at 15:41
  • 2
    Tried in 2020 and it still works like a charm – Shiv Dec 09 '20 at 03:52
  • Is there a way to do this when running the code in pycharm and not in a shell ? – Dr. Paprika Jan 10 '21 at 12:53
  • Thanks. It helped in my little problem which can be used now as a little example: https://gist.github.com/ohaval/3b1936cacc9ce4545f52aa5b8366e044 – Ohav Sep 08 '21 at 12:14
  • so UAC pops up, but doesn't seem to actually elevate permissions when using 3.10.1 -- or at least, i believe it isn't, since it gives me my written "no permissions" error when i run it in CMD without elevated permissions, and works fine when i run it in one with elevated permissions. – EarthToAccess Dec 10 '21 at 22:28
  • 1
    You generally do not want to join the command line like that. I'd offer an edit, but the queue is full. `subprocess` module contains a hidden internal tool `list2cmdline` to make the conversion to match how Microsoft C run time library parses it, as on Windows unfortunately the command line is parsed by the application. https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#parsing-c-command-line-arguments – Marko Kohtala Apr 04 '22 at 13:22
  • Also interesting: https://learn.microsoft.com/en-us/windows/win32/shell/launch – djvg Jun 28 '22 at 18:23
73

It took me a little while to get dguaraglia's answer working, so in the interest of saving others time, here's what I did to implement this idea:

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)
Jorenko
  • 2,594
  • 2
  • 21
  • 26
  • 1
    this just seems to elevate and then exit ... if i put in some print statements they dont get executed a second time – Joran Beasley Aug 13 '12 at 21:02
  • 6
    @JoranBeasley, you will not see any output. ShellExecuteEx doesn't post its STDOUT back to the originating shell. In that respect, debugging will be... challenging. But the privilege-hoisting trick definitely works. – Tim Keating Feb 19 '13 at 20:26
  • 1
    @TimKeating, ActiveState has a recipe that should make debugging a bit easier: [Use DebugView utility with standard python logging](http://code.activestate.com/recipes/577040-use-debugview-utility-with-standard-python-logging/) – samwyse Dec 17 '13 at 03:54
  • 1
    it does seem impossible to get the output in the same console, but with the argument nShow=5 to ShellExecuteEx, a new command window will open with the output from the elevated script. – Emil Styrke Feb 13 '14 at 07:00
  • It may be possible if child redirect its output to provided PIPE. Some links https://stackoverflow.com/questions/4093252/read-another-process-stdout-in-c – anatoly techtonik Jan 11 '15 at 10:40
  • 1
    Some suggested improvements: use `bool(windll.advpack.IsNTAdmin(0, None))` to detect if current it is admin (with a from ctypes import windll). Also, your `params` creation should also include proper quoting so that arguments and scripts with spaces work. Just quoting each one is a good start (`'"'+'" "'.join(...)+'"'`) but to be better you also need to detect embedded quotes within arguments. – coderforlife Aug 27 '15 at 22:29
  • 3
    For the quoting, you can use `subprocess.list2cmdline` to do it properly. – coderforlife Aug 27 '15 at 22:35
  • 1
    Thanks, I just needed to run a single subprocess as admin, so this worked perfectly: `import win32com.shell.shell as shell` `shell.ShellExecuteEx(lpVerb='runas', lpFile='net', lpParameters='start service', nShow=5)` `assert res['hInstApp'] > 32` [Microsoft win32-ShellExecuteEx Docs](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecuteexa) – KyleKing Aug 08 '19 at 12:56
  • I'm still struggling to understand how this magic works. Where I should place my code to make it work? Furthermore, the win32com.shell.shell as shell import doesn't work in Python 3.9. – Svetlozar Draganov Mar 24 '21 at 17:26
29

It seems there's no way to elevate the application privileges for a while for you to perform a particular task. Windows needs to know at the start of the program whether the application requires certain privileges, and will ask the user to confirm when the application performs any tasks that need those privileges. There are two ways to do this:

  1. Write a manifest file that tells Windows the application might require some privileges
  2. Run the application with elevated privileges from inside another program

This two articles explain in much more detail how this works.

What I'd do, if you don't want to write a nasty ctypes wrapper for the CreateElevatedProcess API, is use the ShellExecuteEx trick explained in the Code Project article (Pywin32 comes with a wrapper for ShellExecute). How? Something like this:

When your program starts, it checks if it has Administrator privileges, if it doesn't it runs itself using the ShellExecute trick and exits immediately, if it does, it performs the task at hand.

As you describe your program as a "script", I suppose that's enough for your needs.

Cheers.

dguaraglia
  • 5,774
  • 1
  • 26
  • 23
  • Thanks for those links, they were very useful for me finding out a lot about UAC stuff. – Colen Sep 10 '09 at 23:08
  • 4
    Something you might want to note on this is that you can do ShellExecute without PyWin32 (I had problems getting it installed) by using os.startfile($EXECUTABLE, "runas"). – Mike McQuaid Mar 08 '10 at 18:03
  • @Mike - but `runas` brings up a new prompt though. And startfile doesn't accept command line arguments to `$EXECUTABLE.` – Sridhar Ratnakumar Mar 29 '11 at 21:49
  • I added another answer with a full implementation of this technique that should be able to be added to the start of any python script. – Jorenko Jul 31 '12 at 18:10
  • The article to the second link was "Least Privilege: Teach Your Apps To Play Nicely With Windows Vista User Account Control" in "MSDN Magazine January 2007", but this issue is now only available as `.chm` file. – Peter Jan 10 '18 at 16:11
12

Just adding this answer in case others are directed here by Google Search as I was. I used the elevate module in my Python script and the script executed with Administrator Privileges in Windows 10.

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

Irving Moy
  • 331
  • 3
  • 5
  • Hey, I tried using `elevate` module and I'm getting "The file cannot be accessed by the system" error, any ideas why that would happen? – oz10 Mar 13 '20 at 22:21
  • @paxos1977 Can you post a code snippet that demonstrates that error? Thanks! – Irving Moy Mar 19 '20 at 10:23
  • A simple and neat solution - works great in Windows 10 (`Version 10.0.19043.1415`) – WoJ Dec 23 '21 at 13:25
7

The following example builds on MARTIN DE LA FUENTE SAAVEDRA's excellent work and accepted answer. In particular, two enumerations are introduced. The first allows for easy specification of how an elevated program is to be opened, and the second helps when errors need to be easily identified. Please note that if you want all command line arguments passed to the new process, sys.argv[0] should probably be replaced with a function call: 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()
Noctis Skytower
  • 21,433
  • 16
  • 79
  • 117
5

Recognizing this question was asked years ago, I think a more elegant solution is offered on github by frmdstryr using his module pywinutils:

Excerpt:

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    

This utilizes the COM interface and automatically indicates that admin privileges are needed with the familiar dialog prompt that you would see if you were copying into a directory where admin privileges are required and also provides the typical file progress dialog during the copy operation.

Joe
  • 6,758
  • 2
  • 26
  • 47
KenV99
  • 195
  • 2
  • 9
2

You can make a shortcut somewhere and as the target use: python yourscript.py then under properties and advanced select run as administrator.

When the user executes the shortcut it will ask them to elevate the application.

officialhopsof
  • 173
  • 2
  • 9
2

A variation on Jorenko's work above allows the elevated process to use the same console (but see my comment below):

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
Berwyn
  • 151
  • 7
  • 1
    Sorry. the same console option (SEE_MASK_NO_CONSOLE) only works if you're already elevated. My bad. – Berwyn Jan 13 '16 at 22:19
  • Indeed - you fundamentally cannot run an elevated process in the same console as a non-elevated one. Instead of using a comment to note this, I suggest updating your answer, which is still helpful for showing how to wait for the elevated process to exit and reporting its exit code. – mklement0 Jul 28 '22 at 16:56
  • By the way, I'd rather use a `--no-admin` option for when we don't need to restart the app as an administrator – Tigran Aug 03 '22 at 07:20
  • You could also set up [interprocess communication](https://stackoverflow.com/questions/6920858/interprocess-communication-in-python) and pass the port as an argument to the new process if you need to redirect the output to the parent process. – Tigran Aug 03 '22 at 07:25
2

This may not completely answer your question but you could also try using the Elevate Command Powertoy in order to run the script with elevated UAC privileges.

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

I think if you use it it would look like 'elevate python yourscript.py'

TinyGrasshopper
  • 2,190
  • 2
  • 17
  • 28
2

This is mostly an upgrade to Jorenko's answer, that allows to use parameters with spaces in Windows, but should also work fairly well on Linux :) Also, will work with cx_freeze or py2exe since we don't use __file__ but sys.argv[0] as executable

[EDIT] Disclaimer: The code in this post is outdated. I have published the elevation code as a python package. Install with pip install command_runner

Usage:

from command_runner.elevate import elevate

def main():
    """My main function that should be elevated"""
    print("Who's the administrator, now ?")

if __name__ == '__main__':
    elevate(main)

[/EDIT]

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:])
Orsiris de Jong
  • 2,819
  • 1
  • 26
  • 48
2

For one-liners, put the code to where you need UAC.

Request UAC, if failed, keep running:

import ctypes, sys

ctypes.windll.shell32.IsUserAnAdmin() or ctypes.windll.shell32.ShellExecuteW(
    None, "runas", sys.executable, " ".join(sys.argv), None, 1) > 32 and exit()


Request UAC, if failed, exit:

import ctypes, sys

ctypes.windll.shell32.IsUserAnAdmin() or (ctypes.windll.shell32.ShellExecuteW(
    None, "runas", sys.executable, " ".join(sys.argv), None, 1) > 32, exit())

Function style:

# Created by BaiJiFeiLong@gmail.com at 2022/6/24
import ctypes
import sys


def request_uac_or_skip():
    ctypes.windll.shell32.IsUserAnAdmin() or ctypes.windll.shell32.ShellExecuteW(
        None, "runas", sys.executable, " ".join(sys.argv), None, 1) > 32 and sys.exit()


def request_uac_or_exit():
    ctypes.windll.shell32.IsUserAnAdmin() or (ctypes.windll.shell32.ShellExecuteW(
        None, "runas", sys.executable, " ".join(sys.argv), None, 1) > 32, sys.exit())

BaiJiFeiLong
  • 3,716
  • 1
  • 30
  • 28
0

If your script always requires an Administrator's privileges then:

runas /user:Administrator "python your_script.py"
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • 18
    careful, elevation != running as administrator – Kugel Nov 28 '10 at 17:37
  • I am new to python...can you tell me where will i put that code? – Rahat Islam Khan Apr 03 '15 at 13:47
  • @RahatIslamKhan: Open a Command Prompt window and put it where: the command runs `your_script.py` as an Administrator user. Make sure you understand [@Kugel's comment](http://stackoverflow.com/questions/130763/request-uac-elevation-from-within-a-python-script/138970?noredirect=1#comment4664946_138970). – jfs Apr 03 '15 at 18:14
  • This only works with the specific built-in `Administrator` account - and that account is disabled by default for security reasons. – mklement0 Jul 28 '22 at 16:54