Понадобилось мне сделать так, что бы компьютер с Win11 не засыпал, а выключался полностью и оказалось, что сделать это не так просто. Внимание: выключается не спрашивая ни о чём, можно потерять несохранённые файлы!
Первый шаг - запретить засыпать: идём по пути Control Panel\Hardware and Sound\Power Options\Edit Plan Settings и ставим всё как на картинке:

Открываем планировщик, для этого нажимаем "Старт" и в поиск вставляем taskschd.msc, делаем новую задачу - нажимаем Create task... дальше ставим всё как на картинках:

В Program/script: нужно вставить wscript.exe, в аргументы вот это: "C:\Scripts\RunHidden.vbs", указать путь на C:\Scripts, в этой директории я храню скрипты, если у вас другой адрес - указать его и исправить путь до скрипта.

Что мы сделали: создали задачу, которую планировщик будет запускать каждые пять минут, эта задача будет запускать скрипт на бейзике C:\Scripts\RunHidden.vbs, который лежит и запускается в директории , C:\Scripts и теперь нужно сделать этот самый скрипт, вот его содержимое:
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run "powershell.exe -NoProfile -ExecutionPolicy Bypass -File ""C:\Scripts\AutoShutdown.ps1""", 0, True
Что делает этот бейзик скрипт: он дёргает в фоне!!! скрипт павершелла, C:\Scripts\AutoShutdown.ps1, а его содержимое такое:
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class IdleTime
{
[StructLayout(LayoutKind.Sequential)]
struct LASTINPUTINFO
{
public uint cbSize;
public uint dwTime;
}
[DllImport("user32.dll")]
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
public static uint GetIdleSeconds()
{
LASTINPUTINFO lii = new LASTINPUTINFO();
lii.cbSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf(lii);
GetLastInputInfo(ref lii);
return ((uint)Environment.TickCount - lii.dwTime) / 1000;
}
}
"@
# Время в секундах:
# 300 - 5 минут
# 900 — 15 минут
# 1800 — 30 минут
# 3600 — 1 час
# 7200 — 2 часа
if ([IdleTime]::GetIdleSeconds() -ge 7200)
{
Stop-Computer -Force
}
А вот этот скрипт уже смотрит сколько времени машина бездействует и выключает, если порог времени превышен. Подсказка по времени в комментариях, нужное число вставить. Но нужно учитывать, что обязательно будет ошибка менее пяти минут - скрипт у нас запускается с интервалом пять минут.
На этом месте хочется спросить, а зачем промежуточный скрипт на бейзике, почему не запускать сразу скрипт в повершелле, верно? Дело в том, что если запускать напрямую, то каждые пять минут выскакивает и сразу пропадает окно, на которое улетает фокус мышки, и вообще мне это мелькание не нравится, поэтому я подставил такой костыль, не знаю как сделать правильно.
UPD 22.09.2026 Всё хорошо работает, но машина выключается "внезапно"! Немножко модифицировал скрипт, что бы пищала предупреждая о скором выключении. Сначала пищит три раза 1кГц, через пять минут пищит 1.5кГц три раза, ещё через пять минут пищит последний раз 800Гц и выключается. Период пять минут - минимальный, помните, задание в планировщике исполняется каждые пять минут! Теперь можно спокойно подойти и дёрнуть мышку, если нужно запретить выключение, ну и дополнительно пишет в директорию скрипта пустой файл shutdown который просто для отметки времени выключения.
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class IdleTime
{
[StructLayout(LayoutKind.Sequential)]
struct LASTINPUTINFO
{
public uint cbSize;
public uint dwTime;
}
[DllImport("user32.dll")]
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
public static uint GetIdleSeconds()
{
LASTINPUTINFO lii = new LASTINPUTINFO();
lii.cbSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf(lii);
GetLastInputInfo(ref lii);
return ((uint)Environment.TickCount - lii.dwTime) / 1000;
}
}
"@
# Время в секундах:
# 300 - 5 минут
# 900 - 15 минут
# 1800 - 30 минут
# 3600 - 1 час
# 7200 - 2 часа
# Получаем время бездействия ОДИН раз за запуск, чтобы оно не изменилось по ходу кода
$idle = [IdleTime]::GetIdleSeconds()
# Первое предупреждение (от 60 до 65 минут)
if ($idle -ge 3600 -and $idle -lt 3900)
{
[Console]::Beep(1000, 300); Start-Sleep -Seconds 1
[Console]::Beep(1000, 300); Start-Sleep -Seconds 1
[Console]::Beep(1000, 300)
}
# Второе предупреждение (от 65 до 70 минут)
if ($idle -ge 3901 -and $idle -lt 4200)
{
[Console]::Beep(1500, 300); Start-Sleep -Seconds 1
[Console]::Beep(1500, 300); Start-Sleep -Seconds 1
[Console]::Beep(1500, 300)
}
# Принудительное выключение (70 минут и больше) и запись файла shutdown
if ($idle -ge 4201)
{
[Console]::Beep(800, 2000)
if (Test-Path "shutdown")
{
(Get-Item "shutdown").LastWriteTime = Get-Date
}
else
{
New-Item "shutdown" -ItemType File
}
Stop-Computer -Force
}