|
31
|
Programación / Scripting / [APORTE] [Bash] Instalador completo de compatibilidad Windows en Linux (con Wine)
|
en: 8 Junio 2025, 13:12 pm
|
Instalador automatizado de compatibilidad Windows en Linux (con Wine)Este script Bash automatiza completamente la habilitación e integración de compatibilidad con aplicaciones de Windows en sistemas Linux, incluyendo: - Instalación de Wine y sus dependencias.
- Interfaz gráfica para Wine.
- Asociación automática de archivos .exe en el gestor de archivos.
- Generación automática de iconos (miniaturas) para ejecutables de Windows.
- Fuentes TrueType de Windows.
- Runtimes de Visual Basic,Visual C++, .NET Framework y .NET 6.0+
- DirectX y codecs de video.
- Librerías DLL específicas y componentes auxiliares.
- Acceso a la consola CMD de Windows
Con todos estos componentes instalados he podido ejecutar bajo Linux casi cualquier aplicación para Windows, la mayoría modernas, así como también video juegos antiguos que no son de Steam. No van a funcionar absolutamente todas las aplicaciones de Windows, ya que por lo poco que sé Wine no soporta .NET Framework 4.x en su totalidad, y alguna aplicación puede solicitar librerías específicas que habría que copiar directamente desde una instalación de Windows a Linux, y luego registrar los archivos con regsvr32.exe en caso de ser necesario (y suerte intentando hacer que eso funcione), pero la mayoría de software que he probado ha funcionado correctamente sin hacer nada especial, incluyendo software compilado bajo .NET 8 y .NET 9 que apuntan a Windows. El script ha sido probado en Xubuntu (XFCE) y Kubuntu (KDE).  ( hacer click en la imagen para agrandar) Cabe mencionar que, y según creo tener entendido, 'sudo' se puede configurar para que siempre pida contraseña cada vez que se invoca durante la misma sesión de la terminal, o que después de 1 minuto / un tiempo configurable se vuelva a pedir contraseña la próxima vez que se invoque, así que en esos casos pues el script no realizará un procedimiento totalmente automatizado...
Tras la instalación de todos estos componentes el script generará varios accesos directos y lanzadores en el escritorio de nuestro gestor de archivos (no sé si funcionará para todos) para abrir los componentes de Wine, la carpeta del disco "C:\" de Windows y la CMD, además, en caso de tener instalado el gestor de archivos Thunar también se generarán unas acciones personalizadas en el menú contextual del gestor para ejecutar un programa con Wine y también para abrir la CMD en el directorio actual o abrir el directorio en "Windows Explorer". Todo esto con la finalidad de mejorar un poquito más la experiencia de integración con Wine.  ( hacer click en la imagen para agrandar) Nota: La instalación podría demorar entre 30~60 minutos aprox. dependiendo del hardware y la velocidad de conexión a Internet. En mi caso, usando una máquina virtual con el sistema operativo Xubuntu, con 6 GB de RAM y 4 núcleos, tarda unos 40 minutos. Y se requiere de aprox. 10 GB de espacio libre en disco para los archivos a instalar, además de archivos temporales adicionales que se descargan durante el procedimiento de instalación; El script elimina estos archivos temporales después de completar la instalación y el espacio total utilizado se reduce aprox. 2,50 GB, dejando unos 7,50 GB utilizados. Tengan en cuenta que escribir este script me ha supuesto muchas horas de esfuerzo e investigación ya que no tengo experiencia con Bash (este es de mis primeros scripts) ni con Wine ni con Linux en general, así que he recurrido a ChatGPT en muchas ocasiones para armar hasta la línea de código más básica, a base de mucho ensayo y error (20% errores míos, 80% errores de esta IA inútil), y también horas de tediosa experimentación, ya que instalar un componente o una librería "equivocada" con winetricks supone que Wine se vaya al carajo y no funcione ningún ejecutable de Windows, lo cual me ha ocurrido varias veces... y vuelta a empezar de cero. ¿Mi motivación para escribir este script?, el simple capricho de poder usar WinRAR y otras aplicaciones para Windows que considero esenciales y sin un equivalente digno para Linux, sumado a mi cabezonería y el tiempo libre para experimentar.
El script de instalación: Wine_Install.sh#!/bin/bash set -e
############################################################################### # CONFIGURATION # ###############################################################################
DESKTOP_DIR="$(xdg-user-dir DESKTOP)"
############################################################################### # FUNCTIONS # ###############################################################################
create_desktop_launcher() { local EXEC_PATH="$1" # Path to executable or URL (required) local APP_NAME="$2" # Display name of the app (required) local COMMENT="${3:-}" # Comment/description (optional) local ICON_PATH="${4:-}" # Path to icon file or URL (optional) local TERMINAL="${5:-false}" # true or false to run in terminal (optional, default false) local TYPE="${6:-Application}" # Desktop entry type (Application, Link, etc.) (optional) local CATEGORIES="${7:-Utility;}" # Categories for the launcher (optional) local ARGS="${8:-}" # Arguments for the executable (optional) local DESKTOP_FILE="${9:-$DESKTOP_DIR/$APP_NAME.desktop}" # Path to .desktop file (optional)
# Validate required params if [ -z "$EXEC_PATH" ] || [ -z "$APP_NAME" ]; then echo "❌ Usage: create_desktop_launcher <exec_path> <app_name> [comment] [icon_path] [terminal:true|false] [type] [categories] [args] [desktop_file]" return 1 fi
# Handle icon: if URL, download it to ~/.local/share/icons/<app_name>.png if [[ "$ICON_PATH" == http* ]]; then local ICON_DIR="$HOME/.local/share/icons" mkdir -p "$ICON_DIR" local SAFE_APP_NAME=$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '_') local LOCAL_ICON_PATH="$ICON_DIR/$SAFE_APP_NAME.png"
if [ ! -f "$LOCAL_ICON_PATH" ]; then # echo "⬇️ Downloading icon from URL: $ICON_PATH" if command -v wget > /dev/null 2>&1; then wget -qO "$LOCAL_ICON_PATH" "$ICON_PATH" elif command -v curl > /dev/null 2>&1; then curl -sL "$ICON_PATH" -o "$LOCAL_ICON_PATH" else echo "❌ Neither wget nor curl found. Cannot download icon for desktop launcher." # return 1 fi
if [ ! -f "$LOCAL_ICON_PATH" ]; then echo "❌ Icon download failed for desktop launcher. Check your internet connection or URL." # return 1 fi fi ICON_PATH="$LOCAL_ICON_PATH" fi
# Write .desktop file { echo "[Desktop Entry]" echo "Name=$APP_NAME" [ -n "$COMMENT" ] && echo "Comment=$COMMENT" echo "Exec=$EXEC_PATH $ARGS" [ -n "$ICON_PATH" ] && echo "Icon=$ICON_PATH" echo "Terminal=$TERMINAL" echo "Type=$TYPE" echo "Categories=$CATEGORIES" } > "$DESKTOP_FILE" }
create_thunar_custom_action() { local UCA_FILE="$HOME/.config/Thunar/uca.xml" local UNIQUE_ID="$1" local ICON="$2" local NAME="$3" local SUBMENU="$4" local COMMAND="$5" local DESCRIPTION="$6" local RANGE="$7" local PATTERNS="$8" local EXTRA_TAGS="${9}"
if [ ! -f "$UCA_FILE" ]; then sudo mkdir -p "$(dirname "$UCA_FILE")" echo '<?xml version="1.0" encoding="UTF-8"?> <actions> </actions>' > "$UCA_FILE" # echo "✨ Created new uca.xml configuration file." fi
if grep -q "<unique-id>$UNIQUE_ID</unique-id>" "$UCA_FILE"; then # echo "⚠️ Custom Action '$NAME' already exists in Thunar." return fi
local ACTION_BLOCK=" <action> <icon>$ICON</icon> <name>$NAME</name> <submenu>$SUBMENU</submenu> <unique-id>$UNIQUE_ID</unique-id> <command>$COMMAND</command> <description>$DESCRIPTION</description> <range>$RANGE</range> <patterns>$PATTERNS</patterns>" if [ -n "$EXTRA_TAGS" ]; then ACTION_BLOCK="${ACTION_BLOCK} $EXTRA_TAGS" fi
ACTION_BLOCK="${ACTION_BLOCK} </action>"
awk -v block="$ACTION_BLOCK" ' /<\/actions>/ { print block } { print } ' "$UCA_FILE" > "$UCA_FILE.tmp" && mv "$UCA_FILE.tmp" "$UCA_FILE" # echo "✅ Custom Action '$NAME' added successfully to Thunar." }
############################################################################### # MAIN SCRIPT EXECUTION # ###############################################################################
echo "ℹ️ INFO: This script will install Wine and required components and tools" echo " to integrate and run Windows applications on your Linux system." echo "" echo "⚠️ About 10 GB of free space is required to download and install all componentes." echo " The installation may take approximately 40~60 minutes." echo echo -n "Do you want to continue? [Y/N]: " while true; do read -n1 confirm echo if [[ -z "$confirm" || "$confirm" =~ ^[Yy]$ ]]; then break elif [[ "$confirm" =~ ^[Nn]$ ]]; then echo "❌ Operation cancelled." exit 1 else echo "⚠️ Invalid option. Please press Y or N." fi done clear
echo "🛠️ Adding support for 32-bit architecture and packages (required to install wine32)..." sudo dpkg --add-architecture i386
echo -e "\n🔄 Updating package list to fetch latest info..." sudo apt-get update
echo -e "\n🍷 Installing Wine (wine32, wine64), required to run Windows apps on Linux..." # https://www.winehq.org/ sudo apt-get install --install-recommends -y wine wine32 wine64 libwine fonts-wine
echo -e "\n🍷 Installing Qt GUI for Wine..." # https://github.com/brezerk/q4wine sudo apt-get install --install-recommends -y q4wine
echo -e "\n🍷 Installing Winbind, required for Windows user authentication integration with Wine..." # https://www.samba.org/samba/docs/current/man-html/winbindd.8.html sudo apt-get install -y winbind
echo -e "\n🍷 Installing WineTricks for automatic installation of Windows runtimes and libraries with Wine..." # https://github.com/Winetricks/winetricks sudo apt-get install --install-recommends -y winetricks # Installs latest winetricks script, allowing later dotnetdesktop8 and dotnetdesktop9 runtime installation. # https://github.com/Winetricks/winetricks/issues/2178#issuecomment-2299877869 yes | sudo winetricks --self-update # Use sudo, it requires @root permissions to self update.
echo -e "\n🍷 Installing wine-binfmt, which causes Wine to be invoked automatically whenever a Windows .exe file is to be launched..." # https://packages.debian.org/sid/wine-binfmt # https://binfmt-support.nongnu.org/ sudo apt-get install -y wine-binfmt binfmt-support
if command -v thunar >/dev/null 2>&1; then echo -e "\n📦 Installing Tumbler and its plugins to enable thumbnail generation for various file types in Thunar file manager." # https://docs.xfce.org/xfce/thunar/4.14/tumbler # https://packages.debian.org/sid/xfce/tumbler-plugins-extra sudo apt-get install -y tumbler tumbler-plugins-extra fi
echo -e "\n📦 Installing icoextract-thumbnailer (exe-thumbnailer), which generates thumbnail previews for Windows .exe files in your file manager..." # https://github.com/jlu5/icoextract?tab=readme-ov-file#installing-from-source sudo apt-get install --install-recommends -y icoextract-thumbnailer
echo -e "\n🆕 Creating file: '/usr/local/share/thumbnailers/exe-thumbnailer.thumbnailer' with thumbnail previews configuration for Windows PE files..." # https://github.com/jlu5/icoextract/blob/master/exe-thumbnailer.thumbnailer sudo mkdir -p "/usr/local/share/thumbnailers/" && { if [ ! -f "/usr/local/share/thumbnailers/exe-thumbnailer.thumbnailer" ]; then sudo tee "/usr/local/share/thumbnailers/exe-thumbnailer.thumbnailer" > /dev/null << EOF [Thumbnailer Entry] Exec=exe-thumbnailer -v -s %s %i %o MimeType=application/x-ms-dos-executable;application/x-dosexec;application/x-msdownload;application/vnd.microsoft.portable-executable EOF fi }
echo -e "\n🧹 Removing existing Wine configuration in '~/.wine' (if any)..." rm -rf ~/.wine
echo -e "\n🆕 Creating new Wine configuration with Windows 10 settings..." sudo WINEPREFIX="/root/.wine" wine-stable winecfg -v win10 WINEPREFIX=~/.wine wine-stable winecfg -v win10
echo -e "\n🅰️ Installing Microsoft Windows fonts for better app compatibility..." winetricks --unattended corefonts calibri cambria consolas uff
echo -e "\n⚙️ Installing Microsoft Windows runtimes..." winetricks --unattended vb6run winetricks --unattended vcrun6sp6 vcrun2005 vcrun2008 vcrun2010 vcrun2012 vcrun2013 winetricks --unattended --force vcrun2022 # Force installation due installer file checksum missmatch. { winetricks --unattended dotnet48 # These registry keys forces all .NET Framework assemblies to run on the latest installed CLR (i.e., dotnet48) # It also solves issue: https://bugs.winehq.org/show_bug.cgi?id=41727#c5 wine reg add "HKLM\\Software\\Microsoft\\.NETFramework" /v "OnlyUseLatestCLR" /t "REG_DWORD" /d "0001" /f wine reg add "HKLM\\Software\\WOW6432Node\\Microsoft\\.NetFramework" /v "OnlyUseLatestCLR" /t "REG_DWORD" /d "0001" /f } winetricks --unattended dotnetdesktop6 dotnetdesktop7 # .NET 8 and .NET 9 are available only from latest winetricks release which we got with 'winetricks --self-update' command. winetricks --unattended dotnetdesktop8 dotnetdesktop9
echo -e "\n🎥 Installing codecs for media playback (also useful for Windows video games)..." winetricks --unattended cinepak directshow ffdshow l3codecx openal
echo -e "\n📚 Installing essential Microsoft Windows libraries..." winetricks --unattended cabinet crypt32 dbghelp gdiplus hid iertutil msacm32 msxml6 \ ole32 oleaut32 setupapi uiribbon urlmon webio wininet
echo -e "\n🖥️ Installing DirectX runtimes and APIs for graphics and gaming..." winetricks --unattended d3dx9 d3dx10 dxdiag d3drm d3dxof dx8vb dxtrans \ devenum dinput8 directmusic directplay mdx sdl
echo -e "\n🖥️ Installing Vulkan APIs for advanced graphics support..." winetricks --unattended dxvk vkd3d
echo -e "\n💾 Installing Microsoft Windows CMD..." winetricks --unattended cmd
echo -e "\n🔄 Restoring Wine configuration to Windows 10 settings..." wine winecfg -v win10
echo -e "\n🧹 Cleaning winetricks cache folder in '~/.cache/winetricks'..." rm -rf ~/.cache/winetricks/*
echo -e "\n🚀 Creating desktop link: 'Wine (C:\)'..." if command -v dolphin >/dev/null 2>&1; then WINE_DIR_ICON="folder-windows-symbolic" else WINE_DIR_ICON="wine" fi printf '%s\n' \ "[Desktop Entry]" \ "Type=Link" \ 'Name=Wine (C:\\)' \ "Icon=$WINE_DIR_ICON" \ "URL=file://$HOME/.wine/drive_c" \ > "$DESKTOP_DIR/wine-open-drive_c.desktop"
echo -e "\n🚀 Creating desktop launcher: 'Wine Configuration'..." create_desktop_launcher \ "wine-stable winecfg" \ "Wine Configuration" \ "Launch Wine Configuration" \ "wine-stable" \ "false" \ "Application" \ "Utility;" \ "" \ "$DESKTOP_DIR/wine-configuration.desktop"
echo -e "\n🚀 Creating desktop launcher: 'Qt GUI for Wine'..." create_desktop_launcher \ "q4wine" \ "Qt GUI for Wine" \ "Launch Qt GUI for Wine" \ "q4wine" \ "false" \ "Application" \ "Utility;" \ "" \ "$DESKTOP_DIR/qt-gui-for-wine.desktop"
echo -e "\n🚀 Creating desktop launcher: 'Winetricks'..." create_desktop_launcher \ "winetricks --gui" \ "Winetricks" \ "Launch Winetricks with GUI" \ "winetricks" \ "false" \ "Application" \ "Utility;" \ "" \ "$DESKTOP_DIR/winetricks.desktop"
if command -v thunar >/dev/null 2>&1; then CMD_EXEC="xfce4-terminal --title=\"CMD\" --color-bg=#000000 --color-text=#FFFFFF -e 'sudo wine-stable cmd.exe'" CMD_RUN_IN_TERMINAL=true elif command -v dolphin >/dev/null 2>&1; then CMD_EXEC="konsole --hold -e sudo wine-stable cmd.exe" CMD_RUN_IN_TERMINAL=false fi if [[ -n "$CMD_EXEC" ]]; then echo -e "\n🚀 Creating desktop launcher: 'CMD'..." create_desktop_launcher \ "$CMD_EXEC" \ "CMD" \ "Launch Microsoft Windows CMD as root" \ "https://upload.wikimedia.org/wikipedia/commons/7/7b/CMD-Icon_%28small%29.png" \ $CMD_RUN_IN_TERMINAL \ "Application" \ "Utility;" \ "" \ "$DESKTOP_DIR/cmd.desktop" fi
if command -v thunar >/dev/null 2>&1; then
echo -e "\n🚀 Creating Thunar custom action: 'Run with Wine'..." create_thunar_custom_action \ "wine-run-with-wine" \ "wine" \ "Run with Wine" \ "" \ "wine-stable %f" \ "Run a Microsoft Windows executable with Wine" \ "*" \ "*.exe" \ "<other-files/>"
echo -e "\n🚀 Creating Thunar custom action: 'Run with Wine (Terminal)'..." create_thunar_custom_action \ "wine-run-with-wine-terminal" \ "wine" \ "Run with Wine (Terminal)" \ "" \ "xfce4-terminal --hold -e "wine-stable %f"" \ "Run a Microsoft Windows executable with Wine (Terminal)" \ "*" \ "*.exe" \ "<other-files/>"
echo -e "\n🚀 Creating Thunar custom action: 'Open CMD here'..." create_thunar_custom_action \ "wine-open-cmd-here" \ "cmd" \ "Open CMD here" \ "" \ "sudo xfce4-terminal --title="CMD" --color-bg=#000000 --color-text=#FFFFFF -e "wine start /wait /b /d %f cmd"" \ "Open a Windows CMD in the current directory" \ "*" \ "*" \ "<directories/>"
echo -e "\n🚀 Creating Thunar custom action: 'Open in Explorer (Wine)'..." create_thunar_custom_action \ "wine-open-in-explorer" \ "folder" \ "Open in Explorer (Wine)" \ "" \ "wine-stable explorer %f" \ "Open the current directory in Explorer (Wine)" \ "*" \ "*" \ "<directories/>"
fi
echo -n -e "\n🏁 Operation completed." echo read -n1 -r -s -p "Press any key to finish..." echo exit
Adicionalmente, recomiendo utilizar estos comandos para instalar los runtimes de .NET 8 y 9 en Linux: sudo apt install -y dotnet-runtime-8.0 sudo apt install -y dotnet-runtime-9.0 He preferido no incluir la instalación de estos runtimes en el script por que son parte del soporte nativo en Linux para aplicaciones .NET 8 y .NET 9 que apuntan a Linux; No tienen relación ni influencia sobre Wine. El script ya instala los respectivos runtimes de .NET 8 y 9 dentro de su entorno para poder ejecutar con Wine los binarios de .NET 8 y 9 que apuntan a Windows.
Por último, comparto este script para revertir la instalación de Wine y sus componentes: Wine_Uninstall.sh#!/bin/bash
############################################################################### # MAIN SCRIPT EXECUTION # ###############################################################################
echo "⚠️ WARNING: This script will uninstall Wine and related components, directories, caches and configurations COMPLETELY." echo echo -n "Do you want to continue? [Y/N]: " while true; do read -n1 confirm echo if [[ -z "$confirm" || "$confirm" =~ ^[Yy]$ ]]; then break elif [[ "$confirm" =~ ^[Nn]$ ]]; then echo "❌ Operation cancelled." exit 1 else echo "⚠️ Invalid option. Please press Y or N." fi done clear
# Remove wine directory, including "drive_c" and its contents. WINEPREFIX="/root/.wine" winetricks --unattended annihilate WINEPREFIX="$HOME/.wine" winetricks --unattended annihilate
sudo rm -rf "/root/.wine" rm -rf "$HOME/.wine" rm -rf "$HOME/.cache/winetricks"
# Remove packages that are part of Wine or strictly related to. sudo apt-get remove --purge -y wine wine32 wine64 libwine fonts-wine \ q4wine winetricks wine-binfmt
# Remove auxiliary packages that are not strictly tied to Wine but are "safe" to remove. sudo apt-get remove --purge -y binfmt-support winbind \ samba-common samba-common-bin samba-dsdb-modules
# Additional package cleanup. sudo apt-get autoremove -y sudo apt-get clean
echo -e "\n🏁 Operation completed." read -n1 -r -s -p "Press any key to finish..." echo exit 0 ⚠️ Este script de desinstalación eliminará por completo el directorio de Windows (~/.wine) y todo lo que haya en él, sin embargo, no revierte por completo todos los paquetes instalados previamente por el script del instalador de Wine, en parte por que me parece peligroso forzar la desinstalación de ciertos paquetes relacionados con gtk que se instalaron por parte del paquete q4wine con el parámetro --install-recommends, entre otros paquetes recomendados que se instalan relacionados con Python, ya que todo eso podría depender de otros paquetes que el usuario tuviera instalado anteriormente, así que solo desinstala los paquetes estrictamente relacionados con Wine y los que considero que prácticamente nadie estará utilizando para otras cosas: binfmt-support, winbind, samba*Revisen los paquetes que este script desinstala antes de usarlo. No me hago responsable.
|
|
|
32
|
Programación / Scripting / [APORTE] [Bash] (Linux) VMWare: Montar / Desmontar 'Shared Folders'
|
en: 4 Junio 2025, 21:09 pm
|
Los siguientes dos scripts, desarrollados en el lenguaje Bash, y que se deben usar bajo un sistema operativo Linux en una máquina virtual de VMWare (aunque se pueden modificar para Virtual Box), sirven como atajo para montar y desmontar las "carpetas compartidas" (shared folders) de forma sencilla. Utilizar estos scripts nos ahorra un valioso tiempo al no tener que usar comandos específicos en la distro de Linux para llevar a cabo el montaje de las carpetas compartidas de VMWare y de forma repetitiva (ya que el montaje no es persistente). La idea tras esta simple herramienta es eso, ahorrar tiempo, y de esta manera, mediante el script de desmontaje, poder aislar la máquina virtual del sistema operativo anfitrión impidiendo el acceso a las carpetas compartidas, y volver a habilitar el acceso, tan solo haciendo dos clicks para ejecutar estos scripts.  Estos scripts son un equivalente de estos otros para Windows: — Tema: [APORTE] [VBS] VMWare: Mount / Unmount Shared Folders Network DriveSin embargo, en esta ocasión estos scripts que comparto están programados en el idioma español, e incluyen emojis. Para ser honestos yo no tengo experiencia con Bash, así que básicamente lo ha hecho casi todo ChatGPT, yo solo le di unas pinceladas. Mount_VMWare_Shared_Folders.sh#!/bin/bash
# Carpeta a montar: VMWare Shared Folders (".host:/" = todas las carpetas) # Destino: "/mnt/shared_folders" MOUNT_SOURCE=".host:/" MOUNT_DESTINATION="/mnt/shared_folders"
# ========================================= # Montar las carpetas compartidas de VMWare # =========================================
# Crear /mnt/shared_folders si no existe if [ ! -d "$MOUNT_DESTINATION" ]; then echo "📂 Creando carpeta $MOUNT_DESTINATION... 🔥" sudo mkdir -p "$MOUNT_DESTINATION" fi
# Verificar si ya está montado en /mnt/shared_folders if mountpoint -q "$MOUNT_DESTINATION"; then echo "⚠️ $MOUNT_DESTINATION ya está montado." else echo "🔗 Montando carpeta compartida VMWare en $MOUNT_DESTINATION..." sudo vmhgfs-fuse "$MOUNT_SOURCE" "$MOUNT_DESTINATION" -o allow_other if [ $? -ne 0 ]; then echo "❌ Error montando la carpeta compartida. Abortando... 💔" exit 1 fi echo "✅ Montaje exitoso de carpeta compartida VMWare en $MOUNT_DESTINATION." fi
# ==================================================================== # Crear enlace simbólico de las carpetas compartidas, en el escritorio # ====================================================================
# Detectar ruta del escritorio usando user-dirs.dirs (XDG estándar) DESKTOP_DIR=""
# Leer archivo de configuración XDG si existe if [ -f "$HOME/.config/user-dirs.dirs" ]; then DESKTOP_DIR=$(grep XDG_DESKTOP_DIR "$HOME/.config/user-dirs.dirs" | cut -d= -f2 | tr -d \") fi
# Si no lo encontró o está vacío, probar las carpetas comunes if [ -z "$DESKTOP_DIR" ]; then if [ -d "$HOME/Desktop" ]; then DESKTOP_DIR="$HOME/Desktop" elif [ -d "$HOME/Escritorio" ]; then DESKTOP_DIR="$HOME/Escritorio" else echo "⚠️ No se pudo detectar la ubicación de la carpeta Desktop / Escritorio. Abortando... 😢" exit 1 fi else # Eliminar $HOME si está en la ruta para expandirla bien DESKTOP_DIR="${DESKTOP_DIR/#\$HOME/$HOME}" fi
# Definir origen y destino LINK_DESTINATION="$DESKTOP_DIR/VMWare Shared Folders"
# Crear enlace simbólico solo si no existe ya. if [ -L "$LINK_DESTINATION" ] || [ -e "$LINK_DESTINATION" ]; then echo "⚠️ El enlace o archivo '$LINK_DESTINATION' ya existe. No se creará un nuevo enlace." else ln -s "$MOUNT_DESTINATION" "$LINK_DESTINATION" echo "✅ Enlace simbólico creado en: $LINK_DESTINATION 🎉" fi
echo -e "\n🏁 Operación finalizada." Dismount_VMWare_Shared_Folders.sh#!/bin/bash
# Ruta de montaje de las carpetas compartidas de VMware MOUNT_DESTINATION="/mnt/shared_folders"
echo "🔍 Verificando si está montado $MOUNT_DESTINATION..."
if mountpoint -q "$MOUNT_DESTINATION"; then echo "📤 Desmontando carpeta compartida de VMware de: $MOUNT_DESTINATION..." sudo umount "$MOUNT_DESTINATION" if [ $? -eq 0 ]; then echo "✅ Carpeta compartida desmontada correctamente. 🎉" else echo "❌ Error al desmontar $MOUNT_DESTINATION. ¿Está en uso? 💔" exit 1 fi else echo "⚠️ $MOUNT_DESTINATION no está montado actualmente." if [ -d "$MOUNT_DESTINATION" ]; then echo "ℹ️ La carpeta existe, pero no contiene un punto de montaje activo." else echo "🚫 La carpeta $MOUNT_DESTINATION no existe." fi fi
echo -e "\n🏁 Operación finalizada." PD: parece que SMF no se lleva bien con los emojis, pierden el formato al usar la etiqueta "Bash" en el bloque de código.
|
|
|
33
|
Foros Generales / Foro Libre / Re: Araña
|
en: 4 Junio 2025, 13:11 pm
|
La próxima vez ayudaría enormemente que digas DE DÓNDE ERES O EN QUÉ PARTE DEL MUNDO HAS VISTO ESA ARAÑA, así se puede identificar mejor y descartar del tirón muchas especies de arañas. Cuando yo era un crío, vi alguna así por mi casa muy similar a la de tu foto, con la diferencia de que tenía un patrón de rayas negras en las patas. Época del Internet de 56k, no estaba de moda Googlear para investigar, así que le pregunté a amigos y familiares, y me dijeron que era una "araña tigre". En ese momento pensé que me estaban tomando el pelo... pero no, lo cierto es que se les conoce por ese nombre: — https://es.wikipedia.org/wiki/Scytodes_globulaLa araña de tu foto no creo que sea exactamente una araña tigre ( Scytodes globula), pero en mi opinión no hay lugar a dudas que debe ser una araña de la familia Scytodidae, por la forma de la "boca", las rayas negras en el abdomen con esa forma tan peculiar y los extremos oscuros en las patas. Debería tener 6 ojos para considerarse una Scytodidae, pero no lo veo bien en esa foto. De esta familia hay al menos 150 variantes/especies conocidas, y también se les conoce como arañas escupidoras 💦 por el motivo que se explica a continuación: Los escitódidos (Scytodidae) son una familia de arañas araneomorfas. La familia incluye cinco géneros y unas 150 especies distribuidas alrededor del mundo. Atrapan su presa lanzando un fluido que inmoviliza al contacto. Pueden ser observados moviéndose de lado a lado, para cubrir su alimento formando un patrón entrecruzado con forma de "Z"; uno de dos poros en los quelíceros emite la mitad del patrón.
Tal como las arañas Sicariidae y Diguetidae estas arañas son haploginas (carecen de órganos genitales femeninos endurecidos) y tienen seis ojos, que están ordenados en tres pares. Se diferencian de estos en tener un prosoma en forma de cúpula y un patrón característico de puntos, que a menudo se asemeja a la escritura árabe o china. Debido a eso, su forma de cazar, no suponen ningún peligro para los humanos. Aún así tienen quelíceros (las piezas bucales en forma de colmillo) con los que podrían morder la piel de un humano en una situación extrema donde sientan peligro, pero se supone que "no son agresivas, y su veneno es muy débil para los humanos." (fuente: ChatGPT)
Lo de tu foto creo que podría tratarse de una araña de la especie Scytodes univittata ya que concuerda más con el color grisaceo del abdomen: Fig 1. Scytodes univittata atrapada en una trampa adhesiva en Barcelona. También se puede ver un lepismátido (Ctenolepisma longicaudata) y algunas carcomas del pan (Stegobium paniceum)./ C. Pradera 11-2012 Lo de las tres imágenes es la misma especie de araña, Scytodes univittata. Hay que tener en cuenta que ver a una araña adulta y a una araña joven (ninfa) puede suponer suficiente diferencia en tamaño y en el color de su exoesqueleto (de ninfas pueden ser bastante más translúcido), y eso podría hacerla irreconocible en fotos de arañas que busquemos. Lo de tu foto no sé si será una ninfa o una adulta, no has mencionado el tamaño aproximado ni hay ningún objeto de referencia en la foto (como una moneda) para poder calcularlo. Tampoco sé si es macho o hembra, lo que podría suponer otras diferencias en tamaño y color. En definitiva, no soy entomólogo ni experto en arañas, pero me encanta intentar identificar bichos que descubro por primera vez (en el pasado formulé la misma pregunta que tú en este foro, en varias ocasiones, para identificar otros insectos), y nada, yo diría que lo de la foto es una Scytodes univittata, o sino, seguro otra araña de la familia Scytodidae. Lo primero que yo hice fue una búsqueda inversa en Google Images con (parte de) esa foto que has compartido, y todo apunta a que es una Scytodidae. Así que si sigues teniendo curiosidad puedes buscar más arañas de la familia Scytodidae en Google Images, las hay más claritas y más oscuras, con el abdomen más abombado y más estrecho, con más y menos rallas negras, hay de todo, 150 especies... a lo mejor ves alguna que se parezca más a la de tu foto. Atentamente, Elektro. EDITO:En torno al color reflejado en la pared, debe ser un fenómeno natural. Explicación de ChatGPT:     
|
|
|
34
|
Informática / Hardware / Re: Método "infalible" — bajo Linux — para poner a prueba la integridad de un disco.
|
en: 3 Junio 2025, 09:20 am
|
Le pedí a ChatGPT que desarrollase un script en Bash para automatizar la instalación de badblocks: #!/bin/bash # Script para instalar badblocks en Ubuntu/Debian # Actualizar repositorios sudo apt update # Instalar e2fsprogs que incluye badblocks sudo apt install -y e2fsprogs # Confirmar instalación if command -v badblocks > /dev/null; then echo "badblocks se ha instalado correctamente en Ubuntu/Debian." else echo "Error: badblocks no se pudo instalar." fi
Y también le pedí otro script que me permitiese elegir la unidad de disco donde usar badblocks, y que los parámetros del tamaño de sector el otro parámetro se ajustasen automáticamente analizando el disco elegido y el sistema en ejecución. Este fue el resultado: #!/bin/bash # Verificar si se está ejecutando como root if [ "$EUID" -ne 0 ]; then echo "Por favor, ejecuta este script como root (usando sudo)." exit 1 fi # Listar todos los discos disponibles echo "Discos disponibles en el sistema:" lsblk -d -o NAME,SIZE,MODEL # Solicitar al usuario que seleccione un disco read -p "Introduce el nombre del disco que deseas probar (por ejemplo, sda): " disk # Verificar si el disco existe if [ ! -b "/dev/$disk" ]; then echo "El disco /dev/$disk no existe. Por favor, verifica e intenta de nuevo." exit 1 fi # Determinar el tamaño de sector físico sector_size=$(cat /sys/block/$disk/queue/physical_block_size) # Determinar el tamaño de bloque para badblocks if [ "$sector_size" -eq 512 ]; then block_size=512 elif [ "$sector_size" -eq 4096 ]; then block_size=4096 else block_size=1024 # Valor predeterminado si no se puede determinar fi # Obtener la RAM total en GB (truncada a entero) ram_total_gb=$(free -g | awk '/^Mem:/{print $2}') # Determinar el valor adecuado para -c en función de la RAM # # El parámetro -c de badblocks (que indica cuántos bloques se prueban en cada pasada) # sí que debería ajustarse en función de la cantidad de RAM disponible, # especialmente si se ejecuta en modo destructivo (-w) # porque este modo consume mucha memoria, ya que mantiene múltiples patrones en memoria para comparar. if [ "$ram_total_gb" -lt 1 ]; then block_count=65536 # ~256MB elif [ "$ram_total_gb" -lt 2 ]; then block_count=131072 # ~512MB elif [ "$ram_total_gb" -lt 3 ]; then block_count=262144 # ~1GB elif [ "$ram_total_gb" -lt 4 ]; then block_count=524288 # ~2GB elif [ "$ram_total_gb" -lt 5 ]; then block_count=786432 # ~3GB elif [ "$ram_total_gb" -lt 6 ]; then block_count=1048576 # ~4GB elif [ "$ram_total_gb" -lt 7 ]; then block_count=1310720 # ~5GB elif [ "$ram_total_gb" -lt 8 ]; then block_count=1572864 # ~6GB elif [ "$ram_total_gb" -lt 9 ]; then block_count=1835008 # ~7GB elif [ "$ram_total_gb" -lt 10 ]; then block_count=2097152 # ~8GB elif [ "$ram_total_gb" -lt 11 ]; then block_count=2359296 # ~9GB else block_count=2621440 # ~10GB fi echo "RAM detectada: $ram_total_gb GB" echo "Usando block_count para badblocks: $block_count" # Mostrar la información al usuario echo echo "Has seleccionado el disco /dev/$disk." echo "Tamaño de sector físico: $sector_size bytes." echo "Tamaño de bloque para badblocks: $block_size bytes." echo "Memoria RAM detectada: $ram_total_gb GB" echo "Valor calculado para -c (bloques a probar por pasada): $block_count" echo # Confirmar antes de ejecutar badblocks read -p "¿Deseas ejecutar badblocks en modo destructivo en /dev/$disk? Esto borrará TODOS los datos del disco. (s/n): " confirm if [[ "$confirm" =~ ^[Ss]$ ]]; then echo "Ejecutando badblocks en /dev/$disk..." badblocks -wsv -b $block_size -c $block_count /dev/$disk echo "Prueba completada." else echo "Operación cancelada por el usuario." exit 0 fi
¡Tengo muchísimas ganas de probar este método intensivo con el próximo disco que me compre! No me hago responsable si el script no funciona correctamente o si causase daños en un disco. Yo no utilizo Linux, así que no tengo ni idea de programar en Bash ni conozco los comandos disponibles ni su modo de empleo, por eso le pedí a ChatGPT que hiciera el script por mi. 👍
|
|
|
35
|
Informática / Hardware / Método "infalible" — bajo Linux — para poner a prueba la integridad de un disco.
|
en: 3 Junio 2025, 09:06 am
|
¡Hola! Estaba navegando por eBay buscando un disco duro, y mientras leía las reseñas de un disco en específico, me topé con un comentario bastante interesante. Era de alguien que compartía lo que, según él, es una capa adicional de verificación muy confiable para comprobar la integridad de un disco, especialmente útil para saber si uno recién comprado es perfectamente funcional o por lo contrario será propenso a fallar. Se trata de una prueba intensiva que DEBE REALIZARSE BAJO LINUX (en Windows podríamos usar WSL con Ubuntu), y, aunque como con cualquier otra prueba no es posible garantizar al 100% que el disco no falle en el futuro por algún motivo, pero en teoría sí proporciona mucha fiabilidad adicional, especialmente como dije para discos recien comprados. Realizando esta prueba podriamos detectar con cierta fiabilidad si es un disco sin fallos de fábrica, o si por lo contrario deberíamos devolverlo lo antes posible durante el plazo de devolución. Si el disco que hemos comprado supera la prueba entonces podríamos asumir que está en excelente estado y que, al menos inicialmente, se puede depositar nuestra confianza en él con menor preocupación. Lo que acabo de decir solo son las conclusiones que he sacado de dicho comentario. Yo jamás había escuchado sobre esta prueba intensiva bajo Linux de 4 pasadas usando badblocks. Conozco herramientas para Windows como Victoria y otras tantas que realizan operaciones de escritura similar en el disco, pero le he preguntado a ChatGPT si existe un equivalente a badblocks para Windows y me dijo que no, que lo más confiable es seguir este procedimiento bajo Linux. ¿Qué es badblocks?, bueno, sigan leyendo: Primeramente les dejo aquí el comentario original en inglés tal cual está redactado: What I'm about to tell you will potentially save you from future catastrophic failures including loss of data. Nothing beats having multiple good backup copies.; however, the following information will instruct you on how to test your new drive to make sure it's free from defects. Imagine, you have a spinning disk rotating thousands of times per minute separated by its recording/reading head by less thickness of a human hair. Just one wrong thing and .. "click..click..click". You don't want that. Generally speaking a bad hard drive will make itself known sooner vs later when it's tested fully. Not always. What YOU can do is put the hard drive through its paces to really give it a good solid work out and make sure every space designated by the drive as available/writeable space, is in fact GOOD. Its magnetic/electrical properties strong. This test is a LONG test. What it does is write all 0s, 1s, 0-1s. 1-0s (binary) to the disk. It does this in 4 passes (i.e. writes the disk fully) 4 times. After each write it will read what was just written beginning to end to ensure all bits were written exactly right. If they wern't, the drive's detection mechanism for errors (SMART) will notice (i.e. a bit was not written to a sector correctly) and flag the SMART COUNTER. For example, uncorrectable or reallocated sector count would show a non-0 value. During operation, if the drive encounters a fault with a sector it will swap that sector with another known good sector the drive has in reserve and will log it via SMART. A PERFECTLY WORKING HARD DRIVE WILL SHOW '0' for 'Reallocated Sector Count','Reported Uncorrectable Errors','Current Pending Sector Count','Uncorrectable Sector Count'.IF ANY OF THESE VALUES SHOWS ANYTHING OTHER THAN A 0 (zero), THEN YOU HAVE A FAILING HDD AND SHOULD REPLACE IT ASAP. DO NOT TRUST YOUR DATA ON IT. This command will erase everything on the disk if there is data already on there when you run this test. For a 5TB it took me over 4 days. But I am confident the data will be safe because it worked out the drive hard and pictures above show good numbers. Boot up your computer using a linux distribution boot usb/cd. Debian Ubuntu, any one will do--it doesn't matter. In a terminal type: sudo fdisk -l *Identiy which /dev is your HDD you wish to test. Also note if it says 512 or 4096 where it says "Sector size (logical/physical)". For example, if it's /dev/sde and says 4096 Sector Size (such as this 5tb drive) then type the following command: sudo badblocks -wsv -b 4096 -c 131072 /dev/sde This will 0,1,01,10 write and read/verify the drive back-to-back in 4 passes. On my slow computer via USB 3, this took around 4.5 days to complete. The ' -b ' option is the Block Size. This should match the drive. 512 or 4096. The '-c' option tells badblocks how many blocks to test at time. Changing this number will alter the speed and will either make the process go along faster or slower (dependant on Drive Interface, Ram/Cpu speed, etc.). Instead of '-c 131072' ,try '-c 65536 if it seems the test is taking too long to increment the %'s. In fact try it with '-c 65536' option. Let it run or 30 sec then CTRL-C to cancel it. Then run the command again but with '-c 131072' instead. Run it for 30 sec then CTRL-C to cancel. Compare the 2 and whichever command that had the higher % when you CTRL-C'd would be the number you would choose since that one ran a little bit faster. Google or man page 'badblocks' for more information. Whenever you buy a new hard drive get into the mindset/habit that you will NOT use them for at least a week (or 2) because you have to test them out first. Trust me on this and it's totally worth it. I'd like to think I avoid catastrophic failures because I did this test and it would have presented itself (or at least more likelihood) then vs months/years down the road. That's not a given--any drive can fail anytime or any reason but this is just an additional layer. Like a "shakedown cruise' if you will. Regarding this drive: Seagate Portable 5TB External Hard Drive HDD – USB 3.0, I can say that I am pleased it tested clean and I look forward to using this drive.And I'm confident it will keep my data intact. You are welcome!
Y por último me despido dejándoles la traducción al español y con formato: Lo que estás a punto de leer podría salvarte de fallos catastróficos en el futuro, incluyendo la pérdida total de datos.Nada supera tener varias copias de seguridad bien hechas, pero la información que sigue te enseñará cómo probar tu nuevo disco duro para asegurarte de que no tiene defectos. Imagina esto: un disco girando miles de veces por minuto, separado de su cabezal de lectura/escritura por menos del grosor de un cabello humano. Basta un pequeño fallo para escuchar ese temido sonido: "clic... clic... clic". No quieres que eso pase. En términos generales, un disco duro defectuoso suele mostrar síntomas más temprano que tarde si se le somete a una prueba exhaustiva. No siempre, pero muchas veces sí. Lo que TÚ puedes hacer es someter el disco a una prueba intensiva que lo exprima al máximo, asegurándote de que cada espacio declarado como disponible y escribible sea efectivamente funcional, con propiedades magnéticas y eléctricas sólidas. Esta es una prueba LARGA.Lo que hace es escribir patrones binarios en el disco (todos 0s, luego 1s, luego 0-1s, y después 1-0s), en 4 pasadas completas. Después de cada escritura, el programa lee todo el contenido de principio a fin para verificar que los bits escritos estén correctos. Si algo falla, el sistema SMART del disco detectará errores (por ejemplo, que un bit no fue escrito correctamente en un sector) y marcará los contadores SMART. Algunos valores clave que deben ser igual a cero en un disco perfectamente funcional:- Reallocated Sector Count
- Reported Uncorrectable Errors
- Current Pending Sector Count
- Uncorrectable Sector Count
Si cualquiera de estos valores es distinto de 0, el disco está fallando y deberías reemplazarlo cuanto antes. NO confíes tus datos a él.⚠️ Advertencia: Este test borra todo lo que haya en el disco. Si tiene información, se perderá. En mi caso, con un disco de 5 TB, la prueba tomó más de 4 días. Pero ahora tengo confianza de que los datos estarán seguros, porque el disco superó la prueba con éxito. Pasos para realizar la prueba: Arranca tu computadora usando un Live USB/CD con alguna distribución de Linux (Debian, Ubuntu, cualquiera sirve). Abre un terminal y escribe: Identifica cuál es tu disco (por ejemplo, /dev/sde) y fíjate en el tamaño del sector lógico/físico (512 o 4096). Ejemplo: Si tu disco es /dev/sde y usa sectores de 4096, ejecuta: sudo badblocks -wsv -b 4096 -c 131072 /dev/sde Este comando escribirá los patrones binarios (0,1,01,10), luego los leerá y verificará, durante 4 pasadas completas. En mi caso, vía USB 3.0 en un equipo lento, tomó aproximadamente 4.5 días. Explicación de opciones: → Tamaño del bloque. Debe coincidir con el tamaño de bloque del disco (512 o 4096). → Cuántos bloques probar a la vez. Afecta la velocidad de la prueba. Si ves que va lento con -c 131072, prueba con: Puedes comparar la velocidad de ambos con CTRL+C tras 30 segundos y elegir el que haya avanzado más en porcentaje. Para más información, busca en Google o consulta la man page de badblocks. Consejo final:Cada vez que compres un disco nuevo, acostúmbrate a no usarlo por al menos una semana o dos. Primero, ¡ponlo a prueba! Créeme, vale la pena. Me gusta pensar que evité fallos catastróficos gracias a esta prueba. Es una capa adicional de protección, como un “viaje de prueba” antes de confiarle tus datos. En cuanto a este modelo específico:Seagate Portable 5TB External Hard Drive HDD – USB 3.0Puedo decir que estoy satisfecho. Pasó la prueba sin errores y estoy tranquilo sabiendo que mis datos estarán bien protegidos en él. ¡De nada! 😊
|
|
|
36
|
Programación / Scripting / [APORTE] [PowerShell] Montar y desmontar una unidad virtual de CD/DVD/BD o VHD/VHDX/VHDS
|
en: 2 Junio 2025, 02:17 am
|
El siguiente script, desarrollado en PowerShell, sirve para montar y desmontar una unidad virtual de CD/DVD/BD con el archivo de imagen ISO especificado. Opcionalmente se le podría dar el mismo uso para montar discos virtuales en formato .vhd, .vhdx y .vhds Desarrollé este script para evitar tener que recurrir a software de terceros (ej. ImDisk) en escenarios donde necesito automatizar el montaje de archivos ISO, sin interacción humana y sin dependencias externas. El script no tiene parámetros configurables. El archivo ISO a montar y desmontar está establecido directamente en el código fuente, en la siguiente línea: $isoPath = Join-Path $PSScriptRoot "CD-ROM.iso" Si se requiere que el script no interrumpa, solo hay que comentar las dos líneas que llaman a las funciones Confirm-Continue y Show-GoodbyeScreen: ... Show-WelcomeScreen # Confirm-Continue Mount-ISO # Show-GoodbyeScreen
Mount virtual drive.ps1 <#PSScriptInfo .VERSION 1.0 .GUID 14688e75-298c-1112-a2d0-1234567890aa .AUTHOR ElektroStudios .COMPANYNAME ElektroStudios .COPYRIGHT ElektroStudios © 2025 #> <# =========================================================================================== | | | Variables | | | =========================================================================================== #> $isoPath = Join-Path $PSScriptRoot "CD-ROM.iso" <# =========================================================================================== | | | Functions | | | =========================================================================================== #> function Show-WelcomeScreen { Clear-Host Write-Host "" Write-Host " $($host.ui.RawUI.WindowTitle)" Write-Host " +==================================================+" Write-Host " | |" Write-Host " | This script will mount a virtual drive with the |" Write-Host " | ISO file of the specified path. |" Write-Host " | |" Write-Host " +=================================================+" Write-Host "" Write-Host " Script Settings " -ForegroundColor DarkGray Write-Host " ===========================" -ForegroundColor DarkGray Write-Host " ISO File: $([System.IO.Path]::GetFullPath($isoPath))" -ForegroundColor Gray Write-Host "" } function Confirm-Continue { Write-Host " Press 'Y' key to continue or 'N' to exit." Write-Host "" Write-Host " -Continue? (Y/N)" do { $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") $char = $key.Character.ToString().ToUpper() if ($char -ne "Y" -and $char -ne "N") { [console]::beep(1500, 500) } } while ($char -ne "Y" -and $char -ne "N") if ($char -eq "N") {Exit(1)} else {Clear -Host } } function Mount-ISO { if (Test-Path $isoPath) { try { $fullPath = (Resolve-Path $isoPath).Path $alreadyMounted = $null try { $alreadyMounted = Get-DiskImage -ImagePath $fullPath -ErrorAction Stop } catch { } if ($alreadyMounted -and $alreadyMounted.Attached) { Write-Host "ISO is already mounted. Skipping mount." -ForegroundColor Cyan } else { try { $image = Mount -DiskImage -ImagePath $fullPath -PassThru -ErrorAction Stop Write-Host "Virtual drive has been mounted successfully." -ForegroundColor Green } catch { Write-Host "Error while mounting the ISO:" -ForegroundColor Red Write-Host $_.Exception.Message -ForegroundColor Yellow } } } catch { Write-Host "Failed to mount the virtual drive." -ForegroundColor Red Write-Host $_.Exception.Message -ForegroundColor Yellow } } else { Write-Warning "ISO file not found at: $isoPath" } } function Show-GoodbyeScreen { Write-Host "Operation Completed!" -BackgroundColor Black -ForegroundColor Green Write-Host "" Write-Host "Press any key to exit..." $key = $Host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown") } <# =========================================================================================== | | | Main | | | =========================================================================================== #> [System.Console ]::Title = "Mount ISO file - by Elektro" #[System.Console]::SetWindowSize(150, 45) [CultureInfo]::CurrentUICulture = "en-US" try { Set-ExecutionPolicy -ExecutionPolicy "Unrestricted" -Scope "Process" } catch { } Show-WelcomeScreen Confirm-Continue Mount-ISO Show-GoodbyeScreen
Dismount virtual drive.ps1 <#PSScriptInfo .VERSION 1.0 .GUID 14688e75-298c-1112-a2d0-1234567890ab .AUTHOR ElektroStudios .COMPANYNAME ElektroStudios .COPYRIGHT ElektroStudios © 2025 #> <# =========================================================================================== | | | Variables | | | =========================================================================================== #> $isoPath = Join-Path $PSScriptRoot "CD-ROM.iso" <# =========================================================================================== | | | Functions | | | =========================================================================================== #> function Show-WelcomeScreen { Clear-Host Write-Host "" Write-Host " $($host.ui.RawUI.WindowTitle)" Write-Host " +======================================================+" Write-Host " | |" Write-Host " | This script will dismount a virtual drive previously |" Write-Host " | mounted with the ISO file of the specified path. |" Write-Host " | |" Write-Host " +======================================================+" Write-Host "" Write-Host " Script Settings " -ForegroundColor DarkGray Write-Host " ===========================" -ForegroundColor DarkGray Write-Host " ISO File: $([System.IO.Path]::GetFullPath($isoPath))" -ForegroundColor Gray Write-Host "" } function Confirm-Continue { Write-Host " Press 'Y' key to continue or 'N' to exit." Write-Host "" Write-Host " -Continue? (Y/N)" do { $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") $char = $key.Character.ToString().ToUpper() if ($char -ne "Y" -and $char -ne "N") { [console]::beep(1500, 500) } } while ($char -ne "Y" -and $char -ne "N") if ($char -eq "N") {Exit(1)} else {Clear -Host } } function Dismount-ISO { if (Test-Path $isoPath) { try { $fullPath = (Resolve-Path $isoPath).Path $alreadyMounted = $null try { $alreadyMounted = Get-DiskImage -ImagePath $fullPath -ErrorAction Stop } catch { } if ($alreadyMounted -and $alreadyMounted.Attached) { try { Dismount-DiskImage -ImagePath $fullPath -ErrorAction Stop Write-Host "Virtual drive has been dismounted successfully." -ForegroundColor Green } catch { Write-Host "Error while dismounting the virtual drive:" -ForegroundColor Red Write-Host $_.Exception.Message -ForegroundColor Yellow } } else { Write-Host "No virtual drive mounted for this ISO. Nothing to dismount." -ForegroundColor Cyan } } catch { Write-Host "Failed to dismount the virtual drive." -ForegroundColor Red Write-Host $_.Exception.Message -ForegroundColor Yellow } } else { Write-Warning "ISO file not found at: $isoPath" } } function Show-GoodbyeScreen { Write-Host "Operation Completed!" -BackgroundColor Black -ForegroundColor Green Write-Host "" Write-Host "Press any key to exit..." $key = $Host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown") } <# =========================================================================================== | | | Main | | | =========================================================================================== #> [System.Console ]::Title = "Dismount ISO file - by Elektro" #[System.Console]::SetWindowSize(150, 45) [CultureInfo]::CurrentUICulture = "en-US" try { Set-ExecutionPolicy -ExecutionPolicy "Unrestricted" -Scope "Process" } catch { } Show-WelcomeScreen Confirm-Continue Dismount-ISO Show-GoodbyeScreen
|
|
|
37
|
Programación / .NET (C#, VB.NET, ASP) / Re: Librería de Snippets para VB.NET !! (Compartan aquí sus snippets)
|
en: 30 Mayo 2025, 18:11 pm
|
Librería .NET para automatizar el uso de rar.exe de RARLab (WinRAR)Esto es un wrapper .NET completo para la herramienta por línea de comandos rar.exe oficial de RARLab. Esta librería permite a los desarrolladores de .NET acceder y controlar fácilmente casi todas las funciones de rar.exe, como comprimir, extraer, listar, testar, crear volúmenes de recuperación y administrar archivos RAR, desde sus aplicaciones. Llevaba mucho tiempo queriendo hacer esto, bastantes años hace que se me ocurrió la idea por que hay infinidad de librerías disponibles en la mayoría de lenguajes de programación para manejar formatos ZIP y 7Zip entre otros muchos, pero para el formato RAR, en especial el formato RARv5 son muy escasas... probablemente por desinterés y/o por ser un formato privativo. Lo cierto es que no conozco ningún otro wrapper del executable rar.exe, ni tampoco un wrapper que en lugar de depender de rar.exe haga llamadas nativas a una librería oficial de RARLab. El caso es que nunca empecé este proyecto por simple pereza. Me parecía muy interesante pero al mismo tiempo no me resultaba necesario en realidad desarrollar una infraestructura completa para configurar el lanzamiento del proceso de rar.exe pasándole un comando cualquiera, cosa que podía escribir en una sola línea como esta: Process.Start(".\rar.exe", "argumentos") — pero claro, esto es algo muy codificado en el código por así decirlo, no es tan bonito o elegante o profesional como configurar una sofisticada clase para construir los argumentos y controlar el lanzamiento del proceso con sus códigos de salida y demás. Así que siempre lo estuve aplazando. Y cuando finalmente decidí empezar, hace cosa de unas semanas, esperaba poder compartirlo con ustedes en formato de "snippet" en este hilo, es decir algo de un tamaño reducido, pero fui demasiado ingenuo ya que al final el trabajo ha alcanzado la cantidad de 9 clases para representar diversos comandos, 17 enumeraciones y otras tantas clases para otros elementos integrados, haciendo un total de 37 archivos separados de código fuente. Así que no me ha quedado más remedio que compartirlo en GitHub, y aquí lo comparto con ustedes: 👉 📦 RARLab's rar.exe .NET Wrapper LibraryComo he dicho, esto es una librería, un archivo dll para administrar el uso del archivo rar.exe. Es totalmente "universal", se puede usar en proyectos de VB.NET o de C#, bajo .NET Framework o NET 5.0+ (el proyecto habría que migrarlo), no hay dependencias más allá del archivo 'rar.exe' de WinRAR y la licencia del producto, que deben ser administrados por el usuario.
El README.md del repositorio en GitHub incluye un ejemplo de uso para VB.NET y también para C#. Además, en el código fuente de todas las nueve clases que representan los 'Comandos', incluyen un apartado, arriba del todo de la clase, con un ejemplo de uso para VB.NET, y también en la propia documentación XML de la clase. De todas formas, aquí les dejo un ejemplo de uso completo para VB.NET — Construir un Comando para la creación de archivos RAR utilizando la clase RarCreationCommand:Imports DevCase.RAR Imports DevCase.RAR.Commands
Dim archivePath As String = "C:\New Archive.rar" Dim filesToAdd As String = "C:\Directory to add\" Dim command As New RarCreationCommand(RarCreationMode.Add, archivePath, filesToAdd) With { .RarExecPath = ".\rar.exe", .RarLicenseData = "(Your license key)", .RecurseSubdirectories = True, .EncryptionProperties = Nothing, .SolidCompression = False, .CompressionMode = RarCompressionMode.Normal, .DictionarySize = RarDictionarySize.Mb__128, .OverwriteMode = RarOverwriteMode.Overwrite, .FilePathMode = RarFilePathMode.ExcludeBaseDirFromFileNames, .FileTimestamps = RarFileTimestamps.All, .AddQuickOpenInformation = TriState.True, .ProcessHardLinksAsLinks = True, .ProcessSymbolicLinksAsLinks = TriState.True, .DuplicateFileMode = RarDuplicateFileMode.Enabled, .FileChecksumMode = RarFileChecksumMode.BLAKE2sp, .ArchiveComment = New RarArchiveComment("Hello world!"), .RecoveryRecordPercentage = 0, .VolumeSplitOptions = Nothing, .FileTypesToStore = Nothing }
— Para obtener los argumentos completos de la línea de comandos de nuestro Comando:Console.WriteLine($"Command-line arguments: {command}")
MessageBox.Show(command.ToString())
— Ejecutar nuestro Comando usando la clase RarCommandExecutor:Using rarExecutor As New RarCommandExecutor(command) AddHandler rarExecutor.OutputDataReceived, Sub(sender As Object, e As DataReceivedEventArgs) Console.WriteLine($"[Output] {Date.Now:yyyy-MM-dd HH:mm:ss} - {e.Data}") End Sub AddHandler rarExecutor.ErrorDataReceived, Sub(sender As Object, e As DataReceivedEventArgs) If e.Data IsNot Nothing Then Console.WriteLine($"[Error] {Date.Now:yyyy-MM-dd HH:mm:ss} - {e.Data}") End If End Sub AddHandler rarExecutor.Exited, Sub(sender As Object, e As EventArgs) Dim pr As Process = DirectCast(sender, Process) Dim rarExitCode As RarExitCode = DirectCast(pr.ExitCode, RarExitCode) Console.WriteLine($"[Exited] {Date.Now:yyyy-MM-dd HH:mm:ss} - rar.exe process has terminated with exit code {pr.ExitCode} ({rarExitCode})") End Sub Dim exitcode As RarExitCode = rarExecutor.ExecuteRarAsync().Result End Using
|
|
|
38
|
Programación / .NET (C#, VB.NET, ASP) / Re: Librería de Snippets para VB.NET !! (Compartan aquí sus snippets)
|
en: 30 Mayo 2025, 01:41 am
|
Cuatro métodos de extensión para el tipo DateTime con los que obtener una representación de fecha o fecha y hora realmente amistosa.Ejemplos: Fecha en inglés: 11 May, 2025 Fecha en español: 11 de mayo de 2025 Fecha en alemán: 11. Mai 2025 Fecha y hora en inglés: 11 May, 2025 at 23:59:59 Fecha y hora en español: 11 de mayo de 2025 a las 23:59:59 No soy experto en la representación por escrito de fechas en otros idiomas así que solo intenté perfeccionar estos tres. La representación en alemán estaría bien según me he informado, ya que se supone que se añade un punto de esa forma. En definitiva, creo que para ir tirando está bien así. ''' <summary> ''' Converts a <see cref="Date"/> object to a long friendly date string based on the current culture. ''' <para></para> ''' For example: ''' <list type="bullet"> ''' <item><description>English<para></para>11 May, 2025</description></item> ''' <item><description>Spanish<para></para>11 de mayo de 2025</description></item> ''' <item><description>German<para></para>11. Mai 2025</description></item> ''' </list> ''' </summary> ''' ''' <param name="[date]"> ''' The <see cref="Date"/> object to be formatted. ''' </param> ''' ''' <returns> ''' A string representing the formatted date, based on the current culture. ''' </returns> <DebuggerStepThrough> <Extension> <EditorBrowsable(EditorBrowsableState.Always)> Public Function ToLongFriendlyDateString([date] As Date) As String Return DateExtensions.ToLongFriendlyDateString([date], CultureInfo.CurrentCulture) End Function ''' <summary> ''' Converts a <see cref="Date"/> object to a long friendly date string based on the specified culture. ''' <para></para> ''' For example: ''' <list type="bullet"> ''' <item><description>English<para></para>11 May, 2025</description></item> ''' <item><description>Spanish<para></para>11 de mayo de 2025</description></item> ''' <item><description>German<para></para>11. Mai 2025</description></item> ''' </list> ''' </summary> ''' ''' <param name="[date]"> ''' The <see cref="Date"/> object to be formatted. ''' </param> ''' ''' <param name="provider"> ''' The culture information used to format the date. ''' </param> ''' ''' <returns> ''' A string representing the formatted date, based on the specified culture. ''' </returns> <DebuggerStepThrough> <Extension> <EditorBrowsable(EditorBrowsableState.Always)> Public Function ToLongFriendlyDateString([date] As Date, provider As IFormatProvider) As String Dim culture As CultureInfo = TryCast(provider, CultureInfo) If culture IsNot Nothing Then Select Case culture.TwoLetterISOLanguageName.ToLower() Case "es", "ca", "gl", "pt" ' Spanish, Catalonian, Galego, Portuguese Return [date].ToString("dd 'de' MMMM 'de' yyyy", provider) Case "de" ' Deutsch Return [date].ToString("dd'.' MMMM yyyy", provider) Case Else ' Do nothing. Exit Select End Select End If Return [date].ToString("dd MMMM, yyyy", provider) End Function ''' <summary> ''' Converts a <see cref="Date"/> object to a long friendly date string based on the current culture. ''' <para></para> ''' For example: ''' <list type="bullet"> ''' <item><description>English<para></para>11 May, 2025 at 23:59:59</description></item> ''' <item><description>Spanish<para></para>11 de mayo de 2025 a las 23:59:59</description></item> ''' </list> ''' </summary> ''' ''' <param name="[date]"> ''' The <see cref="Date"/> object to be formatted. ''' </param> ''' ''' <returns> ''' A string representing the formatted date, based on the current culture. ''' </returns> <DebuggerStepThrough> <Extension> <EditorBrowsable(EditorBrowsableState.Always)> Public Function ToLongFriendlyDateAndTimeString([date] As Date) As String Return DateExtensions.ToLongFriendlyDateAndTimeString([date], CultureInfo.CurrentCulture) End Function ''' <summary> ''' Converts a <see cref="Date"/> object to a long friendly date string based on the specifies culture. ''' <para></para> ''' For example: ''' <list type="bullet"> ''' <item><description>English<para></para>11 May, 2025 at 23:59:59</description></item> ''' <item><description>Spanish<para></para>11 de mayo de 2025 a las 23:59:59</description></item> ''' </list> ''' </summary> ''' ''' <param name="[date]"> ''' The <see cref="Date"/> object to be formatted. ''' </param> ''' ''' <param name="provider"> ''' The culture information used to format the date. ''' </param> ''' ''' <returns> ''' A string representing the formatted date, based on the specified culture. ''' </returns> <DebuggerStepThrough> <Extension> <EditorBrowsable(EditorBrowsableState.Always)> Public Function ToLongFriendlyDateAndTimeString([date] As Date, provider As IFormatProvider) As String Dim culture As CultureInfo = TryCast(provider, CultureInfo) If culture IsNot Nothing Then Select Case culture.TwoLetterISOLanguageName.ToLower() Case "en" ' English Return [date].ToString($"'{ToLongFriendlyDateString([date], provider)}' 'at' HH:mm:ss", provider) Case "es" ' Spanish Return [date].ToString($"'{ToLongFriendlyDateString([date], provider)}' 'a las' HH:mm:ss", provider) Case Else ' Do nothing. Exit Select End Select End If Return [date].ToString($"'{ToLongFriendlyDateString([date], provider)}' '—' HH:mm:ss", provider) End Function
|
|
|
39
|
Programación / .NET (C#, VB.NET, ASP) / Re: Librería de Snippets para VB.NET !! (Compartan aquí sus snippets)
|
en: 30 Mayo 2025, 01:15 am
|
Cómo iniciar el programa warp-cli.exe de la VPN de Cloudflare Warp pasándole un comandoUn simple método para ejecutar el programa warp-cli.exe de la VPN de Cloudflare Warp pasándole un comando (ej. "connect", "disconnect", etc): ''' <summary> ''' Sends a custom command to the Cloudflare Warp CLI executable (<c>warp-cli.exe</c>) ''' and captures both standard output and error output. ''' </summary> ''' ''' <param name="command"> ''' The command-line argument to be passed to warp-cli executable. ''' For example: <c>connect</c>, <c>disconnect</c>, <c>status</c>, etc. ''' </param> ''' ''' <param name="refOutput"> ''' Returns the standard output returned by the warp-cli process. ''' </param> ''' ''' <param name="refErrorOutput"> ''' Returns the standard error output returned by the warp-cli process. ''' </param> ''' ''' <param name="warpCliFilePath"> ''' Optional path to the warp-cli executable. If <c>Nothing</c> is specified, the method defaults to: ''' <c>%ProgramFiles%\Cloudflare\Cloudflare Warp\warp-cli.exe</c>. ''' </param> ''' ''' <returns> ''' The exit code returned by the warp-cli process. A value of 0 typically indicates success. ''' </returns> ''' ''' <exception cref="System.IO.FileNotFoundException"> ''' Thrown if the specified or default warp-cli executable file is not found on the system. ''' </exception> ''' ''' <exception cref="System.TimeoutException"> ''' Thrown if the warp-cli process takes longer than 60 seconds to complete. ''' </exception> ''' ''' <exception cref="System.Exception"> ''' Thrown if any other unexpected error occurs while attempting to execute the warp-cli process. ''' The original exception is wrapped as the inner exception. ''' </exception> <DebuggerStepThrough> Public Shared Function CloudflareWarpCliSendCommand(command As String, ByRef refOutput As String, ByRef refErrorOutput As String, Optional warpCliFilePath As String = Nothing) As Integer ' Prevents concurrent execution of the method from multiple threads within the same process. ' This static lock object ensures that only one thread can execute the critical section at a time, ' avoiding race conditions or conflicts when invoking the Warp CLI. Static WarpCliLock As New Object() Static spaceChar As Char = " "c SyncLock WarpCliLock If String.IsNullOrEmpty(warpCliFilePath) Then warpCliFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Cloudflare\Cloudflare Warp\warp-cli.exe") End If Try If Not System. IO. File. Exists(warpCliFilePath ) Then Throw New System.IO.FileNotFoundException("The Warp CLI executable was not found.", warpCliFilePath) End If Using pr As New Process() pr.StartInfo.FileName = warpCliFilePath pr.StartInfo.Arguments = command pr.StartInfo.UseShellExecute = False pr.StartInfo.CreateNoWindow = True pr.StartInfo.RedirectStandardOutput = True pr.StartInfo.RedirectStandardError = True pr.Start() If Not pr.WaitForExit(60000) Then ' Waits a maximum of 60 seconds pr.Kill() Throw New TimeoutException("warp-cli process has timed out.") End If refOutput = pr.StandardOutput.ReadToEnd().Trim(Environment.NewLine.ToCharArray().Concat({spaceChar}).ToArray()) refErrorOutput = pr.StandardError.ReadToEnd().Trim(Environment.NewLine.ToCharArray().Concat({spaceChar}).ToArray()) Return pr.ExitCode End Using Catch ex As Exception Throw New Exception($"Failed to execute warp-cli process. See inner exception for details.", ex) End Try End SyncLock End Function
Casos de uso reales: para conectar a la red de Cloudflare WARP en cualquier proyecto donde hagamos solicitudes http, por ejemplo en un web-crawler, a sitios web con riesgo de que puedan acabar bloqueando nuestra IP. Tres métodos de extensión para dibujar texto sobre una imagen, o dibujar encima otra imagen (con capacidad opcional de usar transparencia) o un valor numérico, con una escala proporcional a la imagen y en una posición alineada a cualquiera de las esquinas o zonas centrales de la imagen (centro absoluto, parte superior central o parte inferior central) de la imagen.Demostración de resultado de la extensión que dibuja un valor numérico (de forma alineada a la esquina inferior derecha de la imagen):  ''' <summary> ''' Draws an overlay image onto the specified <see cref="Image"/> at a given position and scale factor. ''' </summary> ''' ''' <param name="refImg"> ''' The source <see cref="Image"/> to modify. The overlay image will be drawn directly on this image. ''' </param> ''' ''' <param name="overlayImg"> ''' The overlay image to draw on the source <paramref name="refImg"/>. ''' </param> ''' ''' <param name="scale"> ''' The relative image scale factor to determine overlay image size. Lower values increase the size of the overlay image. ''' </param> ''' ''' <param name="position"> ''' The position/alignment where the overlay image should be drawn (e.g., bottom-right). ''' </param> ''' ''' <param name="margin"> ''' The margin (in pixels) from the edge of the image to position the overlay image. ''' <para></para> ''' This value has different meaning depending on <paramref name="position"/> parameter: ''' <para></para> ''' <list type="bullet"> ''' <item> ''' <term> ''' <see cref="ContentAlignment.TopLeft"/>, <see cref="ContentAlignment.TopRight"/>, ''' <para></para> ''' <see cref="ContentAlignment.BottomLeft"/> and <see cref="ContentAlignment.BottomRight"/> ''' </term> ''' <description><para></para><paramref name="margin"/> specifies the diagonal offset.</description> ''' </item> ''' <item> ''' <term> ''' <see cref="ContentAlignment.MiddleLeft"/> and <see cref="ContentAlignment.MiddleRight"/> ''' </term> ''' <description><para></para><paramref name="margin"/> specifies the horizontal offset.</description> ''' </item> ''' <item> ''' <term> ''' <see cref="ContentAlignment.TopCenter"/> and <see cref="ContentAlignment.BottomCenter"/> ''' </term> ''' <description><para></para><paramref name="margin"/> specifies the vertical offset.</description> ''' </item> ''' <item> ''' <term> ''' <see cref="ContentAlignment.MiddleCenter"/> ''' </term> ''' <description><para></para><paramref name="margin"/> is ignored.</description> ''' </item> ''' </list> ''' </param> ''' ''' <param name="transparentColor"> ''' Optional. A <see cref="Color"/> to use as transparency to draw the overlay image. ''' </param> <DebuggerStepThrough> <Extension> <EditorBrowsable(EditorBrowsableState.Always)> Public Sub DrawImageScaled(ByRef refImg As Image, overlayImg As Image, scale As Single, position As ContentAlignment, margin As Single, Optional transparentColor As Color? = Nothing) If refImg Is Nothing Then Throw New ArgumentNullException(NameOf(refImg)) End If If overlayImg Is Nothing Then Throw New ArgumentNullException(NameOf(overlayImg)) End If If margin < 0 Then Throw New ArgumentOutOfRangeException(NameOf(margin), margin, "Margin must be greater than or equal to 0.") End If If scale < 1 Then Throw New ArgumentOutOfRangeException(NameOf(scale), scale, "Font scale must be greater than or equal to 1.") End If Using g As Graphics = Graphics.FromImage(refImg) g.SmoothingMode = SmoothingMode.AntiAlias g.InterpolationMode = InterpolationMode.HighQualityBicubic g.PixelOffsetMode = PixelOffsetMode.HighQuality g.CompositingQuality = CompositingQuality.HighQuality Dim targetSize As Single = Math.Max(refImg.Width, refImg.Height) / scale Dim aspectRatio As Single = CSng(overlayImg.Width / overlayImg.Height) Dim drawWidth As Single Dim drawHeight As Single If overlayImg.Width >= overlayImg.Height Then drawWidth = targetSize drawHeight = targetSize / aspectRatio Else drawHeight = targetSize drawWidth = targetSize * aspectRatio End If Dim posX As Single = 0 Dim posY As Single = 0 Select Case position Case ContentAlignment.TopLeft posX = margin posY = margin Case ContentAlignment.TopCenter posX = (refImg.Width - drawWidth) / 2 posY = margin Case ContentAlignment.TopRight posX = refImg.Width - drawWidth - margin posY = margin Case ContentAlignment.MiddleLeft posX = margin posY = (refImg.Height - drawHeight) / 2 Case ContentAlignment.MiddleCenter posX = (refImg.Width - drawWidth) / 2 posY = (refImg.Height - drawHeight) / 2 Case ContentAlignment.MiddleRight posX = refImg.Width - drawWidth - margin posY = (refImg.Height - drawHeight) / 2 Case ContentAlignment.BottomLeft posX = margin posY = refImg.Height - drawHeight - margin Case ContentAlignment.BottomCenter posX = (refImg.Width - drawWidth) / 2 posY = refImg.Height - drawHeight - margin Case ContentAlignment.BottomRight posX = refImg.Width - drawWidth - margin posY = refImg.Height - drawHeight - margin Case Else Throw New InvalidEnumArgumentException(NameOf(position), position, GetType(ContentAlignment)) End Select If transparentColor.HasValue Then Using attr As New Imaging.ImageAttributes() attr.SetColorKey(transparentColor.Value, transparentColor.Value) Dim destRect As New Rectangle(CInt(posX), CInt(posY), CInt(drawWidth), CInt(drawHeight)) g.DrawImage(overlayImg, destRect, 0, 0, overlayImg.Width, overlayImg.Height, GraphicsUnit.Pixel, attr) End Using Else g.DrawImage(overlayImg, posX, posY, drawWidth, drawHeight) End If End Using End Sub ''' <summary> ''' Draws text onto the specified <see cref="Image"/> at a given position and scale factor. ''' </summary> ''' ''' <param name="refImg"> ''' The <see cref="Image"/> to modify. The text will be drawn directly on this image. ''' </param> ''' ''' <param name="text"> ''' The text to draw on the image. ''' </param> ''' ''' <param name="scale"> ''' The relative image scale factor to determine font size. Lower values increase the size of the text. ''' <para></para> ''' Suggested value is from 10 to 20. ''' </param> ''' ''' <param name="position"> ''' The position/alignment where the text should be drawn (e.g., bottom-right). ''' </param> ''' ''' <param name="margin"> ''' The margin (in pixels) from the edge of the image to position the text. ''' <para></para> ''' This value has different meaning depending on <paramref name="position"/> parameter: ''' <para></para> ''' <list type="bullet"> ''' <item> ''' <term> ''' <see cref="ContentAlignment.TopLeft"/>, <see cref="ContentAlignment.TopRight"/>, ''' <para></para> ''' <see cref="ContentAlignment.BottomLeft"/> and <see cref="ContentAlignment.BottomRight"/> ''' </term> ''' <description><para></para><paramref name="margin"/> specifies the diagonal offset.</description> ''' </item> ''' <item> ''' <term> ''' <see cref="ContentAlignment.MiddleLeft"/> and <see cref="ContentAlignment.MiddleRight"/> ''' </term> ''' <description><para></para><paramref name="margin"/> specifies the horizontal offset.</description> ''' </item> ''' <item> ''' <term> ''' <see cref="ContentAlignment.TopCenter"/> and <see cref="ContentAlignment.BottomCenter"/> ''' </term> ''' <description><para></para><paramref name="margin"/> specifies the vertical offset.</description> ''' </item> ''' <item> ''' <term> ''' <see cref="ContentAlignment.MiddleCenter"/> ''' </term> ''' <description><para></para><paramref name="margin"/> is ignored.</description> ''' </item> ''' </list> ''' </param> ''' ''' <param name="font"> ''' Optional. A custom <see cref="Font"/> to use. If not provided, a bold <c>Arial</c> font is used. ''' <para></para> ''' Note: Custom font size (<see cref="System.Drawing.Font.Size"/>) is ignored. It is determined by <paramref name="scale"/> parameter. ''' </param> ''' ''' <param name="textColor"> ''' Optional. The color of the text. ''' <para></para> ''' Default value is <see cref="Color.White"/>. ''' </param> ''' ''' <param name="outlineColor"> ''' Optional. The color of the text outline. ''' <para></para> ''' Default value is <see cref="Color.Black"/>. ''' </param> ''' ''' <param name="outlineThickness"> ''' Optional. The thickness of the outline, in pixels. ''' <para></para> ''' Default value is 2 pixels. ''' </param> <DebuggerStepThrough> <Extension> <EditorBrowsable(EditorBrowsableState.Always)> Public Sub DrawTextScaled(ByRef refImg As Image, text As String, scale As Single, position As ContentAlignment, margin As Single, Optional font As Font = Nothing, Optional textColor As Color = Nothing, Optional outlineColor As Color = Nothing, Optional outlineThickness As Short = 2) If margin < 0 Then Throw New ArgumentOutOfRangeException(NameOf(margin), margin, "Margin must be greater than or equal to 0.") End If If scale < 1 Then Throw New ArgumentOutOfRangeException(NameOf(scale), scale, "Font scale must be greater than or equal to 1.") End If If textColor = Nothing Then textColor = Color.White End If If outlineColor = Nothing Then outlineColor = Color.Black End If Using g As Graphics = Graphics.FromImage(refImg) g.SmoothingMode = SmoothingMode.AntiAlias g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit g.InterpolationMode = InterpolationMode.HighQualityBicubic g.PixelOffsetMode = PixelOffsetMode.HighQuality g.CompositingQuality = CompositingQuality.HighQuality Dim rawFontSize As Single = Math.Max(refImg.Width, refImg.Height) / scale Dim maxAllowedFontSize As Single = Math.Min(refImg.Width, refImg.Height) Dim fontSize As Single = Math.Min(rawFontSize, maxAllowedFontSize) Using textFont As Font = If(font IsNot Nothing, New Font(font.FontFamily, fontSize, font.Style, GraphicsUnit.Pixel, font.GdiCharSet, font.GdiVerticalFont), New Font("Arial", fontSize, FontStyle.Bold, GraphicsUnit.Pixel)) Dim textSize As SizeF = g.MeasureString(text, textFont) Dim posX As Single = 0 Dim posY As Single = 0 Select Case position Case ContentAlignment.TopLeft posX = margin posY = margin Case ContentAlignment.TopCenter posX = (refImg.Width - textSize.Width) / 2 posY = margin Case ContentAlignment.TopRight posX = refImg.Width - textSize.Width - margin posY = margin Case ContentAlignment.MiddleLeft posX = margin posY = (refImg.Height - textSize.Height) / 2 Case ContentAlignment.MiddleCenter posX = (refImg.Width - textSize.Width) / 2 posY = (refImg.Height - textSize.Height) / 2 Case ContentAlignment.MiddleRight posX = refImg.Width - textSize.Width - margin posY = (refImg.Height - textSize.Height) / 2 Case ContentAlignment.BottomLeft posX = margin posY = refImg.Height - textSize.Height - margin Case ContentAlignment.BottomCenter posX = (refImg.Width - textSize.Width) / 2 posY = refImg.Height - textSize.Height - margin Case ContentAlignment.BottomRight posX = refImg.Width - textSize.Width - margin posY = refImg.Height - textSize.Height - margin Case Else Throw New InvalidEnumArgumentException(NameOf(position), position, GetType(ContentAlignment)) End Select Using outlineBrush As New SolidBrush(outlineColor) For dx As Short = -outlineThickness To outlineThickness For dy As Short = -outlineThickness To outlineThickness If dx <> 0 OrElse dy <> 0 Then g.DrawString(text, textFont, outlineBrush, posX + dx, posY + dy) End If Next dy Next dx End Using Using textBrush As New SolidBrush(textColor) g.DrawString(text, textFont, textBrush, posX, posY) End Using End Using ' font End Using ' g End Sub ''' <summary> ''' Draws a number onto the specified <see cref="Image"/> at a given position and scale factor. ''' </summary> ''' ''' <param name="refImg"> ''' The <see cref="Image"/> to modify. The number will be drawn directly on this image. ''' </param> ''' ''' <param name="number"> ''' The number to draw on the image. ''' </param> ''' ''' <param name="scale"> ''' The relative image scale factor to determine font size. Lower values increase the size of the text. ''' <para></para> ''' Suggested value is from 10 to 20. ''' </param> ''' ''' <param name="position"> ''' The position/alignment where the number should be drawn (e.g., bottom-right). ''' </param> ''' ''' <param name="margin"> ''' The margin (in pixels) from the edge of the image to position the text. ''' <para></para> ''' This value has different meaning depending on <paramref name="position"/> parameter: ''' <para></para> ''' <list type="bullet"> ''' <item> ''' <term> ''' <see cref="ContentAlignment.TopLeft"/>, <see cref="ContentAlignment.TopRight"/>, ''' <para></para> ''' <see cref="ContentAlignment.BottomLeft"/> and <see cref="ContentAlignment.BottomRight"/> ''' </term> ''' <description><para></para><paramref name="margin"/> specifies the diagonal offset.</description> ''' </item> ''' <item> ''' <term> ''' <see cref="ContentAlignment.MiddleLeft"/> and <see cref="ContentAlignment.MiddleRight"/> ''' </term> ''' <description><para></para><paramref name="margin"/> specifies the horizontal offset.</description> ''' </item> ''' <item> ''' <term> ''' <see cref="ContentAlignment.TopCenter"/> and <see cref="ContentAlignment.BottomCenter"/> ''' </term> ''' <description><para></para><paramref name="margin"/> specifies the vertical offset.</description> ''' </item> ''' <item> ''' <term> ''' <see cref="ContentAlignment.MiddleCenter"/> ''' </term> ''' <description><para></para><paramref name="margin"/> is ignored.</description> ''' </item> ''' </list> ''' </param> ''' ''' <param name="font"> ''' Optional. A custom <see cref="Font"/> to use. If not provided, a bold <c>Arial</c> font is used. ''' <para></para> ''' Note: Custom font size (<see cref="System.Drawing.Font.Size"/>) is ignored. It is determined by <paramref name="scale"/> parameter. ''' </param> ''' ''' <param name="textColor"> ''' Optional. The color of the text. ''' <para></para> ''' Default value is <see cref="Color.White"/>. ''' </param> ''' ''' <param name="outlineColor"> ''' Optional. The color of the text outline. ''' <para></para> ''' Default value is <see cref="Color.Black"/>. ''' </param> ''' ''' <param name="outlineThickness"> ''' Optional. The thickness of the outline, in pixels. ''' <para></para> ''' Default value is 2 pixels. ''' </param> <DebuggerStepThrough> <Extension> <EditorBrowsable(EditorBrowsableState.Always)> Public Sub DrawNumberScaled(ByRef refImg As Image, number As Integer, scale As Single, position As ContentAlignment, margin As Single, Optional font As Font = Nothing, Optional textColor As Color = Nothing, Optional outlineColor As Color = Nothing, Optional outlineThickness As Short = 2) DrawTextScaled(refImg, CStr(number), scale, position, margin, font, textColor, outlineColor, outlineThickness) End Sub
|
|
|
40
|
Foros Generales / Foro Libre / Re: Nubes naranjas en el cielo
|
en: 29 Mayo 2025, 13:46 pm
|
¿Naranjas?, yo ahí veo un color gris oscuro tirando a marrón negruno. Lo que están son densamente cargadas, no sé si de agua o de "suciedad" o polvo, pero algo tienen que las vuelve así de opacas con esa tonalidad oscura. Y sino es así da igual, el caso es que tener tiene una explicación lógica aunque no la sepamos con exactitud. Esto dice una IA, aunque por experiencia sé que nunca hay que confiar ciegamente en lo que diga una IA: 
|
|
|
|
|
|
|