¿Esa bat debería migrarse a PowerShell? — Inventario y criterio de migración de los activos cmd/bat
· Actualizado el: · Go Komura · PowerShell, Windows, Archivos bat, cmd, VBScript, Aprovechamiento de activos existentes, Mejora operativa, Scripts
«Con la migración del servidor aparecieron montones de archivos bat de hace veinte años. ¿Deberíamos reescribirlos todos en PowerShell?» — esta pregunta es un clásico en las consultas sobre activos legados. Copias y copias de seguridad, envoltorios (wrappers) para iniciar aplicaciones, trabajos de enlace nocturnos: los bat de cmd siguen sosteniendo en silencio la operación diaria de Windows.
Adelantando la conclusión: no hace falta reescribirlo todo. No hay ningún plan para que las bat dejen de funcionar de inmediato, y reescribir algo que ya funciona es en sí mismo un riesgo de fallo. Al mismo tiempo, sin duda existen bat en las que «dejarlas tal cual» es peligroso: las que llaman a VBScript, las que ocultan errores, las que van a cambiar próximamente. Decidir «conservarlas todas» o «migrarlas todas» sin hacer antes esta distinción es, en ambos casos, la antesala de un incidente.
Este artículo está dirigido al personal de sistemas y operaciones de pymes, y organiza el inventario de activos bat, los criterios de decisión de migración, las trampas propias de bat, la forma de hacer convivir bat y PowerShell durante el periodo de transición, y una tabla de equivalencias para la reescritura.
1. Primero, la conclusión
- cmd.exe y bat no tienen previsto desaparecer, pero Microsoft afirma explícitamente que recomienda usar PowerShell para la automatización de Windows. La orientación oficial es clara: si va a escribir algo nuevo, hágalo en PowerShell.1
- El desuso de VBScript se anunció oficialmente en octubre de 2023, y se ha publicado un plan gradual que pasa por convertirlo en una función de instalación bajo demanda (Feature on Demand) antes de eliminarlo del sistema operativo. Los activos que llaman a VBScript desde bat mediante
cscript/wscriptforman el grupo con mayor urgencia de migración.23 Como alternativa, la documentación oficial recomienda PowerShell.4 - La decisión de migración no es «todo o nada», sino una clasificación. Estable y sin cambios previstos → se conserva. Va a cambiar, necesita manejo de errores o necesita registro → se migra. Depende de VBScript → se migra con prioridad. Todo esto se organiza en la tabla de decisión del capítulo 4.
- Las debilidades estructurales de bat son no detenerse ante los errores, la trampa de
%ERRORLEVEL%y la codificación de caracteres.if errorlevel 1evalúa «1 o más»5, y%errorlevel%se rompe en cuanto se define una variable de entorno con el mismo nombre.5 - Al migrar a PowerShell se obtiene la canalización (pipeline) de objetos, el manejo de errores con try/catch6, el ensayo previo con -WhatIf7 y las pruebas con Pester. El valor esencial de la migración es «poder detectar los fallos, probar con seguridad y verificar de forma automática».
- Durante el periodo de convivencia, la solución práctica es llamar a powershell.exe / pwsh desde bat con
-File. El código de salida que devuelveexiten el script pasa tal cual a%ERRORLEVEL%en la bat, así que se puede sustituir el contenido conservando la gestión de trabajos existente.89 - Los comandos externos excelentes como robocopy no se reescriben: se llaman directamente desde PowerShell. Su código de salida va de 0 a 8 o más, con un esquema particular en el que 8 o más significa fallo, así que basta con escribir correctamente el criterio con
$LASTEXITCODE.1011 - Al distribuir los scripts hay que incluir la directiva de ejecución en el diseño. El valor predeterminado es Restricted (no permite ejecutar scripts) en los sistemas cliente con Windows PowerShell 5.1, y RemoteSigned en Windows Server y en PowerShell 7. Incluso con RemoteSigned se bloquean los scripts sin firmar que llevan la marca de origen de Internet.12
2. Por qué pensarlo ahora — cmd se queda, VBScript desaparece
Antes de nada, ordenemos los hechos de base. El intérprete de línea de comandos de Windows tiene dos líneas: cmd (el shell de comandos) y PowerShell, y la documentación oficial de Microsoft indica explícitamente que «para la automatización de Windows más robusta y moderna se recomienda usar PowerShell, y no los comandos de Windows ni Windows Script Host».1 Ahora bien, una recomendación sigue siendo solo eso: cmd en sí no figura en la lista de funciones en desuso de Windows. Por el momento no hay ningún plan para que las bat existentes dejen de funcionar.
El contraste lo pone VBScript. En octubre de 2023 se anunció oficialmente su desuso (deprecation). Se ha publicado un plan según el cual, en futuras versiones de Windows, se ofrecerá como función de instalación bajo demanda (Feature on Demand) y después se eliminará del sistema operativo.2 Al principio se seguirá incluyendo preinstalado, así que no dejará de funcionar de hoy para mañana3, pero el rumbo hacia la «eliminación» ya está decidido. En Windows Server 2025 también pasa a ser FoD, y la documentación oficial recomienda migrar a PowerShell como alternativa.4
Seamos precisos sobre los plazos. A fecha de julio de 2026, lo único que dice la documentación oficial de Microsoft es el orden de los pasos —«se ofrece como FoD y después se elimina en una futura versión de Windows»—, sin indicar el año ni el mes de la eliminación.23 Es decir, oficialmente no hay respuesta a «cuántos años faltan». Esto no debe leerse como «todavía falta, así que se puede dejar así», sino como que si se empieza a investigar solo después de anunciarse el plazo, ya no habrá tiempo suficiente. Con solo adelantar el inventario (capítulo 7), en el momento en que se anuncie la fecha de eliminación se podrá responder de inmediato: «a nosotros nos afectan estas tres».
Aquí lo importante es que VBScript suele estar escondido dentro de los activos bat. Una configuración en la que una bat invoca VBScript con algo como cscript //nologo convert.vbs fue un patrón habitual en la década de 2000. Una bat con esta configuración no es «segura por ser bat», sino que «comparte destino con VBScript». En el inventario, además de la propia bat, compruebe siempre a qué llama (en el capítulo 7 se incluye un script de exploración). La revisión completa de los activos de VBScript y VBA de la empresa se detalla en «Guía de auditoría de VBA y herramientas internas ante el fin de VBScript».
3. Las trampas de bat y lo que se gana con PowerShell
Como material para la decisión de migración, veamos en concreto las debilidades estructurales de bat. La siguiente bat tiene la forma que se ve a menudo en procesos de copia de seguridad.
@echo off
rem Copia de seguridad nocturna (una bat habitual y arriesgada)
xcopy C:\data \\backup01\share\data /E /Y
echo Copia de seguridad completada >> C:\logs\backup.log
Esta bat esconde tres problemas difíciles de notar.
- No se detiene ni siquiera ante un error. Aunque xcopy falle porque no llega al destino compartido, cmd sigue por defecto con la línea siguiente, y en el registro queda anotado «Copia de seguridad completada». Para detectar el fallo hay que escribir uno mismo, cada vez, la comprobación con
if errorlevel. %ERRORLEVEL%tiene una trampa.if errorlevel 1no evalúa «el código de salida es igual a 1», sino «1 o más».5 Además, la expresión%errorlevel%tiene la especificación de que «si no existe una variable de entorno llamada ERRORLEVEL, se expande al código de salida actual», así que en el momento en que alguien escribeset ERRORLEVEL=0, todas las comprobaciones posteriores quedan rotas.5 Sumado a la especificación deexit /b número, que devuelve el código de salida13, escribirlo correctamente exige un conocimiento considerable.- Incidentes de codificación de caracteres. En un entorno en japonés, cmd pertenece tradicionalmente al mundo Shift_JIS, y todavía hoy ocurren incidentes como que una bat vuelta a guardar en UTF-8 se corrompa, o que falle con rutas que contienen símbolos. El panorama completo de este problema se trata en «Codificación de caracteres y saltos de línea en Windows: fundamentos de la corrupción de texto y CRLF/LF».
Si se escribe el mismo proceso en PowerShell, el tratamiento de los fallos cambia de forma estructural.
# Copia de seguridad nocturna (esqueleto de la versión PowerShell)
$ErrorActionPreference = 'Stop' # Hace que los errores "detengan" la ejecución
try {
# El origen debe ser C:\data\*, que apunta al "contenido de la carpeta". Si se pasa
# 'C:\data', a partir de la segunda ejecución la carpeta de destino ya existe y
# queda anidada como data\data
Copy-Item -Path 'C:\data\*' -Destination '\\backup01\share\data' -Recurse -Force
Add-Content -Path 'C:\logs\backup.log' -Value "$(Get-Date -Format o) Copia de seguridad completada"
exit 0
}
catch {
# Permite registrar qué falló y dónde, conservando la información estructurada
Add-Content -Path 'C:\logs\backup.log' -Value "$(Get-Date -Format o) Error: $($_.Exception.Message)"
exit 1
}
try/catch captura los errores que terminan la instrucción, y la limpieza posterior se puede escribir en finally.6 Además, PowerShell ofrece como mecanismo común -WhatIf, que no ejecuta la operación destructiva y solo muestra «qué habría ocurrido»7, lo que permite ensayar con seguridad, contra el entorno de producción, un script recién reescrito.
Como -WhatIf explicado solo con palabras no siempre resulta intuitivo, veamos cómo se ve en la práctica. En el ejemplo de la documentación oficial, al ejecutar Remove-Item Date.csv -WhatIf el archivo no se elimina; solo se muestra la siguiente línea.7
What if: Performing operation "Remove File" on Target "C:\ps-test\date.csv".
Se muestra una línea por cada elemento afectado, así que incluso al escribir con un comodín, como en Remove-Item C:\logs\*.log -WhatIf, aparecen antes de ejecutar todos los archivos que se van a eliminar. Esto permite comprobar, sin dañar los datos de producción, si con esa condición realmente se afecta solo a lo que se pretendía (el idioma del mensaje mostrado depende de la configuración del entorno). bat no dispone de un mecanismo equivalente, así que no queda más remedio que recurrir al trabajo manual de montar el comando con echo y revisarlo a simple vista.
Además, si se escriben pruebas con Pester, se puede seguir confirmando de forma automática que «debería funcionar» (véase «Organización de pruebas de PowerShell con Pester: un patrón práctico para dificultar que se rompan los scripts operativos»). El diseño del manejo de errores y de los reintentos en sí se profundiza en el artículo publicado el mismo día «Diseño de manejo de errores y reintentos en PowerShell».
En definitiva, lo que se gana con la migración no es la novedad de la sintaxis, sino una calidad operativa consistente en poder detectar los fallos, probar con seguridad y protegerse con pruebas. Dicho de otro modo, en una bat sencilla, como un simple envoltorio de inicio en el que un fallo se nota enseguida, este valor apenas aporta. Por eso hace falta clasificar.
4. No migrar todo — la tabla de decisión para clasificar
| Punto | Opciones (se indica “recomendado” en la opción recomendada) | Criterio de decisión |
|---|---|---|
| Estable, sin cambios previstos, sencilla | Se conserva tal cual (recomendado) / Migrar | Los envoltorios de inicio de aplicaciones o las copias de pocas líneas pueden conservarse. La propia reescritura es un riesgo nuevo |
| Llama a VBScript (cscript/wscript) | Conservar / Migrar con prioridad (recomendado) | El plan desuso → FoD → eliminación de VBScript ya se anunció oficialmente. Dejarlo pasar solo acerca la fecha de retirada2 |
| Va a recibir cambios de especificación o nuevas funciones | Modificar sin salir de bat / Migrar y después modificar (recomendado) | El momento del cambio es la mejor oportunidad para migrar. Se hace la modificación y se organizan las pruebas a la vez |
| Necesita detección de fallos, registro o reintentos | Añadir comprobaciones a bat / Migrar (recomendado) | Cubrir todos los casos con if errorlevel es difícil de mantener. Un terreno donde try/catch y el registro estructurado son eficaces6 |
| Trabajos nocturnos (iniciados por el Programador de tareas) | Mantener el estado actual / Estudiar la migración (recomendado) | Precisamente en la ejecución desatendida, el tratamiento de los fallos es crucial. Revise también la cuenta de ejecución y el problema de “terminar con 0x1” (descrito más adelante en el capítulo 7) |
| El protagonista es un comando externo como robocopy | Reimplementar con cmdlets / Conservar el comando externo y llamarlo (recomendado) | Reimplementar un comando de eficacia probada sale a pérdida. Se pasa a PowerShell solo la llamada y la evaluación del resultado10 |
| La persona que la escribió ya no está en la empresa y se desconoce su especificación | No tocarla / Registrar el comportamiento y después decidir (recomendado) | Primero se documentan la entrada, la salida y la programación actuales. No se reescribe sin entenderla |
Cuando tenga dudas, hay dos principios. Primero, la migración sigue el orden de “dónde aporta más valor”, no el de “lo más antiguo primero”. Segundo, si va a tocar algo, empiece siempre por la lectura (el inventario y el registro).
5. La solución práctica durante la convivencia — llamar a PowerShell desde bat y transmitir el código de salida
Tras la clasificación, en muchos entornos se entra en un periodo de convivencia en el que «una parte sigue en bat y otra pasa a PowerShell». Aquí resulta útil una configuración en la que la bat se conserva como punto de entrada (el contacto con la gestión de trabajos) y solo el contenido pasa a PowerShell. Como el programador de trabajos y los manuales de operación siguen apuntando a la ruta de la bat, se puede modernizar el contenido sin romper el entorno que la rodea.
@echo off
rem bat de entrada: llama a job.ps1 de la misma carpeta y transmite el codigo de salida
rem %~dp0 es la carpeta de esta misma bat (practica recogida en la documentacion oficial)
rem si queda una variable de entorno que alguien fijo con set ERRORLEVEL=..., %ERRORLEVEL%
rem oculta el codigo de salida real; se borra antes de llamar para que vuelva a ser dinamico
rem no sobrescriba aqui la directiva de ejecucion: -ExecutionPolicy crea un ambito Process
rem de prioridad alta que debilita en silencio el AllSigned configurado por el administrador
set "ERRORLEVEL="
powershell.exe -NoProfile -File "%~dp0job.ps1"
exit /b %ERRORLEVEL%
La forma de resolver la ubicación del script con %~dp0, sin depender del directorio actual, es la que muestra la documentación oficial como ejemplo de llamada desde bat.9 Para ejecutarlo con PowerShell 7 basta con cambiar powershell.exe por pwsh (5.1 y 7 se instalan por separado y pueden convivir; sobre cuándo usar cada uno, véase el artículo publicado el mismo día «Diferencias entre Windows PowerShell 5.1 y PowerShell 7, y cómo migrar»).
La transmisión del código de salida puede resumirse así.
- Si el script termina con
exit 4, el código de salida del proceso pasa a ser 4, y se recibe en la bat mediante%ERRORLEVEL%. Esta interacción figura documentada con un ejemplo en la documentación oficial.8 - Cuando se llama con
-File, si el script muere por una excepción sin controlar, el código de salida es 1; si termina con normalidad, es 0. Para evitar el caso de «falló, pero devolvió 0», lo seguro es que el propio script evalúe el resultado y llame aexitde forma explícita.914 - El resultado de llamar a un comando externo desde PowerShell queda en la variable automática
$LASTEXITCODE.11
Una bat con robocopy como protagonista es un buen ejemplo de esta forma. robocopy es un comando de eficacia probada con reintentos (nada menos que 1.000.000 por defecto, con una espera de 30 segundos), duplicado (mirroring) y registro10, y no hay motivo para reimplementarlo con Copy-Item. Eso sí, su esquema de códigos de salida es particular: 0 significa “no había nada que copiar”, 1 significa “copia realizada correctamente” y 8 o más significa fallo.10 Si se evalúa con la regla general de «distinto de 0 es anómalo», una copia correcta se clasificará erróneamente como anómala.
# robocopy se usa tal cual; solo la evaluacion se hace correctamente en PowerShell
# /r:2 /w:5 -- el valor predeterminado de 1.000.000 de reintentos equivale a un
# bloqueo (hang) de facto en un trabajo desatendido, asi que hay que acotarlo siempre
robocopy 'C:\data' '\\backup01\share\data' /MIR /R:2 /W:5 /NP /LOG+:'C:\logs\robocopy.log'
if ($LASTEXITCODE -ge 8) {
Write-Error "robocopy ha fallado (código de salida: $LASTEXITCODE)"
exit 1
}
Write-Host "Sincronización completada (código de salida: $LASTEXITCODE)" # 0-7 son estados de éxito
exit 0
6. Tabla de equivalencias — del vocabulario de bat al de PowerShell
Esta es la tabla de equivalencias para usar al reescribir. Empléela no como una sustitución mecánica, sino como una tabla comparativa de «cómo expresar la misma intención».
| Forma de escribirlo en bat | Práctica habitual en PowerShell | Notas |
|---|---|---|
copy / move / del / md |
Copy-Item / Move-Item / Remove-Item / New-Item |
Para operaciones destructivas, ensaye primero con -WhatIf7 |
xcopy / robocopy |
Llamar a robocopy tal cual | No reimplementar. Evaluar con $LASTEXITCODE que 8 o más es fallo1011 |
for %%f in (*.csv) do ... |
Get-ChildItem *.csv \| ForEach-Object { ... } |
Por la canalización circulan objetos, no cadenas con el nombre de archivo |
if errorlevel 1 goto :error |
try/catch + $ErrorActionPreference = 'Stop' |
Para comandos externos, se sigue evaluando el código de salida como antes6 |
set VAR=value |
$var = 'value' (la variable de entorno es $env:VAR) |
Permite distinguir entre variables de entorno del proceso y variables normales |
call :sub / goto |
function |
Permite añadir tipo y validación a los argumentos |
findstr |
Select-String |
Las líneas coincidentes se devuelven como objetos y se pueden procesar después |
>> log.txt |
Start-Transcript / Add-Content |
Para capturar todo el registro de ejecución, Transcript es lo más sencillo |
rem |
# |
— |
El vocabulario básico del lado de PowerShell (cómo encontrar cmdlets, la canalización, las prácticas de confirmación) se resume en «Fundamentos de los comandos de PowerShell — las operaciones que hay que aprender primero y cómo usarlas con seguridad».
7. El procedimiento de migración por etapas — inventario → clasificación → piloto → funcionamiento en paralelo
Por último, organicemos el avance en cuatro etapas.
(1) Inventario. Empiece por una investigación de solo lectura. Identifique de forma mecánica dónde están las bat y si dependen de VBScript.
# Inventario de activos bat: lista de ubicaciones y deteccion de llamadas a VBScript (investigacion de solo lectura, segura)
# Las ubicaciones que no se pudieron enumerar son un hueco en el inventario, asi que se registran con -ErrorVariable para mostrarlas despues
$targets = Get-ChildItem -Path 'C:\', 'D:\jobs', '\\fileserver\scripts' -Recurse `
-Include '*.bat', '*.cmd' -File -ErrorAction SilentlyContinue -ErrorVariable enumErrors
$readErrors = @()
$report = foreach ($file in $targets) {
# Si hay una llamada a cscript/wscript/.vbs, se marca con la bandera "depende de VBScript"
# Las rutas ya enumeradas se pasan con -LiteralPath (si un nombre de archivo con
# corchetes, como [2026]job.bat, se pasa a -Path, se interpreta como comodin y
# apunta a otro archivo)
# Tambien se registran en $readErrors los archivos cuyo contenido no se pudo leer, por ejemplo por estar bloqueados
$vbs = Select-String -LiteralPath $file.FullName -Pattern 'cscript|wscript|\.vbs' -Quiet `
-ErrorAction SilentlyContinue -ErrorVariable +readErrors
[pscustomobject]@{
Path = $file.FullName
LastWriteTime = $file.LastWriteTime
UsesVBScript = $vbs
}
}
$report | Sort-Object UsesVBScript -Descending | Export-Csv 'C:\audit\bat-inventory.csv' -NoTypeInformation -Encoding UTF8
# Se deja constancia de las ubicaciones que no se pudieron investigar (si la lista no esta vacia, el inventario esta incompleto)
# Incluye tanto las ubicaciones cuya enumeracion fallo como los archivos que se enumeraron pero cuyo contenido no se pudo leer
@($enumErrors) + @($readErrors) | ForEach-Object { $_.TargetObject } |
Set-Content -Path 'C:\audit\bat-uninspected.txt'
El bat-inventory.csv resultante es una tabla sencilla de solo tres columnas. Las filas en las que UsesVBScript es True son las candidatas a migración prioritaria, y Sort-Object las agrupa al principio. El aspecto del contenido es el siguiente (las rutas y fechas son de ejemplo).
| Path | LastWriteTime | UsesVBScript |
|---|---|---|
\\fileserver\scripts\nightly\convert.bat |
2009/04/13 18:22:31 | True |
D:\jobs\eod\export_shipping.bat |
2014/11/07 9:41:02 | True |
D:\jobs\backup\copy_master.bat |
2021/06/02 14:05:47 | (vacío) |
C:\tools\launch_viewer.bat |
2018/02/19 11:30:15 | (vacío) |
Que la columna UsesVBScript quede vacía no es un fallo. Se debe a que -Quiet en Select-String devuelve $true cuando hay coincidencia, y cuando no la hay, en lugar de $false, tiene la especificación de devolver $null.15 Ordenar por «True o vacío» no da problemas en la práctica, pero si quiere que aparezca explícitamente False, conviértalo antes a un valor booleano, por ejemplo con $vbs = [bool](Select-String ...), antes de almacenarlo (el formato de visualización de la fecha y hora sigue la configuración regional del entorno de ejecución).
Solo con estas cuatro filas ya se avanza bastante en la decisión. Las dos primeras entran en «migrar con prioridad» del capítulo 4; la tercera, en «migrar si hay cambios previstos»; y el último envoltorio de inicio, en «conservar tal cual». En la práctica, a esto se le añaden a mano columnas de origen de inicio (Programador de tareas / herramienta de gestión de trabajos / manual) y de responsable, y así se convierte directamente en el registro del plan de migración. Los archivos con LastWriteTime de hace más de diez años son candidatos a «la persona que los escribió ya no está», así que es más seguro reservar de antemano tiempo para entenderlos.
(2) Clasificación de riesgo. Aplique el resultado del inventario a la tabla de decisión del capítulo 4 y clasifique cada bat en «conservar», «migrar» o «migrar con prioridad (depende de VBScript)». Al mismo tiempo, registre el origen de inicio de cada bat (Programador de tareas, herramienta de gestión de trabajos, manual) y el alcance del impacto en caso de fallo. Aquí conviene tener presente el problema de «terminar con 0x1». El 0x1 que aparece en el «resultado de la última ejecución» de una tarea no es un error del propio Programador de tareas, sino que significa que el programa iniciado devolvió el código de salida 1; la causa está casi siempre en «la diferencia de entorno entre la ejecución manual y la ejecución programada». El caso típico es que, al no especificarse «Iniciar en (opcional)» en la tarea, el directorio actual pasa a ser C:\Windows\System32, y la bat que asume rutas relativas se rompe. Otras causas incluyen que las variables de entorno derivadas del perfil o las unidades de red asignadas no existan en la sesión desatendida, o que la directiva de ejecución sea distinta. El procedimiento para aislar la causa se explica en «La tarea del Programador de tareas no se ejecuta o termina con 0x1 — cómo aislar la causa y diseñar una operación segura».
(3) Piloto. Elija una bat de impacto reducido y reescríbala con el método de la bat de entrada del capítulo 5. En este paso, incluya la directiva de ejecución en el diseño. El valor predeterminado de Windows es RemoteSigned: los scripts creados localmente funcionan, pero los scripts sin firmar que llevan la marca de origen de Internet quedan bloqueados.12 El diagnóstico para cuando surgen problemas al distribuir por una carpeta compartida, junto con el diseño de fondo que incluye el flujo de firma, se resume en el artículo publicado el mismo día «Directiva de ejecución y firma de scripts en PowerShell».
(4) Funcionamiento en paralelo. Haga correr la bat antigua y el nuevo script durante un periodo determinado. Separe los destinos de escritura y compare las salidas; en el lado nuevo, hágalo funcionar al principio solo con -WhatIf o con registro; deje preparado el retroceso de forma que solo haya que volver a apuntar el trabajo hacia la bat. Si retira la bat antigua después de haber preparado todo esto, es muy poco probable que la propia migración se convierta en un incidente.
8. Resumen
- cmd y bat no tienen previsto desaparecer, pero la recomendación oficial es PowerShell. VBScript, en cambio, tiene anunciado el plan desuso → FoD → eliminación, y el VBScript llamado desde bat es el objetivo de migración de máxima prioridad.
- La migración no es «todo o nada», sino clasificación. Las bat estables y sin cambios se conservan; se empieza a migrar por las que van a cambiar, las que necesitan manejo de errores y registro, y las que dependen de VBScript.
- bat no se detiene ante los errores,
if errorlevel 1evalúa “1 o más” y%errorlevel%se rompe con una variable de entorno del mismo nombre. PowerShell ofrece con try/catch, -WhatIf y Pester la posibilidad de “detectar, probar y proteger”. - Durante la convivencia, la solución práctica es llamar a
powershell.exe -File(o apwsh -File) desde la bat de entrada y transmitir el código de salida medianteexity%ERRORLEVEL%. - Los comandos externos excelentes como robocopy no se reescriben; basta con escribir correctamente la evaluación de
$LASTEXITCODE(8 o más significa fallo). - El procedimiento es inventario (empezando por la lectura) → clasificación de riesgo → piloto → funcionamiento en paralelo. No olvide tampoco el diseño de la directiva de ejecución y de la distribución.
Artículos relacionados
- Guía de auditoría de VBA y herramientas internas ante el fin de VBScript
- Fundamentos de los comandos de PowerShell — las operaciones que hay que aprender primero y cómo usarlas con seguridad
- La tarea del Programador de tareas no se ejecuta o termina con 0x1 — cómo aislar la causa y diseñar una operación segura
- Codificación de caracteres y saltos de línea en Windows - fundamentos de la corrupción de texto y CRLF/LF
- Directiva de ejecución y firma de scripts en PowerShell
- Diseño de manejo de errores y reintentos en PowerShell
Áreas de consultoría relacionadas
KomuraSoft LLC se encarga del inventario y la planificación de la migración de activos legados donde conviven bat, VBScript y PowerShell, del diseño operativo al convertir trabajos nocturnos a PowerShell, y de la investigación de problemas derivados de la migración. Puede consultarnos incluso a partir de la simple tarea de descifrar una bat cuyo autor ya no está en la empresa.
- Migración y aprovechamiento de activos existentes
- Modernización y mantenimiento del entorno Windows
- Consultoría técnica y revisión de diseño
- Investigación de fallos y análisis de causas
- Contacto
Referencias
-
Microsoft Learn, Windows Commands. Sobre la existencia de dos shells en Windows, cmd (el shell de comandos) y PowerShell, y sobre la afirmación oficial de que para la automatización de Windows más robusta y moderna se recomienda usar PowerShell en lugar de los comandos de Windows o Windows Script Host. ↩ ↩2
-
Microsoft Learn, Deprecated features for Windows client. Sobre el anuncio del desuso de VBScript en octubre de 2023, y sobre el plan de ofrecerlo como Feature on Demand en futuras versiones de Windows antes de eliminarlo del sistema operativo. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Resources for deprecated features. Sobre el hecho de que el Feature on Demand de VBScript se ofrece preinstalado al principio, y de que se puede seguir usando sin interrupción durante el periodo de preparación para su retirada. ↩ ↩2 ↩3
-
Microsoft Learn, Features removed or no longer developed in Windows Server. Sobre el hecho de que en Windows Server 2025 VBScript se ofrece como FoD y se elimina en una versión posterior, y sobre la recomendación oficial de usar PowerShell como alternativa para la automatización de tareas y scripts. ↩ ↩2
-
Microsoft Learn, if. Sobre el hecho de que
if errorlevel <number>es una evaluación de “mayor o igual”, verdadera cuando el código de salida del programa anterior es igual o mayor que number, y sobre el hecho de que%errorlevel%es una expansión que asume que no existe una variable de entorno llamada ERRORLEVEL, devolviendo ese valor si se ha definido una variable con el mismo nombre. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, about_Try_Catch_Finally. Sobre el hecho de que los bloques try/catch/finally gestionan los errores que terminan una instrucción y los que terminan un script, de que en catch se puede especificar el tipo de excepción, y de que finally se ejecuta haya o no error y sirve para las tareas de limpieza posteriores. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, about_CommonParameters. Sobre los parámetros de reducción de riesgo (-WhatIf/-Confirm) que ofrecen los cmdlets que modifican el sistema o los datos, y sobre el hecho de que -WhatIf no ejecuta el comando y solo muestra una descripción de su efecto. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, about_Language_Keywords. Sobre el hecho de que la palabra clave exit fija $LASTEXITCODE y se corresponde con %ERRORLEVEL% en el lado de cmd.exe, un ejemplo real en el que un
exit 4de un script ejecutado conpwsh -Filese observa como 4 en el %ERRORLEVEL% del que lo llamó, y el hecho de que, sin una instrucción exit, el resultado es 0 si termina con normalidad y 1 si hay una excepción sin controlar. ↩ ↩2 -
Microsoft Learn, about_Pwsh. Sobre el uso del parámetro -File de pwsh, el ejemplo oficial de usar %~dp0 para representar el directorio de ejecución al llamar desde un script por lotes, el hecho de que el código de salida es 1 cuando ocurre un error que termina el script con -File, y el hecho de que, si se termina con el comando exit, ese valor numérico pasa a ser el código de salida. ↩ ↩2 ↩3
-
Microsoft Learn, robocopy. Sobre el esquema de códigos de salida de robocopy (0 = no había nada que copiar, 1 = todos los archivos se copiaron correctamente, 8 o más = al menos un fallo), el hecho de que el valor predeterminado de reintentos es /r:1000000 (un millón) y el de espera es /w:30 segundos, y sobre opciones como el duplicado (mirroring) y el registro. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, about_Automatic_Variables. Sobre el hecho de que la variable automática $LASTEXITCODE almacena el código de salida de programas nativos y scripts, y sobre cómo se determina su valor al ejecutar un script con -File (1 con una excepción, el valor indicado con exit, 0 al terminar con normalidad). ↩ ↩2 ↩3
-
Microsoft Learn, about_Execution_Policies. Sobre el hecho de que la directiva de ejecución predeterminada de Windows es RemoteSigned, de que RemoteSigned exige a los scripts procedentes de Internet la firma de un editor de confianza y no exige firma a los scripts creados localmente, y sobre el significado de cada directiva (Restricted/AllSigned/Bypass, etc.). ↩ ↩2
-
Microsoft Learn, exit. Sobre el hecho de que
exit /b <exitcode>termina el script por lotes y fija el valor indicado en la variable de entorno ERRORLEVEL, y de que al terminar el propio cmd ese valor se fija como código de salida del proceso. ↩ -
Microsoft Learn, about_PowerShell_exe. Sobre la especificación del parámetro -File en powershell.exe de Windows PowerShell 5.1, la sintaxis %windir% para pasar variables de entorno desde cmd.exe, y el tratamiento del código de salida. ↩
-
Microsoft Learn, Select-String. Sobre el hecho de que Select-String es un cmdlet que busca texto mediante expresiones regulares, y de que, al especificar -Quiet, el valor devuelto no es un objeto MatchInfo, sino $true si se encuentra el patrón y $null si no se encuentra. ↩
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Diseño de parámetros y modularización de scripts de PowerShell — de un «script que funciona» a un «script que se puede entregar»
Explicamos cómo llevar un script de PowerShell a una calidad entregable: param, [CmdletBinding()], validación, pipeline, -WhatIf, módulos...
Diferencias entre Windows PowerShell 5.1 y PowerShell 7 ── Guía práctica de migración de scripts internos
Explicamos la relación entre Windows PowerShell 5.1 y PowerShell 7 (coexistencia y pwsh.exe), la política oficial de no añadir funciones ...
Dónde mirar cuando un script de PowerShell es lento — claves de arrays, pipeline y cruces de datos
Analizamos las causas típicas de la lentitud en PowerShell: += en arrays, pipeline vs. foreach, cruces con tablas hash, E/S de archivos y...
Deje de usar Write-Host — Flujos de salida de PowerShell y diseño de registros
Explica los seis flujos de salida de PowerShell, los problemas de Write-Host y su uso correcto, por qué se contamina el valor de retorno ...
Procesamiento paralelo en PowerShell — Cuándo usar ForEach-Object -Parallel y cuándo usar jobs
Diferencias entre ForEach-Object -Parallel, Start-ThreadJob y Start-Job, uso de $using:, ThrottleLimit y cuándo el paralelismo resulta má...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de aplicaciones para Windows
Aplicaciones empresariales, integración de dispositivos y herramientas de comunicación, de los requisitos al desarrollo.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Deberían migrarse a PowerShell todas las bat de la empresa?
- No se recomienda migrarlas todas. Una bat que lleva años funcionando de forma estable y sin cambios previstos no debería reescribirse, porque la reescritura en sí misma introduce un nuevo riesgo de fallo. La prioridad de migración es alta para las que van a cambiar próximamente, las que necesitan manejo de errores o registro (log), y las que llaman a VBScript (cscript/wscript). La práctica habitual es clasificarlas con una tabla de decisión y migrar de forma gradual empezando por las de mayor valor.
- ¿Se van a eliminar cmd.exe y los archivos bat?
- cmd.exe no figura en la lista de funciones en desuso de Windows y no hay ningún anuncio de su eliminación. Sin embargo, Microsoft afirma explícitamente que recomienda usar PowerShell, y no comandos ni WSH, para la automatización de Windows. VBScript, en cambio, tiene un anuncio oficial de desuso desde octubre de 2023: en las próximas versiones de Windows pasará a ser una función de instalación bajo demanda (Feature on Demand) y después se aplicará un plan gradual para eliminarlo del sistema operativo. La propia bat seguirá funcionando, pero el VBScript que llama desde ella dejará de funcionar sin remedio en algún momento.
- ¿Cómo se llama a un script de PowerShell desde un archivo bat?
- Se pasa el script a powershell.exe (o a pwsh.exe en PowerShell 7) con el parámetro -File. Si el script está en la misma carpeta que la bat, la forma recogida en la documentación oficial es resolver la ubicación con %~dp0, como en «powershell.exe -NoProfile -File "%~dp0job.ps1"». Conviene no añadir -ExecutionPolicy en la bat, porque esa opción crea un ámbito Process de prioridad alta que debilita la directiva configurada por el administrador; es mejor dejar que rija el diseño de la directiva de ejecución del entorno. Si el script devuelve exit con un número, ese valor pasa tal cual a %ERRORLEVEL% en la bat, de modo que se puede convertir el contenido a PowerShell sin tocar el mecanismo de gestión de trabajos existente.
- ¿Qué trampas tiene %ERRORLEVEL% en bat?
- La más conocida es que «if errorlevel 1» evalúa «el código de salida es 1 o mayor» (no una igualdad). Además, %errorlevel% es una expansión dinámica que asume que no existe una variable de entorno llamada ERRORLEVEL; si alguien la define explícitamente, por ejemplo con set ERRORLEVEL=0, a partir de ese momento siempre devolverá ese valor fijo. Y como bat, por defecto, sigue con la línea siguiente aunque un comando falle, un fallo cuya comprobación se olvidó escribir queda silenciosamente enterrado. Precisamente porque PowerShell tiene muchas menos trampas de este tipo, migrar hacia él con su manejo de errores resulta motivador.
- ¿Cómo se reescribe en PowerShell una bat que usa robocopy?
- Lo habitual es no reescribirla. robocopy es un comando externo excelente, con funciones probadas como reintentos, duplicado (mirroring) y registro, y se puede llamar directamente desde PowerShell. Reimplementarlo con Copy-Item cuesta trabajo y, además, reduce la fiabilidad. El punto a vigilar es la interpretación del código de salida: robocopy devuelve valores de 0 a 8 o más, donde 8 o más significa fallo y 1 significa una copia completada correctamente. En PowerShell se comprueba $LASTEXITCODE y se decide «si es 8 o más, hay un problema».
- Al distribuir un script de PowerShell me topé con la directiva de ejecución. ¿Qué debo hacer?
- La directiva de ejecución predeterminada (RemoteSigned) exige firma en los scripts marcados como procedentes de Internet. Si el bloqueo ocurre al distribuir por una carpeta compartida, primero hay que confirmar la causa; como solución permanente, lo correcto es adoptar un flujo de firma o unificar la política mediante la directiva de grupo. Añadir -ExecutionPolicy Bypass en la llamada desde la bat es una salida cómoda, pero úsela solo después de confirmar que es el estado que realmente se pretende como diseño de la política de la organización.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.