The task scheduler is not the answer as the users might be inactive for more than 2 hours.
As Bitcoin Murderous Maniac points out in a comment: While the Task Scheduler GUI (taskschedm.msc
) seemingly limits you to a maximum idle duration period of 2 hours, you're actually free to type in larger values (include the word hours
and submit with Enter):
Unfortunately, however this idle timeout is no longer supported (from the docs):
The Duration and WaitTimeout settings are deprecated. They're still present in the Task Scheduler user interface, and their interface methods may still return valid values, but they're no longer used.
De facto, as of Windows 11 22H2, the behavior of on-idle tasks appears to be limited to the following, based on my experiments (do tell us if you find information to the contrary; the linked docs hopefully still accurately describe the conditions when a computer is considerd (not) idle):
The task is launched instantly when an idle condition is detected (which happens after a hard-coded 4 minutes at the earliest).
When the computer is no longer idle, the launched task is instantly terminated - that is, the check box in the Task Scheduler GUI that seemingly allows the task to continue to run is no longer effective.
When the computer is idle again, the task is invariably restarted - that is, the check box in the Task Scheduler GUI that seemingly allows the task not to restart after having previously been terminated is no longer effective.
However, you can build on these behaviors to achieve what you want:
When the task is launched on entering the idle state, first launch a Start-Sleep
command that waits for the desired duration, e.g. 5 hours, and only then call Stop-Computer
.
If the computer stays idle for that long right away, the shutdown will occur as planned.
If the computer exits the idle state before that, your task is terminated, so that no premature shutdown occurs - and then restarted the next time the idle state is entered.
The following is self-contained code that sets up such a task:
It runs the task as NT AUHTORITY\System
, invisibly, and irrespective of whether a user is logged on or not.
- In order to set up a task with this user identity, you need to run the code in an elevated session (run as admin).
The only way to guarantee that a shutdown is not just initiated but completes, -Force
must be passed to Stop-Computer
. Note, however, that this means that any unsaved data in a user's session may be lost.
No PowerShell script (*.ps1
file) is necessary - the commands are passed to powershell.exe
, the Windows PowerShell CLI, via its -Command
parameter.
See the comments in the source code for additional information.
#requires -RunAsAdministrator
# Specify the number of hours of idle time after which the shutdown should occur.
$idleTimeoutHours = 5
# Specify the task name.
$taskName = 'ShutdownAfterIdling'
# Create the shutdown action.
# Note: Passing -Force to Stop-Computer is the only way to *guarantee* that the
# computer will shut down, but can result in data loss if the user has unsaved data.
$action = New-ScheduledTaskAction -Execute powershell.exe -Argument @"
-NoProfile -Command "Start-Sleep $((New-TimeSpan -Hours $idleTimeoutHours).TotalSeconds); Stop-Computer -Force"
"@
# Specify the user identy for the scheduled task:
# Use NT AUTHORIT\SYSTEM, so that the tasks runs invisibly and
# whether or not users are logged on.
$principal = New-ScheduledTaskPrincipal -UserID 'NT AUTHORITY\SYSTEM' -LogonType ServiceAccount
# Create a settings set that activates the condition to run only when idle.
# Note: This alone is NOT enough - an on-idle *trigger* must be created too.
$settings = New-ScheduledTaskSettingsSet -RunOnlyIfIdle
# New-ScheduledTaskTrigger does NOT support creating on-idle triggers, but you
# can use the relevant CIM class directly, courtesy of this excellent blog post:
# https://www.ctrl.blog/entry/idle-task-scheduler-powershell.html
$trigger = Get-CimClass -ClassName MSFT_TaskIdleTrigger -Namespace Root/Microsoft/Windows/TaskScheduler
# Finally, create and register the task:
Register-ScheduledTask $taskName -Action $action -Principal $principal -Settings $settings -Trigger $trigger -Force