commit 4ee80159539c0e0fe91a5eccfed817ba0f9c53a3 Author: Miguel Date: Thu Apr 3 09:05:09 2025 +0200 Primera Version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a19790 --- /dev/null +++ b/.gitignore @@ -0,0 +1,174 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc diff --git a/chatgtp.py b/chatgtp.py new file mode 100644 index 0000000..5439179 --- /dev/null +++ b/chatgtp.py @@ -0,0 +1,22 @@ +import json + +IP_HISTORY_FILE = "tu_ruta_a_ip_history.json" # Asegúrate de definir la ruta correcta + +def remove_duplicates_preserve_order(history): + """Elimina duplicados de la lista manteniendo el orden.""" + seen = set() + new_history = [] + for item in history: + if item not in seen: + seen.add(item) + new_history.append(item) + return new_history + +if os.path.exists(IP_HISTORY_FILE): + with open(IP_HISTORY_FILE, "r") as file: + ip_history = json.load(file) + ip_history = remove_duplicates_preserve_order(ip_history) + + # Guardar la lista actualizada en el archivo JSON + with open(IP_HISTORY_FILE, "w") as file: + json.dump(ip_history, file) diff --git a/ip-changer-admin.py b/ip-changer-admin.py new file mode 100644 index 0000000..c85b3a7 --- /dev/null +++ b/ip-changer-admin.py @@ -0,0 +1,106 @@ +import sys +import subprocess +import ctypes +import time +import logging +import os +from datetime import datetime + +def setup_logging(): + """Configura el logging para el script""" + log_dir = os.path.join(os.path.dirname(__file__), 'logs') + if not os.path.exists(log_dir): + os.makedirs(log_dir) + + log_file = os.path.join(log_dir, 'ip_changer_admin.log') + + logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(log_file), + logging.StreamHandler(sys.stdout) + ] + ) + return logging.getLogger(__name__) + +def is_admin(): + """Verifica si el script se está ejecutando con privilegios de administrador""" + try: + return ctypes.windll.shell32.IsUserAnAdmin() + except: + return False + +def run_command(logger, cmd: str) -> bool: + """Ejecuta un comando y retorna True si fue exitoso""" + logger.info(f"Executing command: {cmd}") + try: + result = subprocess.run( + cmd, + shell=True, + capture_output=True, + text=True + ) + + if result.stdout: + logger.info(f"Command output: {result.stdout}") + if result.stderr: + logger.error(f"Command error: {result.stderr}") + + return result.returncode == 0 + + except Exception as e: + logger.error(f"Error executing command: {str(e)}") + return False + +def main(): + logger = setup_logging() + + # Log startup information + logger.info("=" * 50) + logger.info(f"Script started at {datetime.now()}") + logger.info(f"Arguments received: {sys.argv}") + logger.info(f"Running with admin privileges: {is_admin()}") + + if not is_admin(): + logger.error("This script requires administrator privileges") + return 1 + + if len(sys.argv) < 3: + logger.error("Insufficient arguments") + logger.info("Usage: ip_changer_admin.py ") + logger.info("Example for static IP: ip_changer_admin.py Ethernet 192.168.1.100") + logger.info("Example for DHCP: ip_changer_admin.py Ethernet dhcp") + return 1 + + interface = sys.argv[1] + ip_mode = sys.argv[2] + + logger.info(f"Processing request for interface: {interface}") + logger.info(f"IP mode: {ip_mode}") + + if ip_mode.lower() == "dhcp": + cmd = f'netsh interface ip set address "{interface}" dhcp' + else: + # Asumimos que es una IP estática + ip = ip_mode + gateway = '.'.join(ip.split('.')[:3] + ['1']) + cmd = f'netsh interface ip set address "{interface}" static {ip} 255.255.255.0 {gateway}' + + success = run_command(logger, cmd) + + if success: + logger.info("Command executed successfully") + return 0 + else: + logger.error("Command failed") + return 1 + +if __name__ == "__main__": + try: + exit_code = main() + sys.exit(exit_code) + except Exception as e: + logger = setup_logging() + logger.exception("Unhandled exception occurred") + sys.exit(1) diff --git a/ip_config.json b/ip_config.json new file mode 100644 index 0000000..0b2b7a5 --- /dev/null +++ b/ip_config.json @@ -0,0 +1 @@ +{"last_interface": "Ethernet", "last_ip_prefix": "10.1.33"} \ No newline at end of file diff --git a/ip_history.json b/ip_history.json new file mode 100644 index 0000000..b3c3d9b --- /dev/null +++ b/ip_history.json @@ -0,0 +1 @@ +["10.1.22", "169.254.38", "10.1.20", "10.146.76", "192.168.0", "10.101.8", "10.1.33", "10.1.92", "192.168.121", "192.168.1", "169.254.3", "10.202.4", "192.168.2", "10.1.30", "192.168.88"] \ No newline at end of file diff --git a/ipchange - copia.py b/ipchange - copia.py new file mode 100644 index 0000000..89ad786 --- /dev/null +++ b/ipchange - copia.py @@ -0,0 +1,192 @@ +import ctypes +import subprocess +import sys +import re +import os +import json +import time +from win10toast import ToastNotifier + +# Definir una constante para la ruta del directorio +DIR = "D:\\Proyectos\\Scripts\\IPChanger" +IP_HISTORY_FILE = os.path.join(DIR, "ip_history.json") + + +def is_admin(): + """Verifica si el script se está ejecutando con privilegios de administrador.""" + try: + return ctypes.windll.shell32.IsUserAnAdmin() + except: + return False + + +def run_as_admin(argv=None, debug=False): + """Ejecuta el script como administrador.""" + shell32 = ctypes.windll.shell32 + if argv is None and shell32.IsUserAnAdmin(): + # Ya estamos ejecutando como administrador, no es necesario hacer nada + return True + if argv is None: + argv = sys.argv + if hasattr(sys, "_MEIPASS"): + # Soporte para PyInstaller + arguments = map(str, argv[1:]) + else: + arguments = map(str, argv) + argument_line = " ".join(arguments) + executable = str(sys.executable) + if debug: + print(f"Command line: {executable} {argument_line}") + ret = shell32.ShellExecuteW(None, "runas", executable, argument_line, None, 1) + if int(ret) <= 32: + return False + return None + + +def set_static_ip(ip_address, gateway): + """Establece una dirección IP estática para el adaptador Ethernet.""" + try: + subprocess.run( + f'netsh interface ip set address "Ethernet" static {ip_address} 255.255.255.0 {gateway}', + shell=True, + ) + return f"Dirección IP establecida en {ip_address}" + except Exception as e: + return f"Error al establecer la dirección IP: {e}" + + +def set_dhcp(): + """Configura el adaptador Ethernet para obtener la dirección IP automáticamente a través de DHCP.""" + try: + subprocess.run('netsh interface ip set address "Ethernet" dhcp', shell=True) + return "Adaptador Ethernet configurado para DHCP" + except Exception as e: + return f"Error al configurar DHCP: {e}" + + +def save_current_ip(): + """Guarda la dirección IP actual del adaptador Ethernet en un archivo.""" + try: + ip_config_output = subprocess.check_output("ipconfig", shell=True).decode( + "cp850" + ) + current_ip = re.search( + "Adaptador de Ethernet Ethernet:[\s\S]*?Dirección IPv4. . . . . . . . . . . . . . : (\d+\.\d+\.\d+\.\d+)", + ip_config_output, + ) + if current_ip: + update_ip_history(current_ip.group(1)) + return f"Dirección IP actual guardada: {current_ip.group(1)}" + else: + return "No se encontró la dirección IP actual." + except Exception as e: + return f"Error al guardar la dirección IP actual: {e}" + + +def update_ip_history(current_ip): + """Actualiza el historial de las últimas 10 IPs usadas, excluyendo 'auto'.""" + if current_ip.lower() == "auto": + return + + if os.path.exists(IP_HISTORY_FILE): + with open(IP_HISTORY_FILE, "r") as file: + ip_history = json.load(file) + else: + ip_history = [] + + ip_history.insert(0, current_ip) + ip_history = ip_history[:10] + + with open("ip_history.json", "w") as file: + json.dump(ip_history, file) + + +def restore_last_ip(): + """Restaura la última dirección IP guardada para el adaptador Ethernet.""" + if os.path.exists(IP_HISTORY_FILE): + with open(IP_HISTORY_FILE, "r") as file: + ip_history = json.load(file) + if ip_history: + last_ip = ip_history[0] # Obtener la última IP del historial + gateway = last_ip.rsplit(".", 1)[0] + ".1" + return set_static_ip(last_ip, gateway) + else: + return "No hay direcciones IP guardadas en el historial." + else: + return "No se encontró el archivo de historial de IPs." + + +def get_input_parameter(): + """Solicita al usuario un parámetro por línea de comandos.""" + return input( + "Ingrese la dirección IP, 'auto' para DHCP, o 'last' para la última IP: " + ) + + +def display_ip_history(ip_history): + """Muestra el historial de las últimas 10 IPs y permite al usuario seleccionar una.""" + if ip_history: + for i, ip in enumerate(ip_history): + print(f"{i}: {ip}") + choice = input( + "Seleccione una IP del historial (0-9), o presione Enter para continuar: " + ) + if choice.isdigit() and 0 <= int(choice) < len(ip_history): + return ip_history[int(choice)] + return None + + +def main(): + if not is_admin(): + # Si no se está ejecutando como administrador, solicitar elevación + result = run_as_admin() + if result is True: + print("Ejecutando como administrador.") + elif result is False: + print("No se pudo obtener privilegios de administrador.") + return + else: + # El script se reinició con privilegios de administrador + return + + if os.path.exists(IP_HISTORY_FILE): + with open(IP_HISTORY_FILE, "r") as file: + ip_history = json.load(file) + + argument = sys.argv[1] if len(sys.argv) > 1 else None + + if not argument: + chosen_ip = display_ip_history() + if chosen_ip: + argument = chosen_ip + else: + argument = get_input_parameter() + + # Guardar la dirección IP actual solo si no es 'auto' + if argument.lower() != "auto": + message = save_current_ip() + + if argument == "auto": + message = set_dhcp() + elif argument == "last": + message = restore_last_ip() + else: + ip_parts = argument.split(".") + if len(ip_parts) == 3: + argument += ".249" + gateway = ".".join(ip_parts[:3]) + ".1" + message = set_static_ip(argument, gateway) + update_ip_history(argument) # Actualizar el historial solo con IPs estáticas + + # Aquí asumimos que 'message' contiene el mensaje que quieres mostrar + try: + toaster = ToastNotifier() + toaster.show_toast("Cambio de IP", message, duration=10) + except Exception as e: + print( + f"Se produjo un error al mostrar la notificación, pero la operación se completó con éxito: {e}" + ) + + +if __name__ == "__main__": + main() diff --git a/ipchange.py b/ipchange.py new file mode 100644 index 0000000..017096a --- /dev/null +++ b/ipchange.py @@ -0,0 +1,203 @@ +import ctypes +import subprocess +import sys +import re +import os +import json +import time +from win10toast import ToastNotifier +import threading + +# Definir una constante para la ruta del directorio +DIR = "D:\\Proyectos\\Scripts\\IPChanger" +IP_HISTORY_FILE = os.path.join(DIR, "ip_history.json") + + +def is_admin(): + """Verifica si el script se está ejecutando con privilegios de administrador.""" + try: + return ctypes.windll.shell32.IsUserAnAdmin() + except: + return False + + +def run_as_admin(argv=None, debug=False): + """Ejecuta el script como administrador.""" + shell32 = ctypes.windll.shell32 + if argv is None and shell32.IsUserAnAdmin(): + # Ya estamos ejecutando como administrador, no es necesario hacer nada + return True + if argv is None: + argv = sys.argv + if hasattr(sys, "_MEIPASS"): + # Soporte para PyInstaller + arguments = map(str, argv[1:]) + else: + arguments = map(str, argv) + argument_line = " ".join(arguments) + executable = str(sys.executable) + if debug: + print(f"Command line: {executable} {argument_line}") + ret = shell32.ShellExecuteW(None, "runas", executable, argument_line, None, 1) + if int(ret) <= 32: + return False + return None + + +def set_static_ip(ip_address, gateway): + """Establece una dirección IP estática para el adaptador Ethernet.""" + try: + subprocess.run( + f'netsh interface ip set address "Ethernet" static {ip_address} 255.255.255.0 {gateway}', + shell=True, + ) + return f"Dirección IP establecida en {ip_address}" + except Exception as e: + return f"Error al establecer la dirección IP: {e}" + + +def set_dhcp(): + """Configura el adaptador Ethernet para obtener la dirección IP automáticamente a través de DHCP.""" + try: + subprocess.run('netsh interface ip set address "Ethernet" dhcp', shell=True) + return "Adaptador Ethernet configurado para DHCP" + except Exception as e: + return f"Error al configurar DHCP: {e}" + + +def save_current_ip(ip_history): + """Guarda la dirección IP actual del adaptador Ethernet en un archivo y detecta si es de DHCP.""" + try: + ip_config_output = subprocess.check_output("ipconfig /all", shell=True).decode( + "cp850" + ) + + # Buscar la sección del adaptador de Ethernet y DHCP + ethernet_config = re.search( + "Adaptador de Ethernet Ethernet:[\s\S]*?(DHCP habilitado[\s\S]*?)\r\n\r\n", + ip_config_output, + ) + current_ip = re.search( + "Dirección IPv4. . . . . . . . . . . . . . : (\d+\.\d+\.\d+\.\d+)", + ethernet_config.group(1) if ethernet_config else "", + ) + dhcp_enabled = re.search( + "DHCP habilitado . . . . . . . . . . . . . : sí", + ethernet_config.group(1) if ethernet_config else "", + ) + + if current_ip and not dhcp_enabled: + ip_history.insert(0, current_ip.group(1)) + ip_history = ip_history[:10] + return f"Dirección IP actual guardada: {current_ip.group(1)}" + elif dhcp_enabled: + return "La dirección IP actual es obtenida por DHCP." + else: + return "No se encontró la dirección IP actual." + except Exception as e: + return f"Error al obtener la configuración IP: {e}" + + +def restore_last_ip(ip_history): + """Restaura la última dirección IP guardada para el adaptador Ethernet.""" + if ip_history: + last_ip = ip_history[0] # Obtener la última IP del historial + gateway = last_ip.rsplit(".", 1)[0] + ".1" + return set_static_ip(last_ip, gateway) + else: + return "No hay direcciones IP guardadas en el historial." + + +def get_input_parameter(): + """Solicita al usuario un parámetro por línea de comandos.""" + return input( + "Ingrese la dirección IP, 0-9 para los ultimas IP, 'a' o 'auto' para DHCP, o 'l' o 'last' para la última IP: " + ) + + +def display_ip_history(ip_history): + """Muestra el historial de las últimas 10 IPs en orden inverso (de la más antigua a la más reciente) y permite al usuario seleccionar una.""" + if ip_history: + for i, ip in enumerate(reversed(ip_history)): + print(f"{len(ip_history) - 1 - i}: {ip}") + return None + + +def remove_duplicates_preserve_order(ip_history): + """Elimina duplicados de la lista manteniendo el orden.""" + seen = set() + new_history = [] + for item in ip_history: + if item not in seen: + seen.add(item) + new_history.append(item) + return new_history + + +def show_notification(message, duration): + toaster = ToastNotifier() + toaster.show_toast("Cambio de IP", message, duration=duration) + + +def main(): + if not is_admin(): + # Si no se está ejecutando como administrador, solicitar elevación + result = run_as_admin() + if result is True: + print("Ejecutando como administrador.") + elif result is False: + print("No se pudo obtener privilegios de administrador.") + return + else: + # El script se reinició con privilegios de administrador + return + + if os.path.exists(IP_HISTORY_FILE): + with open(IP_HISTORY_FILE, "r") as file: + ip_history = json.load(file) + ip_history = remove_duplicates_preserve_order(ip_history) + else: + ip_history = [] + + argument = sys.argv[1] if len(sys.argv) > 1 else None + + if not argument: + chosen_ip = display_ip_history(ip_history) + argument = get_input_parameter() + + if len(argument) == 0: + return + + if ip_history: + if len(argument) == 1: + if argument.isdigit() and 0 <= int(argument) < len(ip_history): + argument = ip_history[int(argument)] + + # Guardar la dirección IP actual solo si no es 'auto' + message = save_current_ip(ip_history) + print(message) + + if argument == "auto" or argument == "a": + message = set_dhcp() + elif argument == "last" or argument == "l": + if ip_history: + message = restore_last_ip(ip_history) + print(message) + + else: + ip_parts = argument.split(".") + if len(ip_parts) == 3: + argument += ".249" + gateway = ".".join(ip_parts[:3]) + ".1" + message = set_static_ip(argument, gateway) + print(message) + + with open(IP_HISTORY_FILE, "w") as file: + json.dump(ip_history, file) + + notification_thread = threading.Thread(target=show_notification, args=(message, 3)) + notification_thread.start() + + +if __name__ == "__main__": + main() diff --git a/ipchanger.bat b/ipchanger.bat new file mode 100644 index 0000000..3e30d42 --- /dev/null +++ b/ipchanger.bat @@ -0,0 +1 @@ +python ipchange.py \ No newline at end of file diff --git a/last_ip.txt b/last_ip.txt new file mode 100644 index 0000000..110bd40 --- /dev/null +++ b/last_ip.txt @@ -0,0 +1 @@ +10.1.30.249 \ No newline at end of file diff --git a/menu-ip-change.py b/menu-ip-change.py new file mode 100644 index 0000000..e48af87 --- /dev/null +++ b/menu-ip-change.py @@ -0,0 +1,551 @@ +import tkinter as tk +from tkinter import ttk, messagebox +import json +import os +import subprocess +import re +import ctypes +import sys +import ipaddress +from typing import List, Optional +from dataclasses import dataclass +import threading +import time + +try: + import wmi + WMI_AVAILABLE = True +except ImportError: + WMI_AVAILABLE = False + print("WMI module not found. Using alternative method for network interface detection.") + +@dataclass +class NetworkInterface: + name: str + description: str + ip_address: str = "" + subnet_mask: str = "" + gateway: str = "" + dhcp_enabled: bool = True + active: bool = True + adapter_id: str = "" + +class IPChangeConfig: + def __init__(self): + self.config_file = "ip_config.json" + self.history_file = "ip_history.json" + self.load_config() + + def load_config(self): + try: + if os.path.exists(self.config_file): + with open(self.config_file, 'r') as f: + config = json.load(f) + self.last_interface = config.get('last_interface', '') + self.last_ip_prefix = config.get('last_ip_prefix', '') + else: + self.last_interface = '' + self.last_ip_prefix = '' + except Exception as e: + print(f"Error loading config: {e}") + self.last_interface = '' + self.last_ip_prefix = '' + + def save_config(self): + try: + config = { + 'last_interface': self.last_interface, + 'last_ip_prefix': self.last_ip_prefix + } + with open(self.config_file, 'w') as f: + json.dump(config, f) + except Exception as e: + print(f"Error saving config: {e}") + +class IPChangerApp: + def __init__(self, master): + self.master = master + self.master.title("Network Interface IP Configuration") + self.master.geometry("900x700") + + # Inicializar configuración antes que nada + self.config = IPChangeConfig() + + # Inicializar estructuras de datos + self.interfaces: List[NetworkInterface] = [] + self.ip_history: List[str] = [] + + # Inicializar WMI si está disponible + if WMI_AVAILABLE: + self.wmi_client = wmi.WMI() + + # Cargar datos guardados antes de crear widgets + self.load_ip_history() + + # Crear la interfaz + self.create_widgets() + + # Actualizar la lista de IPs en el combo + self.update_history_display() + + # Refrescar interfaces + self.refresh_interfaces() + + # Configurar pesos de la cuadrícula + self.master.grid_columnconfigure(0, weight=1) + self.master.grid_rowconfigure(1, weight=1) # El log expandible + + def get_ip_prefix(self, ip: str) -> str: + """Extrae los primeros 3 octetos de una dirección IP""" + parts = ip.split('.') + if len(parts) >= 3: + return '.'.join(parts[:3]) + return ip + + def create_widgets(self): + # Panel superior para controles + self.control_frame = ttk.Frame(self.master, padding="10") + self.control_frame.grid(row=0, column=0, sticky='new') + + # Sección de interfaces + self.interfaces_frame = ttk.LabelFrame(self.control_frame, text="Network Interfaces", padding="5") + self.interfaces_frame.grid(row=0, column=0, sticky='ew', pady=(0,10)) + + ttk.Label(self.interfaces_frame, text="Select Interface:").grid(row=0, column=0, sticky='w', padx=5) + self.if_var = tk.StringVar() + self.if_combo = ttk.Combobox(self.interfaces_frame, textvariable=self.if_var, state='readonly', width=50) + self.if_combo.grid(row=0, column=1, sticky='ew', padx=5) + self.if_combo.bind('<>', self.on_interface_selected) + + # Info de configuración actual + self.current_frame = ttk.LabelFrame(self.control_frame, text="Current Configuration", padding="5") + self.current_frame.grid(row=1, column=0, sticky='ew', pady=(0,10)) + + self.current_ip_var = tk.StringVar() + self.current_mask_var = tk.StringVar() + self.current_gateway_var = tk.StringVar() + self.dhcp_status_var = tk.StringVar() + + ttk.Label(self.current_frame, text="Current IP:").grid(row=0, column=0, sticky='w', padx=5) + ttk.Label(self.current_frame, textvariable=self.current_ip_var).grid(row=0, column=1, sticky='w') + ttk.Label(self.current_frame, text="Subnet Mask:").grid(row=1, column=0, sticky='w', padx=5) + ttk.Label(self.current_frame, textvariable=self.current_mask_var).grid(row=1, column=1, sticky='w') + ttk.Label(self.current_frame, text="Gateway:").grid(row=2, column=0, sticky='w', padx=5) + ttk.Label(self.current_frame, textvariable=self.current_gateway_var).grid(row=2, column=1, sticky='w') + ttk.Label(self.current_frame, text="DHCP Status:").grid(row=3, column=0, sticky='w', padx=5) + ttk.Label(self.current_frame, textvariable=self.dhcp_status_var).grid(row=3, column=1, sticky='w') + + # Sección de configuración IP + self.ip_frame = ttk.LabelFrame(self.control_frame, text="IP Configuration", padding="5") + self.ip_frame.grid(row=2, column=0, sticky='ew', pady=(0,10)) + + ttk.Label(self.ip_frame, text="IP Prefix (first 3 octets):").grid(row=0, column=0, sticky='w', padx=5) + self.ip_prefix = tk.StringVar(value=self.config.last_ip_prefix) + self.ip_entry = ttk.Entry(self.ip_frame, textvariable=self.ip_prefix, width=30) + self.ip_entry.grid(row=0, column=1, sticky='w', padx=5) + + ttk.Label(self.ip_frame, text="Last Octet:").grid(row=0, column=2, sticky='w', padx=5) + self.last_octet = tk.StringVar(value="249") + self.last_octet_entry = ttk.Entry(self.ip_frame, textvariable=self.last_octet, width=5) + self.last_octet_entry.grid(row=0, column=3, sticky='w', padx=5) + + # Sección de historial + self.history_frame = ttk.LabelFrame(self.control_frame, text="IP History", padding="5") + self.history_frame.grid(row=3, column=0, sticky='ew', pady=(0,10)) + + ttk.Label(self.history_frame, text="Previous IPs:").grid(row=0, column=0, sticky='w', padx=5) + self.history_var = tk.StringVar() + self.history_combo = ttk.Combobox( + self.history_frame, + textvariable=self.history_var, + state='readonly', + width=50 + ) + self.history_combo.grid(row=0, column=1, sticky='ew', padx=5) + self.history_combo.bind('<>', self.on_history_selected) + + # Botones de acción + self.button_frame = ttk.Frame(self.control_frame) + self.button_frame.grid(row=4, column=0, sticky='ew', pady=(0,10)) + + ttk.Button(self.button_frame, text="Set Static IP", command=self.set_static_ip).grid(row=0, column=0, padx=5) + ttk.Button(self.button_frame, text="Enable DHCP", command=self.set_dhcp).grid(row=0, column=1, padx=5) + ttk.Button(self.button_frame, text="Refresh Interfaces", command=self.refresh_interfaces).grid(row=0, column=2, padx=5) + + # Log en la parte inferior + self.log_frame = ttk.LabelFrame(self.master, text="Operation Log", padding="5") + self.log_frame.grid(row=1, column=0, sticky='nsew', padx=10, pady=(0,10)) + + self.log_text = tk.Text(self.log_frame, wrap="none", height=10) + self.log_scrollbar_y = ttk.Scrollbar(self.log_frame, orient="vertical", command=self.log_text.yview) + self.log_scrollbar_x = ttk.Scrollbar(self.log_frame, orient="horizontal", command=self.log_text.xview) + + self.log_text.configure( + yscrollcommand=self.log_scrollbar_y.set, + xscrollcommand=self.log_scrollbar_x.set + ) + + self.log_text.grid(row=0, column=0, sticky='nsew') + self.log_scrollbar_y.grid(row=0, column=1, sticky='ns') + self.log_scrollbar_x.grid(row=1, column=0, sticky='ew') + + ttk.Button(self.log_frame, text="Clear Log", command=self.clear_log).grid(row=2, column=0, columnspan=2, pady=5) + + self.log_frame.grid_columnconfigure(0, weight=1) + self.log_frame.grid_rowconfigure(0, weight=1) + + def clear_log(self): + self.log_text.delete('1.0', tk.END) + + def log_message(self, message: str): + self.log_text.insert(tk.END, f"{message}\n") + self.log_text.see(tk.END) + self.log_text.update_idletasks() + + def load_ip_history(self): + try: + if os.path.exists(self.config.history_file): + with open(self.config.history_file, 'r') as f: + self.ip_history = json.load(f) + else: + self.ip_history = [] + except Exception as e: + self.log_message(f"Error loading IP history: {e}") + self.ip_history = [] + + def save_ip_history(self): + try: + with open(self.config.history_file, 'w') as f: + json.dump(self.ip_history, f) + except Exception as e: + self.log_message(f"Error saving IP history: {e}") + + def add_to_history(self, ip_prefix: str): + if ip_prefix and ip_prefix not in self.ip_history: + self.ip_history.insert(0, ip_prefix) + self.ip_history = self.ip_history[:15] # Mantener solo las últimas 15 IPs + self.save_ip_history() + self.update_history_display() + + def update_history_display(self): + self.history_combo['values'] = self.ip_history + if self.ip_history: + self.history_combo.set(self.ip_history[0]) + + def on_history_selected(self, event): + selected_ip = self.history_var.get() + if selected_ip: + # Ensure we only get the first three octets + ip_parts = selected_ip.split('.') + if len(ip_parts) >= 3: + ip_prefix = '.'.join(ip_parts[:3]) + self.ip_prefix.set(ip_prefix) + self.config.last_ip_prefix = ip_prefix + self.config.save_config() + self.log_message(f"Selected IP prefix from history: {ip_prefix}") + + def refresh_interfaces(self): + self.interfaces.clear() + try: + if WMI_AVAILABLE: + self._refresh_interfaces_wmi() + else: + self._refresh_interfaces_netsh() + + # Actualizar combobox + self.if_combo['values'] = [inf.name for inf in self.interfaces if inf.active] + + # Seleccionar última interfaz usada si está disponible + if self.config.last_interface in self.if_combo['values']: + self.if_combo.set(self.config.last_interface) + self.on_interface_selected() + + self.log_message("Interfaces refreshed successfully") + + except Exception as e: + self.log_message(f"Error refreshing interfaces: {str(e)}") + self.show_error(f"Error refreshing interfaces: {str(e)}") + + def _refresh_interfaces_wmi(self): + try: + adapters = self.wmi_client.Win32_NetworkAdapter(PhysicalAdapter=True) + configs = self.wmi_client.Win32_NetworkAdapterConfiguration() + + for adapter in adapters: + config = next((cfg for cfg in configs if cfg.InterfaceIndex == adapter.InterfaceIndex), None) + + if config and config.IPEnabled: + interface = NetworkInterface( + name=adapter.NetConnectionID, + description=adapter.Description, + ip_address=config.IPAddress[0] if config.IPAddress else "", + subnet_mask=config.IPSubnet[0] if config.IPSubnet else "", + gateway=config.DefaultIPGateway[0] if config.DefaultIPGateway else "", + dhcp_enabled=config.DHCPEnabled, + active=adapter.NetEnabled, + adapter_id=adapter.GUID + ) + self.interfaces.append(interface) + except Exception as e: + raise Exception(f"WMI refresh error: {str(e)}") + + def _refresh_interfaces_netsh(self): + try: + output = subprocess.check_output("netsh interface ipv4 show config", shell=True, text=True) + configs = output.split("\n\n") + + for config in configs: + if not config.strip(): + continue + + name_match = re.search(r"Configuración de\s+\"(.+?)\"", config) + if not name_match: + continue + + name = name_match.group(1) + + ip_match = re.search(r"Dirección IP:\s+(\d+\.\d+\.\d+\.\d+)", config) + mask_match = re.search(r"Máscara de subred:\s+(\d+\.\d+\.\d+\.\d+)", config) + gateway_match = re.search(r"Puerta de enlace predeterminada:\s+(\d+\.\d+\.\d+\.\d+)", config) + dhcp_match = re.search(r"DHCP habilitado:\s+(\w+)", config) + + interface = NetworkInterface( + name=name, + description=name, + ip_address=ip_match.group(1) if ip_match else "", + subnet_mask=mask_match.group(1) if mask_match else "", + gateway=gateway_match.group(1) if gateway_match else "", + dhcp_enabled=dhcp_match.group(1).lower() == "sí" if dhcp_match else True, + active=True + ) + self.interfaces.append(interface) + + except subprocess.CalledProcessError as e: + raise Exception(f"Error executing netsh: {str(e)}") + except Exception as e: + raise Exception(f"Error parsing netsh output: {str(e)}") + + def on_interface_selected(self, event=None): + selected = self.if_var.get() + interface = next((inf for inf in self.interfaces if inf.name == selected), None) + + if interface: + self.current_ip_var.set(interface.ip_address) + self.current_mask_var.set(interface.subnet_mask) + self.current_gateway_var.set(interface.gateway) + self.dhcp_status_var.set("Enabled" if interface.dhcp_enabled else "Disabled") + + # Actualizar IP prefix si hay una IP válida + if interface.ip_address: + prefix = '.'.join(interface.ip_address.split('.')[:3]) + self.ip_prefix.set(prefix) + + # Guardar interfaz seleccionada + self.config.last_interface = selected + self.config.save_config() + + self.log_message(f"Selected interface: {selected}") + + def validate_ip_input(self) -> tuple[bool, str]: + try: + prefix = self.ip_prefix.get().strip() + last_octet = self.last_octet.get().strip() + + # Validar formato del prefijo + if not re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}', prefix): + return False, "IP prefix must be in format: xxx.xxx.xxx" + + # Validar último octeto + if not last_octet.isdigit() or not 0 <= int(last_octet) <= 255: + return False, "Last octet must be between 0 and 255" + + # Construir y validar IP completa + ip = f"{prefix}.{last_octet}" + ipaddress.IPv4Address(ip) + + return True, ip + + except ValueError as e: + return False, f"Invalid IP address: {str(e)}" + + def execute_admin_script(self, interface: str, mode: str, debug: bool = True) -> bool: + """ + Ejecuta el script administrativo con los argumentos proporcionados + mode puede ser una IP o 'dhcp' + """ + # Obtener rutas absolutas + current_dir = os.path.dirname(os.path.abspath(__file__)) + script_path = os.path.join(current_dir, "ip-changer-admin.py") + python_exe = sys.executable + + # Verificar que el script existe + if not os.path.exists(script_path): + error_msg = f"Admin script not found at: {script_path}" + self.log_message(error_msg) + return False + + # Construir la línea de argumentos + args = f'"{script_path}" "{interface}" "{mode}"' + + if debug: + self.log_message("Debug Information:") + self.log_message(f"Current Directory: {current_dir}") + self.log_message(f"Script Path: {script_path}") + self.log_message(f"Python Executable: {python_exe}") + self.log_message(f"Full Command: {python_exe} {args}") + + try: + self.log_message(f"Attempting to execute admin script with elevated privileges") + + ret = ctypes.windll.shell32.ShellExecuteW( + None, # hwnd + "runas", # operation + python_exe, # file + args, # parameters + current_dir, # directory + 1 # show command + ) + + result_code = int(ret) + if result_code > 32: + self.log_message(f"ShellExecuteW successful (return code: {result_code})") + return True + else: + error_codes = { + 0: "The operating system is out of memory or resources", + 2: "The specified file was not found", + 3: "The specified path was not found", + 5: "Access denied", + 8: "Insufficient memory to complete the operation", + 11: "Bad format", + 26: "A sharing violation occurred", + 27: "The file name association is incomplete or invalid", + 28: "The DDE operation timed out", + 29: "The DDE operation failed", + 30: "The DDE operation is busy", + 31: "The file name association is unavailable", + 32: "No application is associated with the file" + } + + self.log_message(f"Failed to execute admin script. Error code {result_code}: {error_msg}") + return False + + except Exception as e: + self.log_message(f"Exception while executing admin script: {str(e)}") + return False + + def set_static_ip(self): + interface = self.if_var.get() + if not interface: + self.show_error("Please select a network interface") + return + + # Validar IP + is_valid, result = self.validate_ip_input() + if not is_valid: + self.show_error(result) + return + + ip = result + + # Guardar IP actual en el historial + self.add_to_history(self.ip_prefix.get()) + + # Ejecutar script con privilegios + if self.execute_admin_script(interface, ip, debug=True): + time.sleep(2) # Esperar a que se apliquen los cambios + self.refresh_interfaces() + self.log_message(f"Successfully set static IP {ip} on {interface}") + else: + self.show_error("Failed to set static IP. Check the log for details.") + + def set_dhcp(self): + interface = self.if_var.get() + if not interface: + self.show_error("Please select a network interface") + return + + # Ejecutar script con privilegios + if self.execute_admin_script(interface, "dhcp", debug=True): + time.sleep(2) # Esperar a que se apliquen los cambios + self.refresh_interfaces() + self.log_message(f"Successfully enabled DHCP on {interface}") + else: + self.show_error("Failed to enable DHCP. Check the log for details.") + + def run_command(self, cmd: str) -> bool: + self.log_message(f"Executing command: {cmd}") + try: + process = subprocess.Popen( + cmd, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True + ) + + while True: + output = process.stdout.readline() + if output == '' and process.poll() is not None: + break + if output: + self.log_message(output.strip()) + + retcode = process.poll() + + error_output = process.stderr.read() + if error_output: + self.log_message(f"Error output:\n{error_output}") + + if retcode == 0: + self.log_message("Command executed successfully") + return True + else: + self.log_message(f"Command failed with return code: {retcode}") + return False + + except Exception as e: + self.log_message(f"Error executing command: {str(e)}") + return False + + def is_admin(self): + try: + return ctypes.windll.shell32.IsUserAnAdmin() + except Exception as e: + self.log_message(f"Error checking admin privileges: {str(e)}") + return False + + def elevate_privileges(self): + if sys.platform == 'win32': + self.show_info("The application needs administrator privileges to change network settings. " + "Please confirm the UAC prompt.") + script_path = os.path.abspath(sys.argv[0]) + try: + ctypes.windll.shell32.ShellExecuteW( + None, + "runas", + sys.executable, + f'"{script_path}"', + None, + 1 + ) + self.master.quit() + except Exception as e: + self.log_message(f"Error elevating privileges: {str(e)}") + self.show_error("Failed to elevate privileges") + + def show_error(self, message: str): + self.log_message(f"ERROR: {message}") + messagebox.showerror("Error", message) + + def show_info(self, message: str): + self.log_message(f"INFO: {message}") + messagebox.showinfo("Information", message) + +def main(): + root = tk.Tk() + app = IPChangerApp(root) + root.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/privilegios.py b/privilegios.py new file mode 100644 index 0000000..e048a64 --- /dev/null +++ b/privilegios.py @@ -0,0 +1,55 @@ +import ctypes +import sys +import os +import subprocess +import re + +def is_admin(): + """Verifica si el script se está ejecutando con privilegios de administrador.""" + try: + return ctypes.windll.shell32.IsUserAnAdmin() + except: + return False + +def run_as_admin(argv=None, debug=False): + """Ejecuta el script como administrador.""" + shell32 = ctypes.windll.shell32 + if argv is None and shell32.IsUserAnAdmin(): + # Ya estamos ejecutando como administrador, no es necesario hacer nada + return True + if argv is None: + argv = sys.argv + if hasattr(sys, '_MEIPASS'): + # Soporte para PyInstaller + arguments = map(str, argv[1:]) + else: + arguments = map(str, argv) + argument_line = u' '.join(arguments) + executable = str(sys.executable) + if debug: + print(f'Command line: {executable} {argument_line}') + ret = shell32.ShellExecuteW(None, "runas", executable, argument_line, None, 1) + if int(ret) <= 32: + return False + return None + +def main(): + if not is_admin(): + # Si no se está ejecutando como administrador, solicitar elevación + result = run_as_admin() + if result is True: + print("Ejecutando como administrador.") + elif result is False: + print("No se pudo obtener privilegios de administrador.") + return + else: + # El script se reinició con privilegios de administrador + return + + # Aquí va el resto del código de tu script... + # Por ejemplo: + # save_current_ip() + # ... + +if __name__ == "__main__": + main()