Source code for stouputils.installer.windows
""" Installer module for Windows specific functions.
Provides Windows specific implementations for checking administrator privileges,
determining appropriate installation paths (global/local), and modifying
the user's PATH environment variable.
"""
# Imports
import os
from ..decorators import LogLevels, handle_error
from ..io import clean_path
from ..print import debug, info, warning
from .common import ask_install_type, prompt_for_path
# Functions
[docs]
@handle_error(message="Failed to add to PATH (Windows)", error_log=LogLevels.WARNING_TRACEBACK)
def add_to_path_windows(install_path: str) -> bool | None:
""" Add install_path to the User PATH environment variable on Windows.
Args:
install_path (str): The path to add to the User PATH environment variable.
Returns:
bool | None: True if the path was added to the User PATH environment variable, None otherwise.
"""
# Convert install_path to a Windows path if it's not already
install_path = install_path.replace("/", "\\")
os.makedirs(install_path, exist_ok=True)
# Get current user PATH
import winreg
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment", 0, winreg.KEY_READ | winreg.KEY_WRITE) as key:
# Get the number of values in the registry key
num_values = winreg.QueryInfoKey(key)[1]
# Find the index of the 'Path' value
path_index = -1
for i in range(num_values):
if winreg.EnumValue(key, i)[0] == 'Path':
path_index = i
break
# Get the current path value
current_path: str = winreg.EnumValue(key, path_index)[1]
# Check if path is already present
if install_path not in current_path.split(';'):
new_path: str = f"{current_path};{install_path}"
winreg.SetValueEx(key, "Path", 0, winreg.REG_EXPAND_SZ, new_path)
debug(f"Added '{install_path}' to user PATH. Please restart your terminal for changes to take effect.")
else:
debug(f"'{install_path}' is already in user PATH.")
return True
[docs]
def check_admin_windows() -> bool:
""" Check if the script is running with administrator privileges on Windows. """
try:
import ctypes
return ctypes.windll.shell32.IsUserAnAdmin() != 0
except Exception:
return False
[docs]
@handle_error(message="Failed to get installation path (Windows)", error_log=LogLevels.ERROR_TRACEBACK)
def get_install_path_windows(
program_name: str,
ask_global: int = 0,
add_path: bool = True,
append_to_path: str = "",
default_global: str = os.environ.get("ProgramFiles", "C:\\Program Files")
) -> str:
""" Get the installation path for the program
Args:
program_name (str): The name of the program to install.
ask_global (int): 0 = ask for anything, 1 = install globally, 2 = install locally
add_path (bool): Whether to add the program to the PATH environment variable. (Only if installed globally)
append_to_path (str): String to append to the installation path when adding to PATH.
(ex: "bin" if executables are in the bin folder)
default_global (str): The default global installation path.
(Default is "C:\\Program Files" which is the most common location for executables on Windows)
Returns:
str: The installation path.
"""
# Default path is located in the current working directory
default_local_path: str = clean_path(os.path.join(os.getcwd(), program_name))
# Define default global path (used in prompt even if not chosen initially)
default_global_path: str = clean_path(os.path.join(default_global, program_name))
# Ask user for installation type (global/local)
install_type: str = ask_install_type(ask_global, default_local_path, default_global_path)
# If the user wants to install globally,
if install_type == 'g':
# Check if the user has admin privileges,
if not check_admin_windows():
# If the user doesn't have admin privileges, fallback to local
warning(
f"Global installation requires administrator privileges. Please re-run as administrator.\n"
f"Install locally instead to '{default_local_path}'? (Y/n): "
)
if input().lower() == 'n':
info("Installation cancelled.")
return ""
else:
# Fallback to local path if user agrees
return prompt_for_path(
f"Falling back to local installation path: {default_local_path}.",
default_local_path
)
# If the user has admin privileges,
else:
# Ask it user wants to override the default global install path
install_path: str = prompt_for_path(
f"Default global installation path is {default_global_path}.",
default_global_path
)
if add_path:
add_to_path_windows(os.path.join(install_path, append_to_path))
return install_path
# Local install
else: # install_type == 'l'
return prompt_for_path(
f"Default local installation path is {default_local_path}.",
default_local_path
)