Las tareas del Programador de tareas no se ejecutan o terminan con 0x1 — cómo aislar la causa y diseñar una operación segura
· Actualizado el: · Go Komura · Programador de tareas, Windows, PowerShell, Automatización de procesos, Procesamiento por lotes, Operaciones, Solución de problemas, Consultoría técnica
«Quiero ejecutar todas las mañanas a las 6 el script de agregación que hice en PowerShell», «si lo ejecuto a mano funciona, pero al programarlo en el Programador de tareas deja de funcionar». Cuando atiendo consultas de automatización de procesos, casi siempre se termina llegando a este tema.
En este blog también hemos escrito una serie de artículos sobre automatización: automatización de la organización de logs, pruebas de scripts con Pester, ejecución de PowerShell desde C# y automatización de procesos con Power Automate. Todos estos artículos dan por sentado, en última instancia, que la tarea se «ejecuta periódicamente mediante el Programador de tareas». Sin embargo, el propio Programador de tareas resulta ser un mecanismo con más particularidades de lo esperado, y no dejan de producirse incidentes del tipo «funciona en manual pero falla en la ejecución programada» o «nadie se dio cuenta de que llevaba un tiempo detenida».
En este artículo organizamos, en el orden en que se suele tropezar en la práctica, las partes del mecanismo del Programador de tareas que están directamente relacionadas con incidentes de operación: la cuenta de ejecución y el tipo de inicio de sesión, cómo aislar el problema cuando «no se ejecuta», las causas típicas del valor de retorno 0x1, cómo dejar registro (logs) y el control de ejecuciones múltiples.
Contexto de este artículo
| Elemento | Contenido |
|---|---|
| SO de referencia | Se asume el Programador de tareas (línea Task Scheduler 2.0) de Windows 10 / 11 y Windows Server 2016 en adelante. Si en su entorno todavía quedan tareas heredadas del antiguo comando at, empiece por hacer un inventario de ellas |
| Lector previsto | Personal de sistemas o desarrollo que sabe escribir scripts de PowerShell o por lotes, pero se atasca al ponerlos en ejecución programada |
| Operación | Se cubren tanto la GUI (taskschd.msc) como el módulo ScheduledTasks de PowerShell. No se incluyen capturas de pantalla. En su lugar, los nombres de pestañas, campos y botones se citan tal como aparecen en pantalla, así que le conviene tener el Programador de tareas abierto mientras lee |
| Supuesto de entorno de dominio | La gMSA (sección 3.3) es un tema exclusivo de entornos de dominio. Si trabaja en un entorno de grupo de trabajo, puede saltarse esa parte |
1. Conclusión principal
- La mayoría de los problemas del Programador de tareas no provienen del script en sí, sino de un desajuste en la comprensión de «como quién y en qué tipo de sesión se ejecuta». En el momento en que selecciona «Ejecutar tanto si el usuario inició sesión como si no», diseñe partiendo de la base de que la tarea se ejecutará en un mundo distinto tanto de la sesión interactiva como del entorno propio del inicio de sesión.1
- La investigación de «no se ejecuta» debe partir de la pestaña Historial y del registro de eventos. Sin embargo, el historial de tareas está deshabilitado de forma predeterminada, así que antes de poner la tarea en producción active siempre «Habilitar todo el historial de tareas».2
- El
0x1que aparece en «Resultado de la última ejecución» no es un error del Programador de tareas, sino que significa que el propio programa iniciado devolvió el código de salida 1. La causa está del lado del script, así que antes que nada hay que diseñar el código de salida y crear un mecanismo propio de registro (log).3 - La forma básica de invocar un script de PowerShell es
-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "ruta completa", y las rutas dentro del script deben basarse en$PSScriptRoot. Aquí también se suele caer en la trampa clásica de poner comillas en el campo «Iniciar en (opcional)», algo que no se debe hacer. - Para evitar el incidente de que la tarea deje de funcionar silenciosamente tras un cambio de contraseña, hace falta diseñar bien la cuenta de ejecución (inventariar las cuentas de servicio y, en un entorno de dominio, valorar el uso de una gMSA).4
- El control de ejecuciones múltiples (de forma predeterminada, «No iniciar una nueva instancia»), el límite de tiempo de ejecución (predeterminado: 3 días) y la condición de alimentación (predeterminado: solo con corriente alterna) son ajustes representativos que se suelen dejar sin querer en su valor predeterminado. Decídalos siempre de forma explícita al registrar la tarea.5
2. Estructura del Programador de tareas — desencadenador, acción, condiciones y configuración
Una tarea del Programador de tareas se compone, a grandes rasgos, de cuatro elementos.
| Elemento | Contenido | Punto que suele causar incidentes |
|---|---|---|
| Desencadenador | Cuándo se inicia (hora, al iniciar sesión, al producirse un evento, etc.) | Cómo se trata un desencadenador de hora que se pasa de largo (StartWhenAvailable, más adelante) |
| Acción | Qué se ejecuta (programa, argumentos, carpeta de inicio) | Comillado de argumentos, errores al especificar la carpeta de inicio |
| Condiciones | En qué situación está permitido ejecutar (alimentación, red, inactividad) | De forma predeterminada está activo «solo con corriente alterna» |
| Configuración | Comportamiento durante la ejecución (ejecuciones múltiples, límite de tiempo, reintentos) | Poner la tarea en producción sin revisar los valores predeterminados |
Una tarea creada con la GUI (taskschd.msc) se puede exportar como XML. Si quiere gestionar la definición de la tarea con Git, o distribuir la misma tarea a varios equipos, es recomendable exportarla como XML y usar schtasks /Create /XML, o bien convertirla en un script con el módulo ScheduledTasks de PowerShell (New-ScheduledTaskAction / New-ScheduledTaskTrigger / New-ScheduledTaskSettingsSet / Register-ScheduledTask).5
$action = New-ScheduledTaskAction -Execute 'pwsh.exe' `
-Argument '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Jobs\Cleanup-Logs.ps1"' `
-WorkingDirectory 'C:\Jobs'
$trigger = New-ScheduledTaskTrigger -Daily -At '06:00'
# La condición de alimentación predeterminada es "iniciar solo con corriente alterna
# y detener al pasar a batería". Si el trabajo también debe correr en portátiles
# o equipos de campo, permítalo aquí de forma explícita
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Hours 2) `
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
# Recibir la contraseña mediante Get-Credential para no mostrarla en pantalla
$cred = Get-Credential -UserName 'DOMAIN\svc-batch' -Message 'Credenciales de la cuenta de ejecución'
Register-ScheduledTask -TaskName 'KS-Cleanup-Logs' `
-Action $action -Trigger $trigger -Settings $settings `
-User $cred.UserName -Password $cred.GetNetworkCredential().Password
Si la definición de la tarea está en forma de código, puede llevar tal cual a producción lo que probó en la máquina de validación, y evita la situación de «no saber con qué configuración está funcionando».
Para desplegarla en varios equipos, también existe experiencia contrastada con el método de exportar a XML la tarea creada con la GUI y distribuirla con schtasks.
rem Exportar la tarea creada en la máquina de validación
schtasks /Query /TN "KS-Cleanup-Logs" /XML > KS-Cleanup-Logs.xml
rem Importarla en cada equipo (la cuenta de ejecución y la contraseña se indican al registrarla)
schtasks /Create /TN "KS-Cleanup-Logs" /XML KS-Cleanup-Logs.xml /RU DOMAIN\svc-batch /RP *
El XML incluye el desencadenador, las condiciones y la configuración, así que si lo guarda en el repositorio podrá revisar la definición de la tarea y gestionar sus diferencias como código. Por el contrario, registrar manualmente la misma tarea mediante la GUI en diez equipos acaba generando, sin falta, una «tarea descarriada» con la configuración distinta en uno de ellos. Es más seguro convertirlo en código antes de que el número de equipos llegue a dos dígitos.
3. Cuenta de ejecución y tipo de inicio de sesión — el punto de fallo más frecuente
Las opciones «Ejecutar solo si el usuario inició sesión» y «Ejecutar tanto si el usuario inició sesión como si no», que se seleccionan en las propiedades de la tarea, corresponden internamente a la elección del tipo de inicio de sesión (LogonType). Si este punto no se entiende bien, se acaba dando vueltas sin poder explicar la mayoría de los casos de «funciona en manual pero no en ejecución programada».1
3.1 Diferencias entre los tres modos
| Selección | Mecanismo interno | Características y limitaciones |
|---|---|---|
| Ejecutar solo si el usuario inició sesión | Token interactivo (InteractiveToken) | La ventana es visible en la pantalla mientras la sesión está iniciada. Si la sesión está cerrada, la tarea directamente no se inicia |
| Ejecutar tanto si el usuario inició sesión como si no | Contraseña almacenada (Password) | La contraseña se guarda al registrar la tarea. No hay pantalla visible (no interactivo). Un cambio de contraseña provoca fallos de inicio |
| Lo mismo, más «No almacenar la contraseña» | S4U | A cambio de no almacenar la contraseña, no se puede acceder a recursos de red ni a archivos cifrados (EFS)1 |
Estos son los incidentes típicos que se ven en la práctica.
- Se registró con «No almacenar la contraseña» (S4U) un script que accede a una carpeta compartida. En las pruebas locales funcionaba, pero en producción solo falla el acceso a la carpeta compartida. → Porque S4U no dispone de credenciales de red.
- Meses después de registrarla con «Ejecutar tanto si el usuario inició sesión como si no», llegó la caducidad de la contraseña del dominio y esta se cambió. A partir de entonces la tarea quedó detenida de forma continua por fallo de inicio de sesión (
0x8007052E), pero nadie se dio cuenta. - Se registró con «ejecutar tanto si… como si no» una tarea que inicia una aplicación con interfaz gráfica. La aplicación se inicia, pero su ventana no aparece en ningún sitio, lo que se malinterpreta como que «no funciona». → Porque se ejecuta en una sesión no interactiva. Los procesos que necesitan una pantalla interactiva, en principio, no se pueden ejecutar con esta configuración.
Además, la cuenta que se ejecuta con Password o S4U necesita el derecho «Iniciar sesión como trabajo por lotes» (SeBatchLogonRight). Este derecho está concedido de forma predeterminada al grupo Administradores, pero si va a usar como cuenta de servicio a un usuario estándar dedicado, revise también la configuración de la directiva de seguridad local.6
3.2 Qué cuenta usar para ejecutar la tarea
- SYSTEM: no requiere gestionar contraseña y es una cuenta potente, pero sus permisos son excesivos. Es cómoda para tareas de mantenimiento que se completan localmente, pero conviene evitar poner como SYSTEM cualquier trabajo que toque datos de negocio. La forma de pensar sobre si realmente se necesitan permisos de administrador se explica en otro artículo, «Cuándo hace falta realmente el permiso de administrador en una aplicación de Windows».
- Cuenta de servicio dedicada (usuario estándar): permite aplicar el principio de mínimo privilegio, pero a cambio requiere actualizar la tarea cada vez que cambia la contraseña. Gestione conjuntamente la caducidad de la contraseña y el inventario de tareas.
- gMSA (cuenta de servicio administrada de grupo): es la primera opción en un entorno de dominio. Como el controlador de dominio administra la contraseña de forma automática, el propio problema de «la tarea muere al cambiar la contraseña» desaparece. El Programador de tareas admite la ejecución con gMSA.4
Por cierto, la casilla «Ejecutar con los privilegios más altos» significa ejecutar con el token del lado administrador (elevado) de los dos en que UAC divide el token. No la marque en trabajos que no necesiten permisos de administrador.
3.3 Procedimiento mínimo para registrar una tarea con una gMSA
Ya que hemos escrito que la gMSA es la «primera opción», mostremos también cómo registrarla en la práctica. Las tareas del Programador de tareas están documentadas oficialmente como una de las configuraciones compatibles con gMSA.4
Los requisitos previos son los siguientes.4
- Que el nivel funcional del dominio y del bosque sea Windows Server 2012 o posterior
- Que el dominio ya tenga creada una clave raíz de KDS (la creación se puede confirmar con el ID de evento 4004 del registro Operational de
KdsSvc) - Que, para crear y administrar la gMSA, se sea miembro de Domain Admins o Enterprise Admins
- El nombre de la gMSA debe ser único a nivel de bosque, no de dominio. Si existe el mismo nombre en otro dominio, la creación fallará
El procedimiento consta de 3 pasos. Los pasos 1 y 2 los realiza el administrador de dominio, y el paso 3 se ejecuta en cada equipo donde vaya a correr la tarea.
# --- 1. Lado del dominio: crear la gMSA. Se indica mediante un grupo de seguridad qué hosts pueden obtener la contraseña ---
# En <SecurityGroup> se indica el grupo que contiene las cuentas de equipo de los servidores donde correrá la tarea
New-ADServiceAccount -Name 'svc-batch' -DNSHostName 'svc-batch.contoso.local' `
-PrincipalsAllowedToRetrieveManagedPassword 'GG-BatchHosts'
# --- 2. En cada equipo donde correrá la tarea: instalar la gMSA y comprobar si puede obtener la contraseña ---
Install-ADServiceAccount -Identity 'svc-batch'
Test-ADServiceAccount -Identity 'svc-batch' # Si devuelve True, se puede usar
El tercer paso es el registro de la tarea. Hay dos puntos clave.
# --- 3. En el equipo donde correrá la tarea: registrarla usando la gMSA como principal ---
$action = New-ScheduledTaskAction -Execute 'pwsh.exe' `
-Argument '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Jobs\Cleanup-Logs.ps1"' `
-WorkingDirectory 'C:\Jobs'
$trigger = New-ScheduledTaskTrigger -Daily -At '06:00'
# Punto 1: añadir $ al final del nombre de la cuenta (formato del nombre de una gMSA)
# Punto 2: el LogonType es Password, pero no se pasa -Password
# (la contraseña de la gMSA la administra el controlador de dominio
# y la obtiene el propio host)
$principal = New-ScheduledTaskPrincipal -UserId 'CONTOSO\svc-batch$' `
-LogonType Password -RunLevel Limited
Register-ScheduledTask -TaskName 'KS-Cleanup-Logs' `
-Action $action -Trigger $trigger -Principal $principal
Los valores que se pueden indicar en -LogonType de New-ScheduledTaskPrincipal son None / Password / S4U / Interactive / Group / ServiceAccount / InteractiveOrPassword.7 Como vimos en la tabla de la sección 3.1, S4U no puede acceder a recursos de red, así que no lo elija a la ligera en trabajos que toquen carpetas compartidas.
Dos aclaraciones. Primero, esta gMSA también necesita el derecho «Iniciar sesión como trabajo por lotes» (es el mismo tema del final de la sección 3.1: no queda exenta por ser una gMSA).6 Segundo, schtasks.exe es un comando que parte de la base de pasar la contraseña de la cuenta de ejecución con /RP, y el procedimiento de registro con gMSA no está documentado oficialmente para ese comando. Si va a usar una gMSA, lo más fiable es apoyar el registro de la tarea en Register-ScheduledTask de PowerShell. Si además combina esto con el método de «exportar a XML y distribuir con schtasks» descrito en el capítulo 2, la configuración consistirá en sobrescribir solo la parte del principal mediante PowerShell.
4. Procedimiento para aislar por qué «no se ejecuta»
4.1 Active el historial antes de sospechar de nada
La opción «Habilitar todo el historial de tareas», situada en el panel derecho del Programador de tareas, está deshabilitada de forma predeterminada. Si el historial permanece deshabilitado, ni siquiera queda registrado el hecho de que algo falló. Actívela siempre antes de poner la tarea en producción. El procedimiento es el siguiente.
- Abra el Programador de tareas (
taskschd.msc, o busque «Programador de tareas» en el menú Inicio). Ábralo con permisos de administrador. - En el árbol del panel izquierdo, seleccione el nodo superior «Programador de tareas (local)». Esta opción no aparece si tiene seleccionada una tarea individual.
- Haga clic en «Habilitar todo el historial de tareas», dentro de la lista «Acciones» del panel derecho. Si ya está habilitado, esta opción aparecerá como «Deshabilitar todo el historial de tareas».
El historial en realidad es el registro Registros de aplicaciones y servicios > Microsoft > Windows > TaskScheduler > Operational del Visor de eventos.2 La pestaña «Historial» de una tarea individual no es más que este mismo registro filtrado por la tarea en cuestión, así que cuando quiera seguir la cronología a través de varias tareas, abra el Visor de eventos.
El procedimiento básico de aislamiento del problema, siguiendo tal cual la guía de solución de problemas de Microsoft, funciona perfectamente bien.2
- Probar el script de forma aislada — antes de ponerlo en la tarea, confirme que el propio script se completa en condiciones equivalentes a las de la cuenta de ejecución (con
runaso en una máquina de validación, si es posible). - Revisar la columna Estado y la pestaña Historial — para distinguir si la tarea llegó siquiera a dispararse o si se inició pero falló. Si no se disparó, sospeche de la configuración del desencadenador y de las condiciones (alimentación, red), y compruebe si la acción en sí funciona con una ejecución manual (clic derecho → Ejecutar).
- Cambiar temporalmente a «Ejecutar solo si el usuario inició sesión» — si con esto funciona, puede acotar la causa a la sesión no interactiva o a las credenciales (capítulo anterior).
4.2 Qué sospechar según lo que aparece en la pestaña Historial — tabla de referencia de ID de evento
Todas las filas de la pestaña Historial (y del registro Operational) tienen un ID de evento. Con solo mirar este ID se sabe de inmediato «hasta dónde llegó» la ejecución. Una ejecución normal de una tarea suele seguir, a grandes rasgos, el orden «Inicio (100) → Inicio del proceso (129) → Inicio de la acción (200) → Fin de la acción (201) → Finalización (102)».
| ID de evento | Significado del mensaje | Qué sospechar cuando aparece |
|---|---|---|
| 106 | Un usuario registró la tarea8 | Registro de la creación. Punto de partida para seguir «cuándo y quién cambió algo» |
| 140 / 141 | Un usuario actualizó / eliminó la tarea8 | Aquí se busca al «culpable» de «hasta ayer funcionaba» |
| 113 | La tarea se pudo registrar, pero algunos desencadenadores no la inician8 | Defecto en la definición del desencadenador. Ya se advierte en el momento del registro |
| 116 | La configuración de la tarea se pudo guardar, pero no se pudieron guardar las credenciales de ejecución8 | Especificación de la cuenta de ejecución y la contraseña. Si aparece esto, la tarea no funcionará |
| 100 | Se inició la tarea9 | Si no aparece, la tarea no se disparó en absoluto, o falló al iniciarse con el evento 101 |
| 101 | No se pudo iniciar la tarea. Incluye un valor de error9 | El desencadenador se disparó pero falló el inicio. Sospeche de las credenciales de la cuenta de ejecución (0x8007052E, etc.) o de los permisos. El evento 100 no aparece |
| 129 | Se inició la tarea con un ID de proceso9 | El proceso ya se generó. De aquí en adelante es cosa del script |
| 200 / 201 | Se inició la acción (action) / la acción finalizó9 | El 201 significa que «el programa iniciado terminó», no indica si el resultado fue correcto. En las versiones actuales de Windows, el 201 incluye el valor de retorno (ResultCode) en el cuerpo del mensaje y en los datos del evento, así que hay que mirar ahí (sección 4.3) |
| 202 | El Programador de tareas no pudo completar la acción. Incluye un valor de error9 | Fallo del propio Programador de tareas. No necesariamente aparece cuando el programa termina con 0x1 |
| 203 | Falló el propio inicio de la acción. Incluye un valor de error9 | Ruta del ejecutable incorrecta, especificación errónea de «Iniciar en (opcional)», permisos insuficientes |
| 102 | La tarea finalizó correctamente9 | Fin del flujo normal |
| 111 | Se superó el límite de tiempo de ejecución y se finalizó la tarea9 | Se alcanzó el «tiempo hasta detener» (predeterminado: 3 días). Ir al capítulo 7 |
| 322 | No se inició porque otra instancia de la misma tarea ya estaba en ejecución10 | Está actuando el control de ejecuciones múltiples. La ejecución anterior no había terminado. Ir al capítulo 7 |
| 323 | Se detuvo la instancia en ejecución para iniciar una nueva instancia9 | MultipleInstances está configurado como «detener la instancia existente» |
| 327 | Se detuvo la instancia porque la alimentación pasó a batería9 | Condición de alimentación (sección 4.4). Frecuente en portátiles y equipos de campo |
| 328 | Se detuvo la instancia porque el equipo dejó de estar inactivo9 | La condición de inactividad está habilitada |
| 329 | Se detuvo la instancia porque la tarea agotó el tiempo de espera9 | Igual que el 111, revise el diseño del tiempo de ejecución |
| 330 | Se detuvo la instancia a petición del usuario9 | Alguien la detuvo manualmente |
Los puntos clave para interpretar esto son tres.
- Si falta el 100, primero compruebe si aparece el 101 (no se pudo iniciar la tarea). Si aparece, el desencadenador se disparó pero el inicio falló, así que sospeche del valor de error y de la cuenta de ejecución (capítulo 3). Si tampoco aparece el 101, la causa está antes de la propia tarea (desencadenador, condiciones, tarea deshabilitada), y si aparece el 322, la razón es directamente que la instancia anterior no había terminado.
- Si hay 100 pero falta el 102, la tarea se inició pero no ha terminado. Si aparece 111 / 329, se agotó el tiempo; si aparece 203, falló el propio inicio; y si aparecen 327 / 328, la instancia en ejecución se detuvo por la condición de alimentación o de inactividad (tanto 327 como 328 son registros de «se detuvo», no de «no se inició», así que aparecen después del 100).
- Si hay tanto 100 como 102 pero el resultado es raro, el ámbito de responsabilidad del Programador de tareas se completó por entero. A partir de ahí solo se puede seguir el rastro con los logs del propio script (capítulo 6).
flowchart TD
S["No se ejecuta o el resultado es raro"] --> Q1{"¿Hay evento 100 (inicio)?"}
Q1 -->|"No"| Q1b{"¿Hay evento 101?"}
Q1b -->|"Sí"| A0["El desencadenador se disparó pero<br/>falló el inicio: revise el valor de error<br/>del 101 y la cuenta de ejecución (cap. 3)"]
Q1b -->|"No"| A1["La causa está antes de la tarea:<br/>desencadenador, condiciones,<br/>deshabilitación (sección 4.4)<br/>Si hay 322, la anterior no había terminado"]
Q1 -->|"Sí"| Q2{"¿Hay evento 102 (fin normal)?"}
Q2 -->|"No"| B["Se inició pero no ha terminado<br/>203 = falló el propio inicio<br/>111 / 329 = se agotó el tiempo<br/>327 / 328 = detenida por alimentación/inactividad<br/>202 = fallo del Programador de tareas"]
Q2 -->|"Sí"| C["El Programador de tareas<br/>completó su parte. Si es 0x1, el script<br/>devolvió el código de salida 1<br/>De aquí en más, siga los logs del script (cap. 6)"]
Figura 1: según haya o no los eventos 100 y 102, el lugar donde investigar se divide entre la configuración de la tarea o el propio script
Aquí hay un punto en el que es fácil equivocarse. Aunque el programa termine con un valor distinto de cero, como 0x1, desde el punto de vista del Programador de tareas «se inició y terminó», por lo que aparece el 201 (ACTION_SUCCESS). El 202 es un evento que se produce cuando «el Programador de tareas no pudo completar la acción», y no es el cajón de sastre para las terminaciones no nulas del programa.9 Por eso, al investigar un 0x1, buscar el 202 a veces no lleva a ningún sitio. Lo que hay que mirar es el valor de retorno del 201 y la columna «Resultado de la última ejecución» de la tarea (sección 4.3). Además, el ResultCode del 201 y el «Resultado de la última ejecución» no siempre coinciden, así que lo más fiable es que el propio script registre su código de salida, como se explica en el capítulo 6.
4.3 Cómo leer «Resultado de la última ejecución»
| Valor mostrado | Significado |
|---|---|
0x0 |
Finalización correcta (el programa iniciado devolvió el código de salida 0) |
0x1 |
El programa iniciado devolvió el código de salida 1 (no es un error del propio Programador de tareas) |
0x41300 |
En espera de la próxima ejecución programada (SCHED_S_TASK_READY) |
0x41301 |
En ejecución en este momento (SCHED_S_TASK_RUNNING) |
0x41303 |
Todavía no se ha ejecutado ni una sola vez (SCHED_S_TASK_HAS_NOT_RUN) |
0x8007010B |
Especificación incorrecta de la carpeta de inicio («Iniciar en (opcional)»). Síntoma típico de haber puesto comillas |
0x8007052E |
Fallo de inicio de sesión. La contraseña guardada es antigua, falta el derecho necesario, etc. |
Los de la serie 0x413xx son códigos de estado del Programador de tareas, los 0x8007xxxx son códigos de error de Windows, y valores pequeños como 0x1 o 0x2 son el propio código de salida del programa iniciado.3 Si sabe distinguir esto, no se equivocará de entrada de lugar (configuración de la tarea o script) al investigar.
flowchart TB
R["Valor mostrado en «Resultado de la última ejecución»"]
R -->|"Valor pequeño como 0x0 / 0x1 / 0x2"| P["Código de salida del propio<br/>programa iniciado<br/>→ Investigar en el script"]
R -->|"0x413xx"| ST["Código de estado del<br/>Programador de tareas<br/>(en espera, en ejecución, sin ejecutar)<br/>→ No indica un fallo en absoluto"]
R -->|"0x8007xxxx"| Q{"¿Se inició la acción?<br/>(¿hay 201? ¿no hay 203?)"}
Q -->|"No se inició"| W["Código de error de Windows<br/>(carpeta de inicio mal especificada,<br/>fallo de inicio de sesión, etc.)<br/>→ Investigar en la configuración de la tarea"]
Q -->|"Sí se inició"| W2["Código de salida devuelto por el<br/>proceso hijo. La app también puede<br/>devolverlo en formato HRESULT<br/>→ Investigar en el script"]
Figura 2: en la misma columna aparecen mezclados tres tipos de valores distintos. Sin embargo, 0x8007xxxx no se puede determinar solo por el prefijo — si la acción llegó a iniciarse, el valor corresponde al que devolvió el proceso hijo, así que hay que cruzar los eventos 201 y 203 para determinar su origen
4.4 Cuidado con los valores predeterminados de Condiciones y Configuración
- Condición de alimentación: de forma predeterminada está activo «Iniciar la tarea solo si el equipo está conectado a la corriente alterna». Si usa un portátil como máquina de validación, se produce un «fallo que no se reproduce», porque solo deja de funcionar con alimentación por batería. Además, a partir de Windows 10, mientras está activo el ahorro de batería se retrasa el desencadenador de muchas tareas.11
- Si se pasa la hora de inicio: si el equipo estaba apagado y la hora de inicio ya pasó, de forma predeterminada la tarea no se ejecuta hasta la siguiente programación. Habilite explícitamente «Ejecutar la tarea tan pronto como sea posible después de una hora de inicio programada» (
-StartWhenAvailable), o decida como parte del diseño si es un trabajo que puede permitirse perder una ejecución.5 - Reactivación desde suspensión: si es un trabajo nocturno en un equipo que se pone en suspensión, decida también si necesita «Activar el equipo para ejecutar esta tarea» (
-WakeToRun).
A continuación resumimos, por pestaña, dónde se encuentran en la GUI los ajustes mencionados hasta ahora. Es la estructura del cuadro de diálogo que se abre con clic derecho sobre la tarea → «Propiedades».
| Pestaña | Qué se decide aquí | Apartado correspondiente en este artículo |
|---|---|---|
| General | Nombre de la tarea, cuenta de ejecución («Cambiar usuario o grupo»), «Ejecutar solo si el usuario inició sesión» / «Ejecutar tanto si el usuario inició sesión como si no», «No almacenar la contraseña», «Ejecutar con los privilegios más altos» | Capítulo 3 |
| Desencadenadores | Cuándo se inicia. Desde «Nuevo» se añaden desencadenadores de hora, de inicio de sesión, de evento, etc. | Sección 4.5 |
| Acciones | Los tres campos «Programa o script», «Agregar argumentos (opcional)» e «Iniciar en (opcional)». Aquí es donde no hay que poner comillas en «Iniciar en (opcional)» | Capítulo 5 |
| Condiciones | Condición de alimentación (solo con corriente alterna / detener al pasar a batería), condición de inactividad, condición de red | Lista anterior |
| Configuración | «Ejecutar la tarea tan pronto como sea posible después de una hora de inicio programada», «Regla que se aplica si la tarea ya se está ejecutando», «Tiempo de espera antes de detener la tarea» | Lista anterior y capítulo 7 |
| Historial | Lista de eventos de esa tarea. Deshabilitada de forma predeterminada; permanece vacía hasta que se habilita con el procedimiento de la sección 4.1 | Secciones 4.1 y 4.2 |
4.5 Precauciones sobre el propio diseño del desencadenador
A veces, lo que parece «no se ejecuta» resulta ser, en realidad, que el diseño del desencadenador estaba desviado de la intención original.
- «El día 31 de cada mes» no se ejecuta en los meses que no tienen día 31. Si se trata de un proceso de cierre de mes, es más seguro orientar el diseño hacia la intención de «el último día de cada mes» (procesar el mes anterior a principios de mes, o hacer que el propio script determine la fecha).
- La hora es la hora local del equipo donde se registró la tarea. Si distribuye el mismo XML a equipos de sedes en el extranjero, o a algún servidor que raramente está operado con la configuración en UTC, la hora de ejecución se desajustará según la sede. Decida como parte de la especificación si quiere «las 6 de la mañana hora de Japón en todas las sedes» o «las 6 de la mañana hora local de cada sede».
- Tenga cuidado si empieza a usar el Programador de tareas para repeticiones a intervalos cortos (cada 5 minutos, por ejemplo). Es una herramienta excelente para «un lote una vez al día», pero en cuanto necesite sondeo (polling) por minutos o supervisión continua, eso ya pertenece al terreno de un proceso residente (capítulo 8, más adelante).
- Los desencadenadores por evento son potentes, pero primero hay que confirmar que el evento objetivo realmente se registra de forma estable. Una configuración que usa como desencadenador un ID de evento concreto del registro de aplicación deja de funcionar silenciosamente si una actualización de la aplicación cambia la forma en que se genera el evento. Con frecuencia resulta más fácil de seguir combinar un desencadenador de hora con una comprobación de condiciones dentro del propio script.
5. Patrones típicos que terminan en 0x1 y la forma correcta de invocar PowerShell
0x1 no es más que el resultado de que el script falló, así que la causa está en las diferencias del entorno de ejecución del script. Lo que principalmente cambia entre la ejecución manual y la ejecución programada es lo siguiente.
- El directorio de trabajo actual es distinto: si no se especifica «Iniciar en (opcional)», la tarea se ejecuta en algo como
C:\Windows\System32. Ahí es donde se rompen los scripts escritos con rutas relativas. En el script, construya las rutas basándose en$PSScriptRoot, y en la tarea especifique la carpeta de trabajo en «Iniciar en (opcional)». En este campo, no hay que poner comillas. Escríbalo sin comillas incluso si la ruta contiene espacios (si las pone, falla con0x8007010B). - Las variables de entorno y el perfil son distintos: dé por hecho que las variables de entorno definidas por los scripts de inicio de sesión o por el perfil de usuario, así como las unidades de red asignadas (por ejemplo, X:), no existen en una sesión no interactiva. Use directamente rutas UNC (
\\servidor\recurso\...) y elimine la diferencia de perfil con-NoProfile. - La directiva de ejecución es distinta: aunque tenga configurado
RemoteSignedpara el usuario, es posible que no esté configurada para la cuenta de servicio. Indique explícitamente-ExecutionPolicy Bypassen los argumentos de la tarea. - La convención de código de salida de la herramienta es peculiar: por ejemplo,
robocopydevuelve 1 cuando «copió todos los archivos correctamente». Si un envoltorio (wrapper) devuelve el código de salida tal cual, puede parecer0x1cuando en realidad todo fue bien, o viceversa. Compruebe siempre la convención de código de salida del comando externo que utilice.
En el caso de robocopy, según la tabla oficial de códigos de salida, los valores de 0 a 7 son combinaciones «sin fallos», mientras que 8 o más indica que hubo al menos un fallo durante el proceso de copia.12 Es decir, lo correcto no es devolver el valor tal cual, sino interponer un envoltorio que lo normalice a 0/1.
# Reciba los argumentos usted mismo. No dependa implícitamente de variables
# del lado de quien lo llama
param(
[Parameter(Mandatory)][string]$Source,
[Parameter(Mandatory)][string]$Destination
)
# robocopy devuelve el código de salida en $LASTEXITCODE.
# Aunque tenga configurado $ErrorActionPreference = 'Stop', una terminación
# no nula de un comando nativo no se convierte en excepción, así que hay
# que comprobarla usted mismo
robocopy $Source $Destination /E /R:2 /W:5 /NP
$rc = $LASTEXITCODE
if ($rc -ge 8) {
Write-Error "robocopy ha fallado. Código de salida: $rc"
exit 1
}
# De 0 a 7 significa que no hubo fallos. Deje constancia en el log de lo
# ocurrido, pero devuelva éxito al Programador de tareas
Write-Host "robocopy finalizó correctamente. Código de salida: $rc"
exit 0
La línea -ge 8 es el núcleo de todo esto. Si se escribe if ($rc -ne 0), incluso el caso en que la copia se realizó correctamente (1) se trataría como un fallo. Esta es la verdadera identidad de la consulta clásica de «cada mañana llega una notificación de que el backup falló, pero en realidad los archivos sí se copiaron».
La forma básica de invocar PowerShell es la siguiente.
Programa o script: pwsh.exe (powershell.exe si usa Windows PowerShell)
Agregar argumentos: -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Jobs\Cleanup-Logs.ps1"
Iniciar en (opcional): C:\Jobs (sin comillas)
Se usa -File en lugar de -Command porque, además de que el escape de argumentos resulta más directo, el exit n del script se convierte tal cual en el código de salida del proceso, lo que permite determinar el éxito o el fracaso desde «Resultado de la última ejecución» del Programador de tareas. En el propio script, diseñe también un retorno explícito: 0 si tiene éxito, distinto de 0 si falla.
# Se establece en Stop para que incluso los "errores no terminales" de los
# cmdlets caigan en el catch. Sin esto, un fallo de Copy-Item, por ejemplo,
# podría pasar de largo y terminar en exit 0
$ErrorActionPreference = 'Stop'
try {
Main
exit 0
}
catch {
Write-Error $_
exit 1
}
La forma de pensar sobre dónde capturar las excepciones y cómo registrarlas se explica en detalle en otro artículo, «Captura de excepciones y diseño de logs».
6. Registre sus propios logs
El historial del Programador de tareas solo le dice «si se inició y cuál fue el código de salida». Qué hizo el script y hasta dónde llegó debe registrarlo el propio script como log.
Como mínimo, en lugar de redirigir mediante los argumentos de la tarea (la notación de redirección en el campo «Argumentos» del Programador de tareas no funciona porque no pasa por un shell), lo más práctico es capturar una transcripción dentro del propio script.
$logDir = 'C:\Jobs\logs'
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
Start-Transcript -Path (Join-Path $logDir ("cleanup-{0:yyyyMMdd}.log" -f (Get-Date))) -Append
try {
# Proceso principal
}
finally {
Stop-Transcript
}
Para el problema de que los propios logs se sigan acumulando (gestión de generaciones, archivado), se puede aplicar tal cual lo que escribimos en «PowerShell aplicado — automatizar de forma segura la investigación, el archivado y la generación de informes de logs».
Para dar un paso más, considere también escribir en el registro de eventos de Windows. A diferencia de un log en archivo, la ventaja es que el resultado (éxito o fallo) llega a un lugar que el equipo de operaciones ya está mirando (el Visor de eventos, las herramientas de supervisión existentes).
# --- Ejecutar una sola vez, en la configuración inicial, con permisos de administrador ---
if (-not [System.Diagnostics.EventLog]::SourceExists('KS-Jobs')) {
[System.Diagnostics.EventLog]::CreateEventSource('KS-Jobs', 'Application')
}
# --- El trabajo (cuenta de mínimo privilegio) solo escribe; SourceExists puede
# requerir permisos de administrador, así que no se llama en tiempo de ejecución ---
catch {
$err = $_
try {
[System.Diagnostics.EventLog]::WriteEntry('KS-Jobs',
"Cleanup-Logs failed: $($err.Exception.Message)",
[System.Diagnostics.EventLogEntryType]::Error, 1001)
}
catch {
# No ocultar el fallo original por no poder escribir en el log
Write-Warning "Fallo al escribir en el registro de eventos: $_"
}
exit 1
}
Dos aclaraciones. Primero, el registro del origen del evento (CreateEventSource) requiere permisos de administrador. Si mezcla ese registro dentro del propio trabajo, en el primer fallo en producción, corriendo con la cuenta de servicio de mínimo privilegio, se produce un doble fallo: «intenta registrar → lanza una excepción → no se puede escribir el evento que realmente importa». Separe el registro hacia la fase de configuración inicial, como arriba, y deje que en tiempo de ejecución solo se escriba. Segundo, si usa pwsh.exe como motor de ejecución, como en el ejemplo de registro de tarea de este artículo, no podrá usar los cmdlets New-EventLog / Write-EventLog propios de la era de Windows PowerShell 5.1 (el comando no se encuentra y falla todo el script). Llamando directamente a las clases de .NET, como arriba, funciona tanto en 5.1 como en 7.
Además, incorporar un mecanismo de «si falla, que llegue a una persona» —una notificación por correo o por Teams/Slack— evita el incidente de «darse cuenta en un inventario de que llevaba meses detenida». No hace falta una infraestructura de notificaciones elaborada: unas pocas líneas que hagan un POST a un webhook solo en caso de fallo ya funcionan perfectamente. Por el contrario, «notificar cada vez que tiene éxito» deja de leerse tarde o temprano, así que es recomendable limitar el éxito a un resumen semanal y centrar la detección en los fallos y en «no se ejecutó» (la última hora de ejecución es antigua). Los criterios de qué escribir en el log también los organizamos en «Captura de excepciones y diseño de logs».
7. Control de ejecuciones múltiples y de ejecuciones prolongadas
¿Qué ocurre si llega la siguiente hora programada mientras la ejecución anterior se está alargando? Esto se decide en «Regla que se aplica si la tarea ya se está ejecutando», en la pestaña Configuración, y en PowerShell corresponde a -MultipleInstances.5
| Valor de configuración | Comportamiento | Uso recomendado |
|---|---|---|
| IgnoreNew (predeterminado en la GUI: No iniciar una nueva instancia) | Si ya está en ejecución, omite el nuevo inicio | Lotes periódicos idempotentes en general. Es la primera opción |
| Queue | Si ya está en ejecución, la ejecuta en orden al terminar la anterior | Procesos de agregación en los que no se puede permitir perder ninguna ejecución |
| Parallel | Se inicia en paralelo | Evítelo en principio. Solo cuando se pueda garantizar la seguridad en paralelo |
Además, configurar «Tiempo de espera antes de detener la tarea» (-ExecutionTimeLimit, predeterminado: 3 días) en un valor realista (aproximadamente 2 o 3 veces el tiempo de ejecución previsto) evita el incidente de que un proceso colgado arrastre consigo el trabajo del día siguiente.5
Conviene tener presente que IgnoreNew o Queue solo protegen dentro de la misma definición de tarea. No evitan un conflicto cuando otra tarea distinta llama al mismo script, o cuando una persona lo ejecuta manualmente al atender una incidencia. Si existen varias vías que tocan el mismo recurso (archivo, base de datos, sistema externo), añada también exclusión mutua en el propio script. La técnica habitual es un mutex con nombre.
$mutex = New-Object System.Threading.Mutex($false, 'Global\KS-Cleanup-Logs')
$acquired = $false
try {
try {
$acquired = $mutex.WaitOne(0)
}
catch [System.Threading.AbandonedMutexException] {
# El trabajo anterior terminó forzosamente sin liberar el mutex
# (se superó el "tiempo hasta detener" de la tarea, se mató el
# proceso, se cortó la alimentación, etc.). En este caso WaitOne
# no devuelve false, sino que lanza una excepción, y la propiedad
# del mutex pasa a este proceso. Si cae sin capturarla, todas las
# ejecuciones siguientes se detendrán aquí mismo cada vez, y el
# trabajo dejará de funcionar para siempre
$acquired = $true
Write-Warning 'La ejecución anterior terminó de forma anómala. Revise si quedó trabajo interrumpido por limpiar.'
# Aquí se comprueba si quedaron archivos escritos a medias o
# registros incompletos, antes de continuar con el proceso principal
}
if (-not $acquired) {
Write-Warning 'Termina porque hay otra instancia en ejecución.'
exit 0 # Use 0 si no quiere tratar "no se ejecutó" como fallo, o distinto de 0 si sí
}
# Proceso principal
}
finally {
if ($acquired) { $mutex.ReleaseMutex() }
$mutex.Dispose()
}
AbandonedMutexException es una excepción que informa de que «el propietario anterior desapareció sin liberar el mutex», y en el momento en que se lanza, la propiedad ya ha pasado a este proceso. Por eso, si aquí se hiciera exit, el mutex quedaría sin liberar y la próxima vez volvería a producirse la misma excepción, dejando el trabajo inoperativo para siempre. El tratamiento correcto es capturarla, comprobar el estado del trabajo interrumpido y continuar.
Añadir Global\ al principio del nombre hace que la exclusión funcione también entre sesiones distintas (por ejemplo, entre la tarea de otro usuario y una ejecución manual). Decida, según la naturaleza del trabajo, si conviene esperar a obtener el bloqueo (pasando un tiempo de espera a WaitOne) o desistir de inmediato. Tenga en cuenta que un objeto con nombre bajo Global\ es visible para cualquiera en la máquina. En un servidor compartido con varios usuarios que inician sesión, si por error o mala intención alguien se adelanta a tomar un mutex con el mismo nombre, el trabajo quedará omitido para siempre (y además, si usa exit 0, parecerá que todo va bien). En esos entornos, configure una ACL (MutexSecurity) en el mutex para restringir qué cuentas pueden obtenerlo, o como mínimo registre en la notificación y en el registro de eventos de la sección anterior el hecho de «se omitió por no poder obtenerlo», de modo que la supervisión pueda detectar omisiones consecutivas. El control de exclusión mutua en integraciones basadas en archivos se trata en detalle en «Buenas prácticas de integración de archivos y bloqueo».
8. Cuándo dejar de usar el Programador de tareas — la frontera con los servicios residentes
El Programador de tareas no es una herramienta universal. Existe una frontera a partir de la cual, cuando los requisitos crecen, conviene cambiar de mecanismo en lugar de forzar su uso.
| Requisito | Mecanismo adecuado |
|---|---|
| Lotes programados de hasta unas pocas veces al día | Programador de tareas |
| El disparador combina personas, eventos y horas, y se quiere visualizar todo el flujo | Power Automate (otro artículo) |
| Sondeo (polling) por minutos, supervisión continua, procesamiento de colas | Servicio de Windows / proceso residente |
| Se quiere mantener estado entre ejecuciones, o controlar con detalle los reintentos y el backoff | Servicio de Windows / proceso residente |
Si empieza a hacer sondeo con «una tarea cada 5 minutos», cada inicio conlleva el coste de generar el proceso y cargar los módulos, y además necesita un mecanismo para guardar el estado anterior en un archivo o similar, lo que en la práctica equivale a reimplementar en pedazos un proceso residente. Cuando llegue a este punto, lo natural es hacerlo residente con el Generic Host de .NET y BackgroundService. El patrón de implementación se explica en «Uso de Generic Host y BackgroundService en una aplicación de escritorio».
A la inversa, convertir en servicio un lote mensual o diario y gestionar el temporizador por cuenta propia también es excesivo. No se equivocará mucho con esta línea divisoria: si «el intervalo de ejecución es de una hora o más, el procesamiento es independiente y no mantiene estado», use el Programador de tareas; en cuanto empiece a salirse de eso, plantéese hacerlo residente.
9. Lista de verificación antes de poner la tarea en producción
Antes de registrar la tarea, se recomienda revisar una vez cada uno de los siguientes puntos.
- ¿Ha decidido la cuenta de ejecución? (¿No eligió SYSTEM por inercia? Si es un dominio, ¿evaluó una gMSA?)
- ¿Entiende las limitaciones del tipo de inicio de sesión? (Con S4U no hay acceso a la red. Con Password, ¿decidió cómo operar cuando cambie la contraseña?)
- ¿Probó el script de forma aislada en condiciones equivalentes a la cuenta de ejecución?
- ¿Lo está invocando con la forma
-NoProfile -NonInteractive -ExecutionPolicy Bypass -File? - ¿Las rutas dentro del script se basan en
$PSScriptRoot/ UNC? (¿No dependen de unidades asignadas ni de rutas relativas?) - ¿No ha puesto comillas en «Iniciar en (opcional)»?
- ¿Ha diseñado el código de salida? (0 en éxito, distinto de 0 en fallo. ¿Comprobó la convención de código de salida de los comandos externos?)
- ¿Habilitó el historial de tareas? ¿Existen el log propio del script y una notificación de fallo?
- ¿Configuró de forma explícita la condición de alimentación, StartWhenAvailable, las ejecuciones múltiples y el límite de tiempo de ejecución?
- ¿Guardó la definición de la tarea en el repositorio, como XML o como script de PowerShell?
10. Resumen
El Programador de tareas no consiste simplemente en «escribir el script y ya está»: solo llega a una operación estable una vez que se diseñan sus tres premisas — cuenta de ejecución, sesión y valores predeterminados. Dicho de otro modo, si al registrar la tarea se cubren de una vez los puntos señalados en este artículo — elección del tipo de inicio de sesión, activación del historial, diseño del código de salida y del log, y la especificación explícita de las ejecuciones múltiples y del límite de tiempo —, después apenas requerirá atención.
«Funciona en manual pero no en ejecución programada» tiene, casi con toda seguridad, la diferencia de sesión y de entorno como causa. Antes de tocar la configuración a ciegas, revise en la pestaña Historial hasta dónde llegó la ejecución y pruebe, de arriba a abajo, el procedimiento de aislamiento de este artículo.
Artículos relacionados
- PowerShell aplicado — automatizar de forma segura la investigación, el archivado y la generación de informes de logs
- Preparación de pruebas de PowerShell con Pester — el patrón práctico para que los scripts de operación sean más difíciles de romper
- Automatizar procesos con Power Automate — cuándo usar flujos en la nube o de escritorio y cómo diseñar el manejo de errores
- Cuándo hace falta realmente el permiso de administrador en una aplicación de Windows
Áreas de consultoría relacionadas
En KomuraSoft LLC atendemos consultas sobre la revisión del diseño de automatización de procesos con PowerShell y el Programador de tareas, así como la reconstrucción de trabajos programados que «funcionan, pero que ya nadie sabe arreglar».
- Consultoría técnica y revisión de diseño
- Desarrollo de aplicaciones para Windows
- Investigación de fallos
- Contacto
Referencias
-
Microsoft Learn, logonType Simple Type (Task Scheduler). Definición del tipo de inicio de sesión. Sobre cómo, con S4U, a cambio de no almacenar la contraseña, no se puede acceder a la red ni a archivos cifrados. ↩ ↩2 ↩3
-
Microsoft Learn, Troubleshoot issues with scheduled tasks not running. Sobre el procedimiento de aislamiento — prueba aislada del script → comprobación de estado e historial → cambio de las opciones de seguridad — y la ubicación del registro de eventos Operational de TaskScheduler. ↩ ↩2 ↩3
-
Microsoft Learn, Task Scheduler error and success constants. Sobre las definiciones de códigos de estado y de error como SCHED_S_TASK_READY (0x41300), SCHED_S_TASK_RUNNING (0x41301) y SCHED_S_TASK_HAS_NOT_RUN (0x41303). ↩ ↩2
-
Microsoft Learn, Manage group Managed Service Accounts. Sobre que el controlador de dominio administra la contraseña de la gMSA y el host la obtiene, que las tareas del Programador de tareas admiten gMSA, que el nivel funcional de dominio y bosque debe ser Windows Server 2012 o posterior, que se necesita una clave raíz de KDS (verificable con el ID de evento 4004 del registro Operational de KdsSvc), que el nombre de la gMSA debe ser único a nivel de bosque, y sobre
-PrincipalsAllowedToRetrieveManagedPassworddeNew-ADServiceAccountyInstall-ADServiceAccount/Test-ADServiceAccounten cada host. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, New-ScheduledTaskSettingsSet. Sobre los parámetros del objeto de configuración de la tarea, como MultipleInstances (Parallel / Queue / IgnoreNew), StartWhenAvailable y ExecutionTimeLimit (predeterminado: 3 días). ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Security Contexts for Tasks. Sobre el contexto de seguridad de las tareas y la necesidad del derecho «Iniciar sesión como trabajo por lotes» para ejecutar tareas registradas con Password o S4U. ↩ ↩2
-
Microsoft Learn, New-ScheduledTaskPrincipal. Sobre cómo
-UserIdindica la cuenta de ejecución,-LogonTypeindica el método de inicio de sesión (None/Password/S4U/Interactive/Group/ServiceAccount/InteractiveOrPassword), y-RunLeveladmiteLimitedyHighest. ↩ -
Microsoft Learn (archivo), General Task Registration. Sobre las definiciones de mensaje de los eventos de Microsoft-Windows-TaskScheduler 106 (registro de la tarea), 113 (registrada pero algunos desencadenadores no la inician), 116 (configuración guardada pero no se pudieron guardar las credenciales), 140 (actualización) y 141 (eliminación). ↩ ↩2 ↩3 ↩4
-
Microsoft Learn (archivo), Task Monitoring and Control. Sobre las definiciones de mensaje de los eventos de Microsoft-Windows-TaskScheduler 100 (inicio de la tarea), 102 (finalización correcta), 111 (finalización por superar el tiempo de ejecución), 129 (inicio con ID de proceso), 200/201 (inicio/fin de la acción), 202/203 (fallo al completar la acción/fallo al iniciarla) y 323 (detención para iniciar una nueva instancia). En particular, el 201 tiene el nombre simbólico
ACTION_SUCCESSy el mensaje «Task Scheduler successfully completed task … and action …», mientras que el 202 dice «Task Scheduler failed to complete the … instance of the … task with action … The error value is: …», e indica que el propio Programador de tareas no pudo completar la acción. El 201 que emiten las versiones actuales de Windows es la versión 2, cuyo cuerpo dice «… with return code N» e incluyeResultCodeen los datos del evento (incluyendo que este valor puede no coincidir con el «Resultado de la última ejecución» de la tarea); también trata 327 (detención por cambio a batería), 328 (detención por dejar de estar inactivo), 329 (tiempo de espera agotado) y 330 (detención a petición del usuario). ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 -
Microsoft Learn (archivo), Event ID 322 — Task Properties. Sobre que el evento 322 (nombre simbólico
NEW_INSTANCE_IGNORED) indica que «no se inició porque ya había otra instancia de la misma tarea en ejecución», y sobre el procedimiento para revisar las condiciones y la configuración. ↩ -
Microsoft Learn, What’s New in Task Scheduler. Sobre cómo, a partir de Windows 10, mientras está activo el ahorro de batería se retrasa el desencadenador de las tareas no interactivas. ↩
-
Microsoft Learn, robocopy. Sobre la tabla de códigos de salida (0: nada que copiar; 1: todos los archivos copiados correctamente; 2 en adelante: combinaciones de archivos adicionales y discrepancias) y sobre que 8 o más indica que hubo al menos un fallo durante el proceso de copia. ↩
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Power Automate y PowerShell + Programador de tareas: cuándo usar cada uno ── conectar las herramientas de automatización sin mezclarlas, cada una en su lugar
Para TI de pymes con PowerShell y Power Automate a la vez: diferencias, tabla de decisión, integración vía SharePoint y aspectos de licen...
Cómo llamar correctamente a un exe externo desde PowerShell — la trampa de las comillas en argumentos, los códigos de salida y la codificación de caracteres
Al llamar a robocopy o a un EXE interno desde PowerShell, los argumentos se corrompen, no se obtiene el código de salida o la salida se v...
Manejo de errores y diseño de reintentos en PowerShell ── de la trampa donde try/catch no funciona a las prácticas recomendadas de exit code y reintentos
Analiza la diferencia entre errores terminantes y no terminantes en PowerShell, la trampa de try/catch, -ErrorAction Stop, $LASTEXITCODE,...
Usar WMI/CIM desde C# y PowerShell ── Guía práctica de obtención de información de hardware, monitorización de procesos y consultas remotas
WMI/CIM es la solución estándar para leer el número de serie, monitorizar el disco y detectar procesos. Cmdlets CIM, migración desde Get-...
Guía práctica de la directiva de grupo (GPO) — funcionamiento, verificación de la aplicación y cuándo usar Intune
¿Usa GPO para distribuir configuraciones sin saber exactamente qué significa? Explicamos el funcionamiento de la directiva de grupo, el o...
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.
- ¿Qué significa el 0x1 que aparece en «Resultado de la última ejecución» del Programador de tareas?
- 0x1 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á del lado del script. Las diferencias típicas entre la ejecución manual y la ejecución programada son el directorio de trabajo actual, las variables de entorno y el perfil, y la directiva de ejecución. También hay que prestar atención a las convenciones de código de salida de comandos externos como robocopy, que devuelve 1 incluso cuando todo ha ido bien. Si sabe distinguir entre los códigos de estado del Programador de tareas de la serie 0x413xx y los códigos de error de Windows con prefijo 0x8007xxxx, no se equivocará de lugar al investigar.
- ¿Por qué funciona en ejecución manual pero no en el Programador de tareas?
- Casi con toda seguridad la causa es la diferencia de cuenta de ejecución y de sesión/entorno. Si selecciona «Ejecutar tanto si el usuario inició sesión como si no», la tarea se ejecuta en una sesión no interactiva, por lo que las unidades de red asignadas y las variables de entorno del perfil de usuario no existen. Además, con «No almacenar la contraseña» (S4U) no se puede acceder a recursos de red. Para aislar el problema resultan eficaces estos pasos: revisar la pestaña Historial, probar el script de forma aislada y cambiar temporalmente a «Ejecutar solo si el usuario inició sesión». El historial de tareas está deshabilitado de forma predeterminada, así que actívelo siempre antes de poner la tarea en producción.
- ¿Cuál es la forma correcta de invocar un script de PowerShell desde el Programador de tareas?
- La base es indicar pwsh.exe (o powershell.exe) como programa y usar argumentos con la forma -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "ruta completa". Usar -File en lugar de -Command hace que el exit n del script se convierta directamente en el código de salida del proceso, de modo que se puede determinar el éxito o el fracaso. Las rutas dentro del script deben basarse en $PSScriptRoot, y el campo «Iniciar en (opcional)» no debe llevar comillas (si las lleva, falla con 0x8007010B). En el script conviene diseñar el retorno explícito: 0 si tiene éxito, distinto de 0 si falla.
- ¿Cómo se evita que un cambio de contraseña detenga la tarea?
- Al registrar la tarea con «Ejecutar tanto si el usuario inició sesión como si no», la contraseña queda almacenada, de modo que tras cambiarla la tarea queda detenida de forma continua por fallo de inicio de sesión (0x8007052E). En un entorno de dominio, la primera opción es una gMSA (cuenta de servicio administrada de grupo), cuya contraseña administra automáticamente el controlador de dominio, con lo que este problema desaparece por completo. Si usa una cuenta de servicio dedicada, gestione conjuntamente la caducidad de la contraseña y el inventario de tareas. Además, incorporar una notificación por correo o Teams en caso de fallo evita el incidente de no darse cuenta de que la tarea llevaba meses detenida.
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.