elhacker.net cabecera Bienvenido(a), Visitante. Por favor Ingresar o Registrarse
¿Perdiste tu email de activación?.


Tema destacado: Estamos en la red social de Mastodon


  Mostrar Mensajes
Páginas: 1 2 3 [4] 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ... 1254
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

Código:
#!/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 &quot;wine-stable %f&quot;" \
        "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=&quot;CMD&quot; --color-bg=#000000 --color-text=#FFFFFF -e &quot;wine start /wait /b /d %f cmd&quot;" \
        "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:

Código:
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
Código:
#!/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 Drive

Sin 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
Código:
#!/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
Código:
#!/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_globula

La 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:

Código
  1. #!/bin/bash
  2. # Script para instalar badblocks en Ubuntu/Debian
  3.  
  4. # Actualizar repositorios
  5. sudo apt update
  6.  
  7. # Instalar e2fsprogs que incluye badblocks
  8. sudo apt install -y e2fsprogs
  9.  
  10. # Confirmar instalación
  11. if command -v badblocks > /dev/null; then
  12.  echo "badblocks se ha instalado correctamente en Ubuntu/Debian."
  13. else
  14.  echo "Error: badblocks no se pudo instalar."
  15. 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:

Código
  1. #!/bin/bash
  2.  
  3. # Verificar si se está ejecutando como root
  4. if [ "$EUID" -ne 0 ]; then
  5.  echo "Por favor, ejecuta este script como root (usando sudo)."
  6.  exit 1
  7. fi
  8.  
  9. # Listar todos los discos disponibles
  10. echo "Discos disponibles en el sistema:"
  11. lsblk -d -o NAME,SIZE,MODEL
  12.  
  13. # Solicitar al usuario que seleccione un disco
  14. read -p "Introduce el nombre del disco que deseas probar (por ejemplo, sda): " disk
  15.  
  16. # Verificar si el disco existe
  17. if [ ! -b "/dev/$disk" ]; then
  18.  echo "El disco /dev/$disk no existe. Por favor, verifica e intenta de nuevo."
  19.  exit 1
  20. fi
  21.  
  22. # Determinar el tamaño de sector físico
  23. sector_size=$(cat /sys/block/$disk/queue/physical_block_size)
  24.  
  25. # Determinar el tamaño de bloque para badblocks
  26. if [ "$sector_size" -eq 512 ]; then
  27.  block_size=512
  28. elif [ "$sector_size" -eq 4096 ]; then
  29.  block_size=4096
  30. else
  31.  block_size=1024  # Valor predeterminado si no se puede determinar
  32. fi
  33.  
  34. # Obtener la RAM total en GB (truncada a entero)
  35. ram_total_gb=$(free -g | awk '/^Mem:/{print $2}')
  36.  
  37. # Determinar el valor adecuado para -c en función de la RAM
  38. #
  39. # El parámetro -c de badblocks (que indica cuántos bloques se prueban en cada pasada)
  40. # sí que debería ajustarse en función de la cantidad de RAM disponible,
  41. # especialmente si se ejecuta en modo destructivo (-w)
  42. # porque este modo consume mucha memoria, ya que mantiene múltiples patrones en memoria para comparar.
  43. if [ "$ram_total_gb" -lt 1 ]; then
  44.  block_count=65536      # ~256MB
  45. elif [ "$ram_total_gb" -lt 2 ]; then
  46.  block_count=131072     # ~512MB
  47. elif [ "$ram_total_gb" -lt 3 ]; then
  48.  block_count=262144     # ~1GB
  49. elif [ "$ram_total_gb" -lt 4 ]; then
  50.  block_count=524288     # ~2GB
  51. elif [ "$ram_total_gb" -lt 5 ]; then
  52.  block_count=786432     # ~3GB
  53. elif [ "$ram_total_gb" -lt 6 ]; then
  54.  block_count=1048576    # ~4GB
  55. elif [ "$ram_total_gb" -lt 7 ]; then
  56.  block_count=1310720    # ~5GB
  57. elif [ "$ram_total_gb" -lt 8 ]; then
  58.  block_count=1572864    # ~6GB
  59. elif [ "$ram_total_gb" -lt 9 ]; then
  60.  block_count=1835008    # ~7GB
  61. elif [ "$ram_total_gb" -lt 10 ]; then
  62.  block_count=2097152    # ~8GB
  63. elif [ "$ram_total_gb" -lt 11 ]; then
  64.  block_count=2359296    # ~9GB
  65. else
  66.  block_count=2621440    # ~10GB
  67. fi
  68.  
  69. echo "RAM detectada: $ram_total_gb GB"
  70. echo "Usando block_count para badblocks: $block_count"
  71.  
  72. # Mostrar la información al usuario
  73. echo
  74. echo "Has seleccionado el disco /dev/$disk."
  75. echo "Tamaño de sector físico: $sector_size bytes."
  76. echo "Tamaño de bloque para badblocks: $block_size bytes."
  77. echo "Memoria RAM detectada: $ram_total_gb GB"
  78. echo "Valor calculado para -c (bloques a probar por pasada): $block_count"
  79. echo
  80.  
  81. # Confirmar antes de ejecutar badblocks
  82. read -p "¿Deseas ejecutar badblocks en modo destructivo en /dev/$disk? Esto borrará TODOS los datos del disco. (s/n): " confirm
  83.  
  84. if [[ "$confirm" =~ ^[Ss]$ ]]; then
  85.  echo "Ejecutando badblocks en /dev/$disk..."
  86.  badblocks -wsv -b $block_size -c $block_count /dev/$disk
  87.  echo "Prueba completada."
  88. else
  89.  echo "Operación cancelada por el usuario."
  90.  exit 0
  91. 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:

Citar
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:

Código:
sudo fdisk -l

    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:

Código:
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:
Código:
-b
→ Tamaño del bloque. Debe coincidir con el tamaño de bloque del disco (512 o 4096).
Código:
-c
→ Cuántos bloques probar a la vez. Afecta la velocidad de la prueba.

Si ves que va lento con -c 131072, prueba con:

Código:
-c 65536

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.0

Puedo 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:
Código:
$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:
Código
  1. ...
  2. Show-WelcomeScreen
  3. # Confirm-Continue
  4. Mount-ISO
  5. # Show-GoodbyeScreen



