| name | corporate-windows-tooling |
| description | Crear herramientas de escritorio Windows que funcionen en entornos corporativos restringidos — PowerShell + .NET, sin admin, sin .exe |
| version | 1.0.0 |
| author | Mastermind |
| tags | ["windows","powershell","dotnet","corporate","deployment","desktop","portable"] |
| related_skills | ["software-development","python-code-implementation"] |
Corporate Windows Tooling
Patrones para crear herramientas de escritorio que funcionen en entornos corporativos donde PyInstaller .exe está bloqueado por políticas de seguridad.
Trigger
Activar cuando el usuario mencione:
- "No funciona en el ordenador de la empresa"
- "Sin ser administrador no me deja usarlo"
- "Antivirus bloquea el .exe"
- "AppLocker" / "políticas de grupo"
- "No tengo permisos de instalación"
- Necesita herramienta de escritorio Windows para usuarios no técnicos
Decision Matrix
| Solución | Cuándo usarla | Requisitos |
|---|
| PyInstaller .exe | Primera opción si no hay restricciones | Python + PyInstaller |
| PowerShell + .NET | Entorno corporativo restringido | Windows 10+ (ya incluido) |
| Batch + Python | Si Python ya está instalado en los PCs | Python en PATH |
| HTML + JavaScript | Solo lectura/exportación, sin modificar archivos | Navegador |
Regla: Si el usuario menciona "corporativo", "empresa", "sin admin" → PowerShell + .NET directamente. No preguntar.
Patrón PowerShell + .NET
Estructura del proyecto
mi-herramienta/
├── ejecutar.bat # Lanzador (doble clic)
├── src/
│ ├── Mi-Script.ps1 # Script principal
│ └── lib/
│ └── libreria.dll # Librería .NET (ej: itextsharp.dll)
├── README.md
└── SPEC.md
ejecutar.bat (siempre el mismo patrón)
@echo off
chcp 65001 >nul 2>&1
title Título de la Herramienta
echo ========================================
echo Nombre de la Herramienta
echo Hecho con ❤️ por David Antizar
echo ========================================
echo.
echo Iniciando herramienta...
echo.
:: Verificar si PowerShell está disponible
where powershell.exe >nul 2>&1
if %errorlevel% neq 0 (
echo ERROR: No se encontro PowerShell en el sistema.
pause
exit /b 1
)
:: Ejecutar script PowerShell
powershell.exe -ExecutionPolicy Bypass -File "%~dp0src\Mi-Script.ps1"
if %errorlevel% neq 0 (
echo.
echo La herramienta se cerro con errores.
pause
)
Pitfall: %~dp0 resuelve la ruta relativa al .bat, no al directorio de trabajo actual. Essential para que funcione desde cualquier ubicación.
Script PowerShell — Cabecera
#Requires -Version 5.1
<#
.SYNOPSIS
Descripción corta
.DESCRIPTION
Descripción detallada
.NOTES
Autor: David Antizar (Ntizar)
Hecho con ❤️ por David Antizar
#>
$ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$LibDir = Join-Path $ScriptDir "lib"
$LibDll = Join-Path $LibDir "libreria.dll"
Cargar librería .NET
if (-not (Test-Path $LibDll)) {
[System.Windows.Forms.MessageBox]::Show(
"No se encuentra libreria.dll en:`n$LibDir",
"Error", "OK", "Error"
)
exit 1
}
Add-Type -Path $LibDll
GUI con Windows Forms
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = "Título - David Antizar"
$form.Size = New-Object System.Drawing.Size(800, 650)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedSingle"
$form.MaximizeBox = $false
$form.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$form.BackColor = [System.Drawing.Color]::White
Leer Excel con COM (sin librería externa)
function Read-ExcelFile {
param([string]$RutaArchivo)
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$excel.DisplayAlerts = $false
$workbook = $excel.Workbooks.Open($RutaArchivo)
$worksheet = $workbook.Sheets[1]
$usedRange = $worksheet.UsedRange
$filas = $usedRange.Rows.Count
$columnas = $usedRange.Columns.Count
# Cabeceras (primera fila)
$cabeceras = @()
for ($col = 1; $col -le $columnas; $col++) {
$cabeceras += $worksheet.Cells.Item(1, $col).Text
}
# Datos
$datos = @()
for ($fila = 2; $fila -le $filas; $fila++) {
$valores = @{}
for ($col = 1; $col -le $columnas; $col++) {
$valores[$cabeceras[$col-1]] = $worksheet.Cells.Item($fila, $col).Text
}
$datos += [PSCustomObject]$valores
}
$workbook.Close($false)
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
return @{ Cabeceras = $cabeceras; Datos = $datos }
}
Pitfall: Sin [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel), el proceso EXCEL.EXE queda zombie en segundo plano.
Escribir Excel con COM
function Write-ExcelFile {
param([string]$RutaArchivo, [array]$Datos)
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$excel.DisplayAlerts = $false
$workbook = $excel.Workbooks.Add()
$worksheet = $workbook.Sheets[1]
$worksheet.Name = "METADATOS"
$columnas = $Datos[0].PSObject.Properties.Name
# Cabeceras
for ($col = 0; $col -lt $columnas.Count; $col++) {
$worksheet.Cells.Item(1, $col + 1) = $columnas[$col]
}
# Datos
for ($fila = 0; $fila -lt $Datos.Count; $fila++) {
for ($col = 0; $col -lt $columnas.Count; $col++) {
$worksheet.Cells.Item($fila + 2, $col + 1) = $Datos[$fila].$($columnas[$col])
}
}
$worksheet.Columns.AutoFit() | Out-Null
$workbook.SaveAs($RutaArchivo)
$workbook.Close($false)
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
}
Descargar librerías .NET desde NuGet
Si necesitas una librería .NET (ej: iTextSharp para PDFs):
curl -sL "https://www.nuget.org/api/v2/package/iTextSharp/5.5.13.4" -o itextsharp.nupkg
python3 -c "
import zipfile
with zipfile.ZipFile('itextsharp.nupkg', 'r') as z:
z.extractall('itextsharp')
for f in z.namelist():
if f.endswith('.dll'):
print(f)
"
cp itextsharp/lib/net461/itextsharp.dll /ruta/proyecto/src/lib/
Pitfall: Los .nupkg son archivos ZIP. Python zipfile funciona para extraerlos si unzip no está disponible.
Patrón iTextSharp para PDFs
Add-Type -Path "$LibDir\itextsharp.dll"
# Leer metadatos de PDF
$reader = New-Object iTextSharp.text.pdf.PdfReader($RutaPdf)
$info = $reader.Info # Hashtable con /Title, /Author, etc.
$reader.Close()
# Escribir metadatos en PDF
$tempFile = $RutaPdf + ".tmp"
$reader = New-Object iTextSharp.text.pdf.PdfReader($RutaPdf)
$stamper = New-Object iTextSharp.text.pdf.PdfStamper($reader, [System.IO.File]::Create($tempFile))
$infoDict = $stamper.Writer.Info
$infoDict["/Title"] = "Nuevo título"
$infoDict["/Author"] = "Autor"
$stamper.Close()
$reader.Close()
# Reemplazar original
Remove-Item $RutaPdf -Force
Rename-Item $tempFile $RutaPdf
Pitfalls
-
Execution Policy Bypass: Siempre usar powershell.exe -ExecutionPolicy Bypass -File en el .bat. Sin esto, PowerShell bloquea scripts por defecto.
-
COM Excel zombie: Siempre hacer ReleaseComObject después de usar Excel COM. Sino, EXCEL.EXE queda en memoria.
-
iTextSharp 5.x vs 7.x: iTextSharp 5.5.x es la versión con namespace iTextSharp.text.pdf. iTextSharp 7.x cambió a iText.Kernel.Pdf. Usar 5.5.x para simplificar.
-
Ruta relativa: %~dp0 en .bat = directorio del .bat. $MyInvocation.MyCommand.Path en PowerShell = ruta del script. Nunca usar rutas absolutas hardcodeadas.
-
GUI PowerShell: Windows Forms en PowerShell es limitado pero suficiente para herramientas simples. No intentar hacer frameworks complejos.
-
CSV fallback: Si Excel no está instalado, el COM approach falla. Ofrecer CSV como alternativa. El usuario puede exportar Excel a CSV.
-
pyinstaller .exe sin admin: Si el usuario puede ejecutar .exe pero no instalar Python, PyInstaller sigue siendo la primera opción. PowerShell es solo cuando el .exe está bloqueado por políticas.