Habitualmente nos encontramos con la situación en la que debemos ejecutar ciertos programas o flujos de ejecución y necesitamos permisos de Administrador. Estas situaciones se nos presentan cuando tenemos que manejar carpetas y ficheros en acciones que impliquen lectura, escritura o ejecución en directorios protegidos del sistema. Para ello se requiere una elevación de permisos que deberemos comprobar si existe antes de lanzar nuestra ejecución.
Aprovechando que seguramente tengamos que hacer esto varias veces, creo que la mejor opción de hacerlo es con el mejor lenguaje pegamento que existe hasta la fecha, que no es ni más ni menos que Python. Por ello la solución final que vamos a plantear es excelente tanto para administradores de sistemas como para desarrolladores, siendo además multiplataforma GNU/Linux y Windows.
Tenemos que tener en cuenta que GNU/Linux y Windows no funcionan igual para la gestión de permisos. En Windows preguntaremos desde la API disponible si el usuario es administrador, mientras que en GNU/Linux preguntaremos por si el usuario tiene permisos root.
import ctypes, os
from sys import exit
def is_admin():
is_admin = False
try:
is_admin = os.getuid() == 0
except AttributeError:
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
print ("Admin privileges: {}".format(is_admin))
return is_admin
Para mirar los permisos en Windows usaremos la librería ctypes
y para los permisos en GNU/Linux usaremos la librería os
. Ambas están incluida en Python. Por una razón arbitraría se ha elegido comprobar primero GNU/Linux y después Windows.
Cuando se comprueba en GNU/Linux invocamos a la función os.getuid
que nos devolverá 0 en el caso de ser root. Si llamamos a la función desde Windows con Python nos elevará una excepción del tipo AttributeError
, que aprovecharemos a capturar para hacer la comprobación en Windows invocando a la función ctypes.windll.shell32.IsUserAnAdmin
. En ambos casos guardaremos el booleano de la evaluación y lo devolveremos al final de la función.
Hasta aquí sencillo, pero esto es código Python y es necesario tener el interprete de Python, con el que obliga al sistema a tener instalado el intérprete de Python. Para solucionarlo, deberíamos convertir nuestro código en un programa añadiendo un punto de entrada y devolviendo un código de retorno tras la ejecución.
import ctypes, os
from sys import exit
def is_admin():
is_admin = False
is_win = False
try:
is_admin = os.getuid() == 0
except AttributeError:
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
is_win = True
print ("Admin privileges: {}".format(is_admin))
return is_admin, is_win
Antes modificaremos un poco la función is_admin()
añadiendo un valor is_win
que usaremos para saber que estamos comprobando en GNU/Linux o Windows los permisos de administrador, devolviendo el valor como un segundo valor en el retorno de la función.
Recordad que Python es posible recoger múltiples valores de retorno en una variable como una tupla, en variables separadas o incluso tirar los valores que no nos interesen.
def my_function():
a = 2
b = 3
return a, b
a = my_function()
print(a) # out: (2, 3)
a, b = my_function()
print(a) # out: 2
print(b) # out: 3
a, _ = my_function() # b es ignorado
print(a) # out: 2
Además añadimos un punto de entrada debajo de la función is_admin()
.
if __name__ == "__main__":
is_admin, is_win = is_admin()
# Converting boolean to integer
ret = int(is_admin == False)
if is_win:
exit(ret)
else:
exit(ret * -1)
Comprobar si __name__
es __main__
, es una excelente forma de hacer que cada unidad de fichero Python sea ejecutable si se quiere. El nombre de __main__
sólo es dado al fichero Python que es dado al intérprete para ser ejecutado y en caso de no serlo, siempre puede ser importado en otro fichero Python, sin que el punto de entrada afecte.
Como vemos, ahora estamos recogiendo en el retorno is_admin
e is_win
. Necesitamos el segundo valor para decidir el valor de retorno, ya que los códigos de error en Windows son enteros positivos y en GNU/Linux son enteros negativos. El único punto en común es que devolver un 0 es señal de que la ejecución fue correcta. Sabiendo esto debemos adaptar el retorno de los errores dependiendo el sistema.
💾 Binarizando nuestra aplicación Python
Para convertirlo en un ejecutable usaremos PyInstaller, el cual podéis instalar con un simple pip install pyinstaller
. Para binarizar nuestra aplicación en un ejecutable autocontenido con el interprete de Python ejecutaremos:
pyinstaller -F is_admin.py
De esta forma resultará, mucho mas sencilla la distribución de nuestra aplicación.
👔 Puesta en Producción
Ahora vamos a plantear los dos casos de uso para Windows y GNU/Linux. Como aclaración, decir que los ejemplos expuestos se pueden usar tanto con la versión binarizada o con la de código invocando al intérprete de Python.
Windows
@echo off
cd
cd /D "%~dp0"
cd
#REM python is_admin.py
is_admin.exe
if errorlevel 1 (
echo Exit Code is %errorlevel%
echo.
echo Admin privileges required
echo Run it again as Administrator
echo.
pause
exit /b %errorlevel%
)
#REM [your_executable].exe
pause
👀 Puedes descargar la versión binarizada de is_admin.exe
GNU/Linux
#!/bin/bash
pwd
cd `dirname $0`
SCRIPTDIR=`pwd`
pwd
#python is_admin.py
is_admin
if [ $? -eq 0 ]
then
# [your_executable]
else
echo Exit Code is $?
echo.
echo Admin privileges required
echo Run it again as Administrator
echo.
read -rsp $'Press any key to continue...\n' -n 1 key
exit $?
fi
read -rsp $'Press any key to continue...\n' -n 1 key
Si no estás tan familiarizado con el Bash te invito a que visites el proyecto ExplainShell para que puedas obtener la explicación de los diferentes comandos y argumentos.
🥅 Conclusiones
La solución presentada, sin ser seguramente perfecta, asegura tanto a administradores de sistemas como desarrolladores, comprobar si existen los permisos de administrador necesarios de manera sencilla; pudiendo hacerlo con una versión binarizada autocontenida que incluya el intérpretete Python, ejecutándolo usando el intérprete de Python del sistema gracias a ser todo dependencias internas, o llamando directamente a la función is_admin()
si se quiere integrar en un desarrollo.
El código completo puedes encontrarlo en el siguiente gist de GitHub:
Espero que esta forma de trabajar te resulte útil y puedas extrapolarla y usar para otros casos Python como tu lenguaje pegamento generando código y aplicaciones altamente reutilizables.