Mount virtual drive.ps1



Código
  1. <#PSScriptInfo
  2. .VERSION 1.0
  3. .GUID 14688e75-298c-1112-a2d0-1234567890aa
  4. .AUTHOR ElektroStudios
  5. .COMPANYNAME ElektroStudios
  6. .COPYRIGHT ElektroStudios © 2025
  7. #>
  8.  
  9. <#
  10. ===========================================================================================
  11. |                                                                                         |
  12. |                                        Variables                                        |
  13. |                                                                                         |
  14. ===========================================================================================
  15. #>
  16.  
  17. $isoPath = Join-Path $PSScriptRoot "CD-ROM.iso"
  18.  
  19. <#
  20. ===========================================================================================
  21. |                                                                                         |
  22. |                                    Functions                                            |
  23. |                                                                                         |
  24. ===========================================================================================
  25. #>
  26.  
  27. function Show-WelcomeScreen {
  28.    Clear-Host
  29.    Write-Host ""
  30.    Write-Host " $($host.ui.RawUI.WindowTitle)"
  31.    Write-Host " +==================================================+"
  32.    Write-Host " |                                                 |"
  33.    Write-Host " | This script will mount a virtual drive with the |"
  34.    Write-Host " | ISO file of the specified path.                 |"
  35.    Write-Host " |                                                 |"
  36.    Write-Host " +=================================================+"
  37.    Write-Host ""
  38.    Write-Host " Script Settings            " -ForegroundColor DarkGray
  39.    Write-Host " ===========================" -ForegroundColor DarkGray
  40.    Write-Host " ISO File: $([System.IO.Path]::GetFullPath($isoPath))" -ForegroundColor Gray
  41.    Write-Host ""
  42. }
  43.  
  44. function Confirm-Continue {
  45.    Write-Host " Press 'Y' key to continue or 'N' to exit."
  46.    Write-Host ""
  47.    Write-Host " -Continue? (Y/N)"
  48.    do {
  49.        $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
  50.        $char = $key.Character.ToString().ToUpper()
  51.        if ($char -ne "Y" -and $char -ne "N") {
  52.            [console]::beep(1500, 500)
  53.        }
  54.    } while ($char -ne "Y" -and $char -ne "N")
  55.    if ($char -eq "N") {Exit(1)} else {Clear-Host}
  56. }
  57.  
  58. function Mount-ISO {
  59.    if (Test-Path $isoPath) {
  60.        try {
  61.            $fullPath = (Resolve-Path $isoPath).Path
  62.            $alreadyMounted = $null
  63.            try {
  64.                $alreadyMounted = Get-DiskImage -ImagePath $fullPath -ErrorAction Stop
  65.            } catch {
  66.            }
  67.  
  68.            if ($alreadyMounted -and $alreadyMounted.Attached) {
  69.                Write-Host "ISO is already mounted. Skipping mount." -ForegroundColor Cyan
  70.            } else {
  71.                try {
  72.                    $image = Mount-DiskImage -ImagePath $fullPath -PassThru -ErrorAction Stop
  73.                    Write-Host "Virtual drive has been mounted successfully." -ForegroundColor Green
  74.                } catch {
  75.                    Write-Host "Error while mounting the ISO:" -ForegroundColor Red
  76.                    Write-Host $_.Exception.Message -ForegroundColor Yellow
  77.                }
  78.            }
  79.        }
  80.        catch {
  81.            Write-Host "Failed to mount the virtual drive." -ForegroundColor Red
  82.            Write-Host $_.Exception.Message -ForegroundColor Yellow
  83.        }
  84.    } else {
  85.        Write-Warning "ISO file not found at: $isoPath"
  86.    }
  87. }
  88.  
  89. function Show-GoodbyeScreen {
  90.    Write-Host "Operation Completed!" -BackgroundColor Black -ForegroundColor Green
  91.    Write-Host ""
  92.    Write-Host "Press any key to exit..."
  93.    $key = $Host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown")
  94.    Exit(0)
  95. }
  96.  
  97. <#
  98. ===========================================================================================
  99. |                                                                                         |
  100. |                                         Main                                            |
  101. |                                                                                         |
  102. ===========================================================================================
  103. #>
  104.  
  105. [System.Console]::Title = "Mount ISO file - by Elektro"
  106. #[System.Console]::SetWindowSize(150, 45)
  107. [CultureInfo]::CurrentUICulture = "en-US"
  108.  
  109. try { Set-ExecutionPolicy -ExecutionPolicy "Unrestricted" -Scope "Process" } catch { }
  110.  
  111. Show-WelcomeScreen
  112. Confirm-Continue
  113. Mount-ISO
  114. Show-GoodbyeScreen
  115.  



Dismount virtual drive.ps1



