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: · · 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 0x1 que 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.

  1. Abra el Programador de tareas (taskschd.msc, o busque «Programador de tareas» en el menú Inicio). Ábralo con permisos de administrador.
  2. 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.
  3. 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

  1. 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 runas o en una máquina de validación, si es posible).
  2. 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).
  3. 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).
NoNoNoNo se ejecuta o el resultado es raro¿Hay evento 100 (inicio)?¿Hay evento 101?El desencadenador se disparó perofalló el inicio: revise el valor de errordel 101 y la cuenta de ejecución (cap. 3)La causa está antes de la tarea:desencadenador, condiciones,deshabilitación (sección 4.4)Si hay 322, la anterior no había terminado¿Hay evento 102 (fin normal)?Se inició pero no ha terminado203 = falló el propio inicio111 / 329 = se agotó el tiempo327 / 328 = detenida por alimentación/inactividad202 = fallo del Programador de tareasEl Programador de tareascompletó su parte. Si es 0x1, el scriptdevolvió el código de salida 1De 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.

Valor pequeño como 0x0 / 0x1 / 0x20x413xx0x8007xxxxNo se inicióSí se inicióValor mostrado en «Resultado de la última ejecución»Código de salida del propioprograma iniciado→ Investigar en el scriptCódigo de estado delProgramador de tareas(en espera, en ejecución, sin ejecutar)→ No indica un fallo en absoluto¿Se inició la acción?(¿hay 201? ¿no hay 203?)Código de error de Windows(carpeta de inicio mal especificada,fallo de inicio de sesión, etc.)→ Investigar en la configuración de la tareaCódigo de salida devuelto por elproceso hijo. La app también puededevolverlo en formato HRESULT→ 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 con 0x8007010B).
  • 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 RemoteSigned para el usuario, es posible que no esté configurada para la cuenta de servicio. Indique explícitamente -ExecutionPolicy Bypass en los argumentos de la tarea.
  • La convención de código de salida de la herramienta es peculiar: por ejemplo, robocopy devuelve 1 cuando «copió todos los archivos correctamente». Si un envoltorio (wrapper) devuelve el código de salida tal cual, puede parecer 0x1 cuando 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

Á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».

Referencias

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

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

  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

  4. 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 -PrincipalsAllowedToRetrieveManagedPassword de New-ADServiceAccount y Install-ADServiceAccount/Test-ADServiceAccount en cada host.  2 3 4

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

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

  7. Microsoft Learn, New-ScheduledTaskPrincipal. Sobre cómo -UserId indica la cuenta de ejecución, -LogonType indica el método de inicio de sesión (None / Password / S4U / Interactive / Group / ServiceAccount / InteractiveOrPassword), y -RunLevel admite Limited y Highest

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

  9. 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_SUCCESS y 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 incluye ResultCode en 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

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

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

  12. 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 recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

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.

Volver al blog