Ejecutar aplicación de Python al iniciar Windows

Ejecutar aplicación de Python al iniciar Windows

En Windows es común que muchos programas se inicien con el mismo sistema. Si tenemos una aplicación programada en Python (ya sea en código de fuente .py o empaquetada como un archivo .exe) y queremos que sea ejecutada al iniciarse Windows, podemos incluir en ella un código que le indique al sistema operativo que debe ser invocada cada vez que el ordenador es encendido. El procedimiento es sencillo. Se trata de crear una nueva entrada en el registro, en una ubicación determinada, en donde el sistema de Microsoft podrá leer la ruta de nuestro script o programa y correrlo al inicio.

El registro de Windows es una base de datos clave-valor (como un gran diccionario de Python) donde el sistema operativo y los diversos programas almacenan datos de configuración. Los desarrolladores pueden interactuar con el registro a través de las funciones de la API de Windows. En Python existen diversas alternativas para acceder a la API, principalmente el módulo estándar ctypes y el paquete pywin32. En este artículo utilizaremos pywin32, así que instalémoslo vía pip:

py -m pip install pywin32

Asegurémonos de que se haya instalado correctamente procurando no obtener ningún error en la consola interactiva al ejecutar:

>>> import win32api

Una vez instalado, crearemos tres funciones para lograr nuestro cometido. Las funciones run_at_startup_set() y run_script_at_startup_set() serán las encargadas de crear una nueva entrada en el registro para dejar asentado que nuestra aplicación o script de Python debe iniciarse junto al sistema operativo. La utilización de una u otra dependerá de si distribuimos nuestra aplicación como un archivo ejecutable o como código de fuente, respectivamente. La función run_at_startup_remove(), inversamente, permitirá remover la entrada del registro.

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


# Ruta del registro donde se almacenan las aplicaciones que deben
# iniciarse con el sistema.
STARTUP_KEY_PATH = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"


def run_at_startup_set(appname, path=None, user=False):
    """
    Establecer la entrada en el registro para iniciar la aplicación
    con el sistema.
    """
    # Abrir la clave donde se almacenan las aplicaciones
    # que deben iniciarse con el sistema.
    key = RegOpenKeyEx(
        HKEY_CURRENT_USER if user else HKEY_LOCAL_MACHINE,
        STARTUP_KEY_PATH,
        0,
        KEY_WRITE | KEY_QUERY_VALUE
    )
    # Chequear que nuestra aplicación no se encuentra ya
    # en el registro.
    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
    # Crear una nueva entrada.
    RegSetValueEx(key, appname, 0, REG_SZ, path or GetModuleFileName(0))
    # Cerrar la clave.
    RegCloseKey(key)


def run_script_at_startup_set(appname, user=False):
    """
    Como run_at_startup_set(), pero para aplicaciones distribuidas
    como archivos de código de fuente (.py).
    """
    run_at_startup_set(
        appname,
        # Establecer la ruta del intérprete (obtenida vía GetModuleFileName())
        # seguida de la ruta del actual archivo de Python (__file__).
        '{} "{}"'.format(GetModuleFileName(0), __file__),
        user
    )


def run_at_startup_remove(appname, user=False):
    """
    Eliminar el valor del registro para la aplicación pasada
    como argumento.
    """
    key = RegOpenKeyEx(
        HKEY_CURRENT_USER if user else HKEY_LOCAL_MACHINE,
        STARTUP_KEY_PATH,
        0,
        KEY_WRITE
    )
    RegDeleteValue(key, appname)
    RegCloseKey(key)

Todas las funciones incluyen un argumento user que es falso por defecto. Cuando user=False, estas funciones escriben en una clave del registro que es común para todos los usuarios, por lo cual requiere permisos de administrador. Si deseamos configurar el inicio de la aplicación o del script solo para el usuario actual (el que está corriendo el código), entonces indicamos user=True y las funciones no requieren permisos elevados.

Si distribuimos nuestra aplicación como un ejecutable (por ejemplo, miapp.exe), coloquemos una llamada a la primera función en el inicio de nuestro código así:

# Para aplicaciones distribuidas como ejecutables.
run_at_startup_set("Mi aplicación")

# Para aplicaciones distribuidas como ejecutables.
run_at_startup_set("Mi aplicación", user=True)

El primer argumento constiuye el nombre de la clave en el registro, por lo cual hay que asegurarse de que sea un nombre único. No hay problema en que el código se ejecute cada vez que el usuario abre nuestro programa: la función se encargará de chequear que la clave no exista antes de agregarla al registro; y si la clave ya existe, simplemente no hará nada.

Podemos comprobar que la operación ha sido satisfactoria buscando la ruta (almacenada en STARTUP_KEY_PATH) en el editor del registro de Windows (regedit.exe):

Registro de Windows

Si queremos utilizar la función para configurar el inicio de una aplicación distinta a aquella que ejecuta el código, puede especificarse como segundo argumento la ruta completa del programa:

# Para otras aplicaciones distribuidas como ejecutables.
run_at_startup_set("Otra aplicación", r"C:\ruta\a\otra\aplicacion.exe")

# Para otras aplicaciones distribuidas como ejecutables.
run_at_startup_set(
    "Otra aplicación",
    r"C:\ruta\a\otra\aplicacion.exe",
    user=True
)

Si distribuimos nuestra aplicación como código de fuente (por ejemplo, miapp.py), luego debemos hacer una llamada como esta:

# Para aplicaciones distribuidas como código de fuente.
run_script_at_startup_set("Mi aplicación")

# Para aplicaciones distribuidas como código de fuente.
run_script_at_startup_set("Mi aplicación", user=True)

Nótese cómo en este caso el valor del registro incluye la ruta del intérpete de Python que se utilizó para invocar el código:

Registro de Windows

Por último, si en algún momento queremos limpiar la entrada del registro vía código, hacemos:

# Quitar la aplicación del inicio de Windows.
run_at_startup_remove("Mi aplicación")

# Quitar la aplicación del inicio de Windows.
run_at_startup_remove("Mi aplicación", user=True)



Deja una respuesta