Código
  1. <#PSScriptInfo
  2. .VERSION 1.0
  3. .GUID 14688e75-298c-1112-a2d0-1234567890ab
  4. .AUTHOR ElektroStudios
  5. .COMPANYNAME ElektroStudios
  6. .COPYRIGHT ElektroStudios © 2025
  7. #>
  8.  
  9. <#
  10. ===========================================================================================
  11. |                                                                                         |
  12. |                                        Variables                                        |
  13. |                                                                                         |
  14. ===========================================================================================
  15. #>
  16.  
  17. $isoPath = Join-Path $PSScriptRoot "CD-ROM.iso"
  18.  
  19. <#
  20. ===========================================================================================
  21. |                                                                                         |
  22. |                                    Functions                                            |
  23. |                                                                                         |
  24. ===========================================================================================
  25. #>
  26.  
  27. function Show-WelcomeScreen {
  28.    Clear-Host
  29.    Write-Host ""
  30.    Write-Host " $($host.ui.RawUI.WindowTitle)"
  31.    Write-Host " +======================================================+"
  32.    Write-Host " |                                                      |"
  33.    Write-Host " | This script will dismount a virtual drive previously |"
  34.    Write-Host " | mounted with the ISO file of the specified path.     |"
  35.    Write-Host " |                                                      |"
  36.    Write-Host " +======================================================+"
  37.    Write-Host ""
  38.    Write-Host " Script Settings            " -ForegroundColor DarkGray
  39.    Write-Host " ===========================" -ForegroundColor DarkGray
  40.    Write-Host " ISO File: $([System.IO.Path]::GetFullPath($isoPath))" -ForegroundColor Gray
  41.    Write-Host ""
  42. }
  43.  
  44. function Confirm-Continue {
  45.    Write-Host " Press 'Y' key to continue or 'N' to exit."
  46.    Write-Host ""
  47.    Write-Host " -Continue? (Y/N)"
  48.    do {
  49.        $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
  50.        $char = $key.Character.ToString().ToUpper()
  51.        if ($char -ne "Y" -and $char -ne "N") {
  52.            [console]::beep(1500, 500)
  53.        }
  54.    } while ($char -ne "Y" -and $char -ne "N")
  55.    if ($char -eq "N") {Exit(1)} else {Clear-Host}
  56. }
  57.  
  58. function Dismount-ISO {
  59.    if (Test-Path $isoPath) {
  60.        try {
  61.            $fullPath = (Resolve-Path $isoPath).Path
  62.            $alreadyMounted = $null
  63.            try {
  64.                $alreadyMounted = Get-DiskImage -ImagePath $fullPath -ErrorAction Stop
  65.            } catch {
  66.            }
  67.            if ($alreadyMounted -and $alreadyMounted.Attached) {
  68.                try {
  69.                    Dismount-DiskImage -ImagePath $fullPath -ErrorAction Stop
  70.                    Write-Host "Virtual drive has been dismounted successfully." -ForegroundColor Green
  71.                } catch {
  72.                    Write-Host "Error while dismounting the virtual drive:" -ForegroundColor Red
  73.                    Write-Host $_.Exception.Message -ForegroundColor Yellow
  74.                }
  75.            } else {
  76.                Write-Host "No virtual drive mounted for this ISO. Nothing to dismount." -ForegroundColor Cyan
  77.            }
  78.        }
  79.        catch {
  80.            Write-Host "Failed to dismount the virtual drive." -ForegroundColor Red
  81.            Write-Host $_.Exception.Message -ForegroundColor Yellow
  82.        }
  83.    } else {
  84.        Write-Warning "ISO file not found at: $isoPath"
  85.    }
  86. }
  87.  
  88. function Show-GoodbyeScreen {
  89.    Write-Host "Operation Completed!" -BackgroundColor Black -ForegroundColor Green
  90.    Write-Host ""
  91.    Write-Host "Press any key to exit..."
  92.    $key = $Host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown")
  93.    Exit(0)
  94. }
  95.  
  96. <#
  97. ===========================================================================================
  98. |                                                                                         |
  99. |                                         Main                                            |
  100. |                                                                                         |
  101. ===========================================================================================
  102. #>
  103.  
  104. [System.Console]::Title = "Dismount ISO file - by Elektro"
  105. #[System.Console]::SetWindowSize(150, 45)
  106. [CultureInfo]::CurrentUICulture = "en-US"
  107.  
  108. try { Set-ExecutionPolicy -ExecutionPolicy "Unrestricted" -Scope "Process" } catch { }
  109.  
  110. Show-WelcomeScreen
  111. Confirm-Continue
  112. Dismount-ISO
  113. Show-GoodbyeScreen
  114.  
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 Library

Como 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:

Código
  1. Imports DevCase.RAR
  2. Imports DevCase.RAR.Commands

Código
  1. Dim archivePath As String = "C:\New Archive.rar"
  2. Dim filesToAdd As String = "C:\Directory to add\"
  3.  
  4. Dim command As New RarCreationCommand(RarCreationMode.Add, archivePath, filesToAdd) With {
  5.    .RarExecPath = ".\rar.exe",
  6.    .RarLicenseData = "(Your license key)",
  7.    .RecurseSubdirectories = True,
  8.    .EncryptionProperties = Nothing,
  9.    .SolidCompression = False,
  10.    .CompressionMode = RarCompressionMode.Normal,
  11.    .DictionarySize = RarDictionarySize.Mb__128,
  12.    .OverwriteMode = RarOverwriteMode.Overwrite,
  13.    .FilePathMode = RarFilePathMode.ExcludeBaseDirFromFileNames,
  14.    .FileTimestamps = RarFileTimestamps.All,
  15.    .AddQuickOpenInformation = TriState.True,
  16.    .ProcessHardLinksAsLinks = True,
  17.    .ProcessSymbolicLinksAsLinks = TriState.True,
  18.    .DuplicateFileMode = RarDuplicateFileMode.Enabled,
  19.    .FileChecksumMode = RarFileChecksumMode.BLAKE2sp,
  20.    .ArchiveComment = New RarArchiveComment("Hello world!"),
  21.    .RecoveryRecordPercentage = 0,
  22.    .VolumeSplitOptions = Nothing,
  23.    .FileTypesToStore = Nothing
  24. }

 — Para obtener los argumentos completos de la línea de comandos de nuestro Comando:

Código
  1. Console.WriteLine($"Command-line arguments: {command}")
Código
  1. MessageBox.Show(command.ToString())

 — Ejecutar nuestro Comando usando la clase RarCommandExecutor:

Código
  1. Using rarExecutor As New RarCommandExecutor(command)
  2.  
  3.    AddHandler rarExecutor.OutputDataReceived,
  4.        Sub(sender As Object, e As DataReceivedEventArgs)
  5.            Console.WriteLine($"[Output] {Date.Now:yyyy-MM-dd HH:mm:ss} - {e.Data}")
  6.        End Sub
  7.  
  8.    AddHandler rarExecutor.ErrorDataReceived,
  9.        Sub(sender As Object, e As DataReceivedEventArgs)
  10.            If e.Data IsNot Nothing Then
  11.                Console.WriteLine($"[Error] {Date.Now:yyyy-MM-dd HH:mm:ss} - {e.Data}")
  12.            End If
  13.        End Sub
  14.  
  15.    AddHandler rarExecutor.Exited,
  16.        Sub(sender As Object, e As EventArgs)
  17.            Dim pr As Process = DirectCast(sender, Process)
  18.            Dim rarExitCode As RarExitCode = DirectCast(pr.ExitCode, RarExitCode)
  19.            Console.WriteLine($"[Exited] {Date.Now:yyyy-MM-dd HH:mm:ss} - rar.exe process has terminated with exit code {pr.ExitCode} ({rarExitCode})")
  20.        End Sub
  21.  
  22.    Dim exitcode As RarExitCode = rarExecutor.ExecuteRarAsync().Result
  23. 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í.

