Run Python Application at Startup on Windows

On Windows, it is something common for a program to run when the system starts. If we have an application written in Python (either as .py source code or packed as an executable .exe file) and we want it to be executed when Windows starts, we can include in it a code to let the operating system know that it must be invoked whenever the computer is turned on. The procedure is simple. We create a new entry inside the registry, in a specific location where Microsoft's system will be able to read our program's path and run it on startup.

The Windows Registry is a key-value database (like a big Python dictionary) where the operating system and many programs store configuration data. Developers are able to interact with the registry using functions from the Windows API. In Python there are some alternatives for accessing the API, mainly the ctypes standard module and the pywin32 package. In this post we'll use pywin32, so let's install it via pip:

py -m pip install pywin32

And make sure that the package was installed successfully by checking there is no exception when running the following in the Python interactive shell:

>>> import win32api

Once installed, we'll create three functions to achieve our goal. The run_at_startup_set() and run_script_at_startup_set() functions will be responsible for creating a new entry in the registry to let the system know our Python script or application must start when the operating system does. (The former should be used when delivering our application as an executable file, the later when delivering as source code.) Inversely, the run_at_startup_remove() function will let us remove the entry from the registry.

from win32api import (GetModuleFileName, RegCloseKey, RegDeleteValue,
                    RegOpenKeyEx, RegSetValueEx, RegEnumValue)
from win32con import (HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER, KEY_WRITE,
                    KEY_QUERY_VALUE, REG_SZ)
from winerror import ERROR_NO_MORE_ITEMS
import pywintypes
# The registry path where the applications that must run at startup
# are stored.
STARTUP_KEY_PATH = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"
def run_at_startup_set(appname, path=None, user=False):
    """
    Store the entry in the registry for running the application
    at startup.
    """
    # Open the registry key where applications that run
    # at startup are stored.
    key = RegOpenKeyEx(
        HKEY_CURRENT_USER if user else HKEY_LOCAL_MACHINE,
        STARTUP_KEY_PATH,
        0,
        KEY_WRITE | KEY_QUERY_VALUE
    )
    # Make sure our application is not already in the registry.
    i = 0
    while True:
        try:
            name, _, _ = RegEnumValue(key, i)
        except pywintypes.error as e:
            if e.winerror == ERROR_NO_MORE_ITEMS:
                break
            else:
                raise
        if name == appname:
            RegCloseKey(key)
            return
        i += 1
    # Create a new entry or key.
    RegSetValueEx(key, appname, 0, REG_SZ, path or GetModuleFileName(0))
    # Close the key when no longer used.
    RegCloseKey(key)
def run_script_at_startup_set(appname, user=False):
    """
    Like run_at_startup_set(), but for applications released as
    source code files (.py).
    """
    run_at_startup_set(
        appname,
        # Set the interpreter path (returned by GetModuleFileName())
        # followed by the path of the current Python file (__file__).
        '{} "{}"'.format(GetModuleFileName(0), __file__),
        user
    )
def run_at_startup_remove(appname, user=False):
    """
    Remove the registry application passed in the first param.
    """
    key = RegOpenKeyEx(
        HKEY_CURRENT_USER if user else HKEY_LOCAL_MACHINE,
        STARTUP_KEY_PATH,
        0,
        KEY_WRITE
    )
    RegDeleteValue(key, appname)
    RegCloseKey(key)

Each function includes a user argument that is False by default. When user=True, these functions write in a registry key that is common to all users, in which case the code requires admin rights. If we just want to configure our application or script to run at startup for the current user (the one that executes the code), then we use user=True and thus the functions do not require elevation.

If our application is released as an executable file (e. g. myapp.exe), let's call the first function at the beginning of our code like this:

# For all users (requires elevation).
run_at_startup_set("My Application")
# For the current user only (no elevation required.)
run_at_startup_set("My Application", user=True)

The first argument will be the name of the key in the registry, so make sure it is a unique name. There is no risk in executing the code every time our program is started since the function will check that the key is not already there before writing anything. If the key already exists, nothing happens.

We can check whether the operation succeeded by looking up the path (stored in STARTUP_KEY_PATH) using the Windows Registry editor (regedit.exe):

/images/run-python-application-at-startup-on-windows/windows-registry-python.png

If we want to use the function to set up the startup of another application, its path can be specified as the second argument:

# For all users (requires elevation).
run_at_startup_set("Other Application", r"C:\path\to\other\application.exe")
# For the current user only (no elevation required.)
run_at_startup_set(
    "Other Application",
    r"C:\path\to\other\application.exe",
    user=True
)

If our application is delivered as a source code file (such as myapp.py), then we must make a call like the following:

# For all users (requires elevation).
run_script_at_startup_set("My Application")
# For the current user only (no elevation required.)
run_script_at_startup_set("My Application", user=True)

Note how in this case the value in the registry includes the path of the interpreter which called the code above:

/images/run-python-application-at-startup-on-windows/windows-registry-python-source-code.png

Finally, if we want to clear the registry programmatically, so our application or script no longer starts with the system, we do:

# For all users (requires elevation).
run_at_startup_remove("My Application")
# For the current user only (no elevation required.)
run_at_startup_remove("My Application", user=True)