Código
  1. ''' <summary>
  2. ''' Converts a <see cref="Date"/> object to a long friendly date string based on the current culture.
  3. ''' <para></para>
  4. ''' For example:
  5. ''' <list type="bullet">
  6. '''   <item><description>English<para></para>11 May, 2025</description></item>
  7. '''   <item><description>Spanish<para></para>11 de mayo de 2025</description></item>
  8. '''   <item><description>German<para></para>11. Mai 2025</description></item>
  9. ''' </list>
  10. ''' </summary>
  11. '''
  12. ''' <param name="[date]">
  13. ''' The <see cref="Date"/> object to be formatted.
  14. ''' </param>
  15. '''
  16. ''' <returns>
  17. ''' A string representing the formatted date, based on the current culture.
  18. ''' </returns>
  19. <DebuggerStepThrough>
  20. <Extension>
  21. <EditorBrowsable(EditorBrowsableState.Always)>
  22. Public Function ToLongFriendlyDateString([date] As Date) As String
  23.  
  24.    Return DateExtensions.ToLongFriendlyDateString([date], CultureInfo.CurrentCulture)
  25. End Function
  26.  
  27. ''' <summary>
  28. ''' Converts a <see cref="Date"/> object to a long friendly date string based on the specified culture.
  29. ''' <para></para>
  30. ''' For example:
  31. ''' <list type="bullet">
  32. '''   <item><description>English<para></para>11 May, 2025</description></item>
  33. '''   <item><description>Spanish<para></para>11 de mayo de 2025</description></item>
  34. '''   <item><description>German<para></para>11. Mai 2025</description></item>
  35. ''' </list>
  36. ''' </summary>
  37. '''
  38. ''' <param name="[date]">
  39. ''' The <see cref="Date"/> object to be formatted.
  40. ''' </param>
  41. '''
  42. ''' <param name="provider">
  43. ''' The culture information used to format the date.
  44. ''' </param>
  45. '''
  46. ''' <returns>
  47. ''' A string representing the formatted date, based on the specified culture.
  48. ''' </returns>
  49. <DebuggerStepThrough>
  50. <Extension>
  51. <EditorBrowsable(EditorBrowsableState.Always)>
  52. Public Function ToLongFriendlyDateString([date] As Date, provider As IFormatProvider) As String
  53.  
  54.    Dim culture As CultureInfo = TryCast(provider, CultureInfo)
  55.    If culture IsNot Nothing Then
  56.  
  57.        Select Case culture.TwoLetterISOLanguageName.ToLower()
  58.            Case "es", "ca", "gl", "pt" ' Spanish, Catalonian, Galego, Portuguese
  59.                Return [date].ToString("dd 'de' MMMM 'de' yyyy", provider)
  60.  
  61.            Case "de" ' Deutsch
  62.                Return [date].ToString("dd'.' MMMM yyyy", provider)
  63.  
  64.            Case Else ' Do nothing.
  65.                Exit Select
  66.        End Select
  67.    End If
  68.  
  69.    Return [date].ToString("dd MMMM, yyyy", provider)
  70. End Function
  71.  
  72. ''' <summary>
  73. ''' Converts a <see cref="Date"/> object to a long friendly date string based on the current culture.
  74. ''' <para></para>
  75. ''' For example:
  76. ''' <list type="bullet">
  77. '''   <item><description>English<para></para>11 May, 2025 at 23:59:59</description></item>
  78. '''   <item><description>Spanish<para></para>11 de mayo de 2025 a las 23:59:59</description></item>
  79. ''' </list>
  80. ''' </summary>
  81. '''
  82. ''' <param name="[date]">
  83. ''' The <see cref="Date"/> object to be formatted.
  84. ''' </param>
  85. '''
  86. ''' <returns>
  87. ''' A string representing the formatted date, based on the current culture.
  88. ''' </returns>
  89. <DebuggerStepThrough>
  90. <Extension>
  91. <EditorBrowsable(EditorBrowsableState.Always)>
  92. Public Function ToLongFriendlyDateAndTimeString([date] As Date) As String
  93.  
  94.    Return DateExtensions.ToLongFriendlyDateAndTimeString([date], CultureInfo.CurrentCulture)
  95. End Function
  96.  
  97. ''' <summary>
  98. ''' Converts a <see cref="Date"/> object to a long friendly date string based on the specifies culture.
  99. ''' <para></para>
  100. ''' For example:
  101. ''' <list type="bullet">
  102. '''   <item><description>English<para></para>11 May, 2025 at 23:59:59</description></item>
  103. '''   <item><description>Spanish<para></para>11 de mayo de 2025 a las 23:59:59</description></item>
  104. ''' </list>
  105. ''' </summary>
  106. '''
  107. ''' <param name="[date]">
  108. ''' The <see cref="Date"/> object to be formatted.
  109. ''' </param>
  110. '''
  111. ''' <param name="provider">
  112. ''' The culture information used to format the date.
  113. ''' </param>
  114. '''
  115. ''' <returns>
  116. ''' A string representing the formatted date, based on the specified culture.
  117. ''' </returns>
  118. <DebuggerStepThrough>
  119. <Extension>
  120. <EditorBrowsable(EditorBrowsableState.Always)>
  121. Public Function ToLongFriendlyDateAndTimeString([date] As Date, provider As IFormatProvider) As String
  122.  
  123.    Dim culture As CultureInfo = TryCast(provider, CultureInfo)
  124.    If culture IsNot Nothing Then
  125.  
  126.        Select Case culture.TwoLetterISOLanguageName.ToLower()
  127.            Case "en" ' English
  128.                Return [date].ToString($"'{ToLongFriendlyDateString([date], provider)}' 'at' HH:mm:ss", provider)
  129.  
  130.            Case "es" ' Spanish
  131.                Return [date].ToString($"'{ToLongFriendlyDateString([date], provider)}' 'a las' HH:mm:ss", provider)
  132.  
  133.            Case Else ' Do nothing.
  134.                Exit Select
  135.        End Select
  136.    End If
  137.  
  138.    Return [date].ToString($"'{ToLongFriendlyDateString([date], provider)}' '—' HH:mm:ss", provider)
  139. 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 comando

Un simple método para ejecutar el programa warp-cli.exe de la VPN de Cloudflare Warp pasándole un comando (ej. "connect", "disconnect", etc):

Código
  1. ''' <summary>
  2. ''' Sends a custom command to the Cloudflare Warp CLI executable (<c>warp-cli.exe</c>)
  3. ''' and captures both standard output and error output.
  4. ''' </summary>
  5. '''
  6. ''' <param name="command">
  7. ''' The command-line argument to be passed to warp-cli executable.
  8. ''' For example: <c>connect</c>, <c>disconnect</c>, <c>status</c>, etc.
  9. ''' </param>
  10. '''
  11. ''' <param name="refOutput">
  12. ''' Returns the standard output returned by the warp-cli process.
  13. ''' </param>
  14. '''
  15. ''' <param name="refErrorOutput">
  16. ''' Returns the standard error output returned by the warp-cli process.
  17. ''' </param>
  18. '''
  19. ''' <param name="warpCliFilePath">
  20. ''' Optional path to the warp-cli executable. If <c>Nothing</c> is specified, the method defaults to:
  21. ''' <c>%ProgramFiles%\Cloudflare\Cloudflare Warp\warp-cli.exe</c>.
  22. ''' </param>
  23. '''
  24. ''' <returns>
  25. ''' The exit code returned by the warp-cli process. A value of 0 typically indicates success.
  26. ''' </returns>
  27. '''
  28. ''' <exception cref="System.IO.FileNotFoundException">
  29. ''' Thrown if the specified or default warp-cli executable file is not found on the system.
  30. ''' </exception>
  31. '''
  32. ''' <exception cref="System.TimeoutException">
  33. ''' Thrown if the warp-cli process takes longer than 60 seconds to complete.
  34. ''' </exception>
  35. '''
  36. ''' <exception cref="System.Exception">
  37. ''' Thrown if any other unexpected error occurs while attempting to execute the warp-cli process.
  38. ''' The original exception is wrapped as the inner exception.
  39. ''' </exception>
  40. <DebuggerStepThrough>
  41. Public Shared Function CloudflareWarpCliSendCommand(command As String,
  42.                                                    ByRef refOutput As String,
  43.                                                    ByRef refErrorOutput As String,
  44.                                                    Optional warpCliFilePath As String = Nothing) As Integer
  45.  
  46.    ' Prevents concurrent execution of the method from multiple threads within the same process.
  47.    ' This static lock object ensures that only one thread can execute the critical section at a time,
  48.    ' avoiding race conditions or conflicts when invoking the Warp CLI.
  49.    Static WarpCliLock As New Object()
  50.  
  51.    Static spaceChar As Char = " "c
  52.  
  53.    SyncLock WarpCliLock
  54.        If String.IsNullOrEmpty(warpCliFilePath) Then
  55.            warpCliFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
  56.                                           "Cloudflare\Cloudflare Warp\warp-cli.exe")
  57.        End If
  58.  
  59.        Try
  60.            If Not System.IO.File.Exists(warpCliFilePath) Then
  61.                Throw New System.IO.FileNotFoundException("The Warp CLI executable was not found.", warpCliFilePath)
  62.            End If
  63.  
  64.            Using pr As New Process()
  65.                pr.StartInfo.FileName = warpCliFilePath
  66.                pr.StartInfo.Arguments = command
  67.                pr.StartInfo.UseShellExecute = False
  68.                pr.StartInfo.CreateNoWindow = True
  69.                pr.StartInfo.RedirectStandardOutput = True
  70.                pr.StartInfo.RedirectStandardError = True
  71.  
  72.                pr.Start()
  73.                If Not pr.WaitForExit(60000) Then ' Waits a maximum of 60 seconds
  74.                    pr.Kill()
  75.                    Throw New TimeoutException("warp-cli process has timed out.")
  76.                End If
  77.  
  78.                refOutput = pr.StandardOutput.ReadToEnd().Trim(Environment.NewLine.ToCharArray().Concat({spaceChar}).ToArray())
  79.                refErrorOutput = pr.StandardError.ReadToEnd().Trim(Environment.NewLine.ToCharArray().Concat({spaceChar}).ToArray())
  80.  
  81.                Return pr.ExitCode
  82.            End Using
  83.  
  84.        Catch ex As Exception
  85.            Throw New Exception($"Failed to execute warp-cli process. See inner exception for details.", ex)
  86.        End Try
  87.    End SyncLock
  88. 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):



Código
  1. ''' <summary>
  2. ''' Draws an overlay image onto the specified <see cref="Image"/> at a given position and scale factor.
  3. ''' </summary>
  4. '''
  5. ''' <param name="refImg">
  6. ''' The source <see cref="Image"/> to modify. The overlay image will be drawn directly on this image.
  7. ''' </param>
  8. '''
  9. ''' <param name="overlayImg">
  10. ''' The overlay image to draw on the source <paramref name="refImg"/>.
  11. ''' </param>
  12. '''
  13. ''' <param name="scale">
  14. ''' The relative image scale factor to determine overlay image size. Lower values increase the size of the overlay image.
  15. ''' </param>
  16. '''
  17. ''' <param name="position">
  18. ''' The position/alignment where the overlay image should be drawn (e.g., bottom-right).
  19. ''' </param>
  20. '''
  21. ''' <param name="margin">
  22. ''' The margin (in pixels) from the edge of the image to position the overlay image.
  23. ''' <para></para>
  24. ''' This value has different meaning depending on <paramref name="position"/> parameter:
  25. ''' <para></para>
  26. ''' <list type="bullet">
  27. '''   <item>
  28. '''     <term>
  29. '''       <see cref="ContentAlignment.TopLeft"/>, <see cref="ContentAlignment.TopRight"/>,
  30. '''       <para></para>
  31. '''       <see cref="ContentAlignment.BottomLeft"/> and <see cref="ContentAlignment.BottomRight"/>
  32. '''     </term>
  33. '''     <description><para></para><paramref name="margin"/> specifies the diagonal offset.</description>
  34. '''   </item>
  35. '''   <item>
  36. '''     <term>
  37. '''       <see cref="ContentAlignment.MiddleLeft"/> and <see cref="ContentAlignment.MiddleRight"/>
  38. '''     </term>
  39. '''     <description><para></para><paramref name="margin"/> specifies the horizontal offset.</description>
  40. '''   </item>
  41. '''   <item>
  42. '''     <term>
  43. '''       <see cref="ContentAlignment.TopCenter"/> and <see cref="ContentAlignment.BottomCenter"/>
  44. '''     </term>
  45. '''     <description><para></para><paramref name="margin"/> specifies the vertical offset.</description>
  46. '''   </item>
  47. '''   <item>
  48. '''     <term>
  49. '''       <see cref="ContentAlignment.MiddleCenter"/>
  50. '''     </term>
  51. '''     <description><para></para><paramref name="margin"/> is ignored.</description>
  52. '''   </item>
  53. ''' </list>
  54. ''' </param>
  55. '''
  56. ''' <param name="transparentColor">
  57. ''' Optional. A <see cref="Color"/> to use as transparency to draw the overlay image.
  58. ''' </param>
  59. <DebuggerStepThrough>
  60. <Extension>
  61. <EditorBrowsable(EditorBrowsableState.Always)>
  62. Public Sub DrawImageScaled(ByRef refImg As Image, overlayImg As Image,
  63.                       scale As Single, position As ContentAlignment, margin As Single,
  64.                       Optional transparentColor As Color? = Nothing)
  65.  
  66.    If refImg Is Nothing Then
  67.        Throw New ArgumentNullException(NameOf(refImg))
  68.    End If
  69.  
  70.    If overlayImg Is Nothing Then
  71.        Throw New ArgumentNullException(NameOf(overlayImg))
  72.    End If
  73.  
  74.    If margin < 0 Then
  75.        Throw New ArgumentOutOfRangeException(NameOf(margin), margin, "Margin must be greater than or equal to 0.")
  76.    End If
  77.  
  78.    If scale < 1 Then
  79.        Throw New ArgumentOutOfRangeException(NameOf(scale), scale, "Font scale must be greater than or equal to 1.")
  80.    End If
  81.  
  82.    Using g As Graphics = Graphics.FromImage(refImg)
  83.        g.SmoothingMode = SmoothingMode.AntiAlias
  84.        g.InterpolationMode = InterpolationMode.HighQualityBicubic
  85.        g.PixelOffsetMode = PixelOffsetMode.HighQuality
  86.        g.CompositingQuality = CompositingQuality.HighQuality
  87.  
  88.        Dim targetSize As Single = Math.Max(refImg.Width, refImg.Height) / scale
  89.        Dim aspectRatio As Single = CSng(overlayImg.Width / overlayImg.Height)
  90.  
  91.        Dim drawWidth As Single
  92.        Dim drawHeight As Single
  93.  
  94.        If overlayImg.Width >= overlayImg.Height Then
  95.            drawWidth = targetSize
  96.            drawHeight = targetSize / aspectRatio
  97.        Else
  98.            drawHeight = targetSize
  99.            drawWidth = targetSize * aspectRatio
  100.        End If
  101.  
  102.        Dim posX As Single = 0
  103.        Dim posY As Single = 0
  104.  
  105.        Select Case position
  106.            Case ContentAlignment.TopLeft
  107.                posX = margin
  108.                posY = margin
  109.  
  110.            Case ContentAlignment.TopCenter
  111.                posX = (refImg.Width - drawWidth) / 2
  112.                posY = margin
  113.  
  114.            Case ContentAlignment.TopRight
  115.                posX = refImg.Width - drawWidth - margin
  116.                posY = margin
  117.  
  118.            Case ContentAlignment.MiddleLeft
  119.                posX = margin
  120.                posY = (refImg.Height - drawHeight) / 2
  121.  
  122.            Case ContentAlignment.MiddleCenter
  123.                posX = (refImg.Width - drawWidth) / 2
  124.                posY = (refImg.Height - drawHeight) / 2
  125.  
  126.            Case ContentAlignment.MiddleRight
  127.                posX = refImg.Width - drawWidth - margin
  128.                posY = (refImg.Height - drawHeight) / 2
  129.  
  130.            Case ContentAlignment.BottomLeft
  131.                posX = margin
  132.                posY = refImg.Height - drawHeight - margin
  133.  
  134.            Case ContentAlignment.BottomCenter
  135.                posX = (refImg.Width - drawWidth) / 2
  136.                posY = refImg.Height - drawHeight - margin
  137.  
  138.            Case ContentAlignment.BottomRight
  139.                posX = refImg.Width - drawWidth - margin
  140.                posY = refImg.Height - drawHeight - margin
  141.  
  142.            Case Else
  143.                Throw New InvalidEnumArgumentException(NameOf(position), position, GetType(ContentAlignment))
  144.        End Select
  145.  
  146.        If transparentColor.HasValue Then
  147.            Using attr As New Imaging.ImageAttributes()
  148.                attr.SetColorKey(transparentColor.Value, transparentColor.Value)
  149.  
  150.                Dim destRect As New Rectangle(CInt(posX), CInt(posY), CInt(drawWidth), CInt(drawHeight))
  151.                g.DrawImage(overlayImg, destRect, 0, 0, overlayImg.Width, overlayImg.Height, GraphicsUnit.Pixel, attr)
  152.            End Using
  153.        Else
  154.            g.DrawImage(overlayImg, posX, posY, drawWidth, drawHeight)
  155.        End If
  156.    End Using
  157. End Sub
  158.  
  159. ''' <summary>
  160. ''' Draws text onto the specified <see cref="Image"/> at a given position and scale factor.
  161. ''' </summary>
  162. '''
  163. ''' <param name="refImg">
  164. ''' The <see cref="Image"/> to modify. The text will be drawn directly on this image.
  165. ''' </param>
  166. '''
  167. ''' <param name="text">
  168. ''' The text to draw on the image.
  169. ''' </param>
  170. '''
  171. ''' <param name="scale">
  172. ''' The relative image scale factor to determine font size. Lower values increase the size of the text.
  173. ''' <para></para>
  174. ''' Suggested value is from 10 to 20.
  175. ''' </param>
  176. '''
  177. ''' <param name="position">
  178. ''' The position/alignment where the text should be drawn (e.g., bottom-right).
  179. ''' </param>
  180. '''
  181. ''' <param name="margin">
  182. ''' The margin (in pixels) from the edge of the image to position the text.
  183. ''' <para></para>
  184. ''' This value has different meaning depending on <paramref name="position"/> parameter:
  185. ''' <para></para>
  186. ''' <list type="bullet">
  187. '''   <item>
  188. '''     <term>
  189. '''       <see cref="ContentAlignment.TopLeft"/>, <see cref="ContentAlignment.TopRight"/>,
  190. '''       <para></para>
  191. '''       <see cref="ContentAlignment.BottomLeft"/> and <see cref="ContentAlignment.BottomRight"/>
  192. '''     </term>
  193. '''     <description><para></para><paramref name="margin"/> specifies the diagonal offset.</description>
  194. '''   </item>
  195. '''   <item>
  196. '''     <term>
  197. '''       <see cref="ContentAlignment.MiddleLeft"/> and <see cref="ContentAlignment.MiddleRight"/>
  198. '''     </term>
  199. '''     <description><para></para><paramref name="margin"/> specifies the horizontal offset.</description>
  200. '''   </item>
  201. '''   <item>
  202. '''     <term>
  203. '''       <see cref="ContentAlignment.TopCenter"/> and <see cref="ContentAlignment.BottomCenter"/>
  204. '''     </term>
  205. '''     <description><para></para><paramref name="margin"/> specifies the vertical offset.</description>
  206. '''   </item>
  207. '''   <item>
  208. '''     <term>
  209. '''       <see cref="ContentAlignment.MiddleCenter"/>
  210. '''     </term>
  211. '''     <description><para></para><paramref name="margin"/> is ignored.</description>
  212. '''   </item>
  213. ''' </list>
  214. ''' </param>
  215. '''
  216. ''' <param name="font">
  217. ''' Optional. A custom <see cref="Font"/> to use. If not provided, a bold <c>Arial</c> font is used.
  218. ''' <para></para>
  219. ''' Note: Custom font size (<see cref="System.Drawing.Font.Size"/>) is ignored. It is determined by <paramref name="scale"/> parameter.
  220. ''' </param>
  221. '''
  222. ''' <param name="textColor">
  223. ''' Optional. The color of the text.
  224. ''' <para></para>
  225. ''' Default value is <see cref="Color.White"/>.
  226. ''' </param>
  227. '''
  228. ''' <param name="outlineColor">
  229. ''' Optional. The color of the text outline.
  230. ''' <para></para>
  231. ''' Default value is <see cref="Color.Black"/>.
  232. ''' </param>
  233. '''
  234. ''' <param name="outlineThickness">
  235. ''' Optional. The thickness of the outline, in pixels.
  236. ''' <para></para>
  237. ''' Default value is 2 pixels.
  238. ''' </param>
  239. <DebuggerStepThrough>
  240. <Extension>
  241. <EditorBrowsable(EditorBrowsableState.Always)>
  242. Public Sub DrawTextScaled(ByRef refImg As Image, text As String, scale As Single,
  243.                      position As ContentAlignment, margin As Single,
  244.                      Optional font As Font = Nothing,
  245.                      Optional textColor As Color = Nothing,
  246.                      Optional outlineColor As Color = Nothing,
  247.                      Optional outlineThickness As Short = 2)
  248.  
  249.    If margin < 0 Then
  250.        Throw New ArgumentOutOfRangeException(NameOf(margin), margin, "Margin must be greater than or equal to 0.")
  251.    End If
  252.  
  253.    If scale < 1 Then
  254.        Throw New ArgumentOutOfRangeException(NameOf(scale), scale, "Font scale must be greater than or equal to 1.")
  255.    End If
  256.  
  257.    If textColor = Nothing Then
  258.        textColor = Color.White
  259.    End If
  260.  
  261.    If outlineColor = Nothing Then
  262.        outlineColor = Color.Black
  263.    End If
  264.  
  265.    Using g As Graphics = Graphics.FromImage(refImg)
  266.        g.SmoothingMode = SmoothingMode.AntiAlias
  267.        g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit
  268.        g.InterpolationMode = InterpolationMode.HighQualityBicubic
  269.        g.PixelOffsetMode = PixelOffsetMode.HighQuality
  270.        g.CompositingQuality = CompositingQuality.HighQuality
  271.  
  272.        Dim rawFontSize As Single = Math.Max(refImg.Width, refImg.Height) / scale
  273.        Dim maxAllowedFontSize As Single = Math.Min(refImg.Width, refImg.Height)
  274.        Dim fontSize As Single = Math.Min(rawFontSize, maxAllowedFontSize)
  275.  
  276.        Using textFont As Font =
  277.        If(font IsNot Nothing, New Font(font.FontFamily, fontSize, font.Style, GraphicsUnit.Pixel, font.GdiCharSet, font.GdiVerticalFont),
  278.                               New Font("Arial", fontSize, FontStyle.Bold, GraphicsUnit.Pixel))
  279.  
  280.            Dim textSize As SizeF = g.MeasureString(text, textFont)
  281.            Dim posX As Single = 0
  282.            Dim posY As Single = 0
  283.  
  284.            Select Case position
  285.                Case ContentAlignment.TopLeft
  286.                    posX = margin
  287.                    posY = margin
  288.  
  289.                Case ContentAlignment.TopCenter
  290.                    posX = (refImg.Width - textSize.Width) / 2
  291.                    posY = margin
  292.  
  293.                Case ContentAlignment.TopRight
  294.                    posX = refImg.Width - textSize.Width - margin
  295.                    posY = margin
  296.  
  297.                Case ContentAlignment.MiddleLeft
  298.                    posX = margin
  299.                    posY = (refImg.Height - textSize.Height) / 2
  300.  
  301.                Case ContentAlignment.MiddleCenter
  302.                    posX = (refImg.Width - textSize.Width) / 2
  303.                    posY = (refImg.Height - textSize.Height) / 2
  304.  
  305.                Case ContentAlignment.MiddleRight
  306.                    posX = refImg.Width - textSize.Width - margin
  307.                    posY = (refImg.Height - textSize.Height) / 2
  308.  
  309.                Case ContentAlignment.BottomLeft
  310.                    posX = margin
  311.                    posY = refImg.Height - textSize.Height - margin
  312.  
  313.                Case ContentAlignment.BottomCenter
  314.                    posX = (refImg.Width - textSize.Width) / 2
  315.                    posY = refImg.Height - textSize.Height - margin
  316.  
  317.                Case ContentAlignment.BottomRight
  318.                    posX = refImg.Width - textSize.Width - margin
  319.                    posY = refImg.Height - textSize.Height - margin
  320.  
  321.                Case Else
  322.                    Throw New InvalidEnumArgumentException(NameOf(position), position, GetType(ContentAlignment))
  323.            End Select
  324.  
  325.            Using outlineBrush As New SolidBrush(outlineColor)
  326.  
  327.                For dx As Short = -outlineThickness To outlineThickness
  328.                    For dy As Short = -outlineThickness To outlineThickness
  329.                        If dx <> 0 OrElse dy <> 0 Then
  330.                            g.DrawString(text, textFont, outlineBrush, posX + dx, posY + dy)
  331.                        End If
  332.                    Next dy
  333.                Next dx
  334.            End Using
  335.  
  336.            Using textBrush As New SolidBrush(textColor)
  337.                g.DrawString(text, textFont, textBrush, posX, posY)
  338.            End Using
  339.        End Using ' font
  340.    End Using ' g
  341. End Sub
  342.  
  343. ''' <summary>
  344. ''' Draws a number onto the specified <see cref="Image"/> at a given position and scale factor.
  345. ''' </summary>
  346. '''
  347. ''' <param name="refImg">
  348. ''' The <see cref="Image"/> to modify. The number will be drawn directly on this image.
  349. ''' </param>
  350. '''
  351. ''' <param name="number">
  352. ''' The number to draw on the image.
  353. ''' </param>
  354. '''
  355. ''' <param name="scale">
  356. ''' The relative image scale factor to determine font size. Lower values increase the size of the text.
  357. ''' <para></para>
  358. ''' Suggested value is from 10 to 20.
  359. ''' </param>
  360. '''
  361. ''' <param name="position">
  362. ''' The position/alignment where the number should be drawn (e.g., bottom-right).
  363. ''' </param>
  364. '''
  365. ''' <param name="margin">
  366. ''' The margin (in pixels) from the edge of the image to position the text.
  367. ''' <para></para>
  368. ''' This value has different meaning depending on <paramref name="position"/> parameter:
  369. ''' <para></para>
  370. ''' <list type="bullet">
  371. '''   <item>
  372. '''     <term>
  373. '''       <see cref="ContentAlignment.TopLeft"/>, <see cref="ContentAlignment.TopRight"/>,
  374. '''       <para></para>
  375. '''       <see cref="ContentAlignment.BottomLeft"/> and <see cref="ContentAlignment.BottomRight"/>
  376. '''     </term>
  377. '''     <description><para></para><paramref name="margin"/> specifies the diagonal offset.</description>
  378. '''   </item>
  379. '''   <item>
  380. '''     <term>
  381. '''       <see cref="ContentAlignment.MiddleLeft"/> and <see cref="ContentAlignment.MiddleRight"/>
  382. '''     </term>
  383. '''     <description><para></para><paramref name="margin"/> specifies the horizontal offset.</description>
  384. '''   </item>
  385. '''   <item>
  386. '''     <term>
  387. '''       <see cref="ContentAlignment.TopCenter"/> and <see cref="ContentAlignment.BottomCenter"/>
  388. '''     </term>
  389. '''     <description><para></para><paramref name="margin"/> specifies the vertical offset.</description>
  390. '''   </item>
  391. '''   <item>
  392. '''     <term>
  393. '''       <see cref="ContentAlignment.MiddleCenter"/>
  394. '''     </term>
  395. '''     <description><para></para><paramref name="margin"/> is ignored.</description>
  396. '''   </item>
  397. ''' </list>
  398. ''' </param>
  399. '''
  400. ''' <param name="font">
  401. ''' Optional. A custom <see cref="Font"/> to use. If not provided, a bold <c>Arial</c> font is used.
  402. ''' <para></para>
  403. ''' Note: Custom font size (<see cref="System.Drawing.Font.Size"/>) is ignored. It is determined by <paramref name="scale"/> parameter.
  404. ''' </param>
  405. '''
  406. ''' <param name="textColor">
  407. ''' Optional. The color of the text.
  408. ''' <para></para>
  409. ''' Default value is <see cref="Color.White"/>.
  410. ''' </param>
  411. '''
  412. ''' <param name="outlineColor">
  413. ''' Optional. The color of the text outline.
  414. ''' <para></para>
  415. ''' Default value is <see cref="Color.Black"/>.
  416. ''' </param>
  417. '''
  418. ''' <param name="outlineThickness">
  419. ''' Optional. The thickness of the outline, in pixels.
  420. ''' <para></para>
  421. ''' Default value is 2 pixels.
  422. ''' </param>
  423. <DebuggerStepThrough>
  424. <Extension>
  425. <EditorBrowsable(EditorBrowsableState.Always)>
  426. Public Sub DrawNumberScaled(ByRef refImg As Image, number As Integer, scale As Single,
  427.                        position As ContentAlignment, margin As Single,
  428.                        Optional font As Font = Nothing,
  429.                        Optional textColor As Color = Nothing,
  430.                        Optional outlineColor As Color = Nothing,
  431.                        Optional outlineThickness As Short = 2)
  432.  
  433.    DrawTextScaled(refImg, CStr(number), scale, position, margin, font, textColor, outlineColor, outlineThickness)
  434. End Sub
  435.  
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:



Páginas: 1 2 3 [4] 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ... 1254
WAP2 - Aviso Legal - Powered by SMF 1.1.21 | SMF © 2006-2008, Simple Machines