Как в Windows 10 автоматически выполнять задачи на восходе и закате солнца
В Windows 10 есть режим ночного света, меняющий свет экрана с дневного на более теплый и обратно. Это работает вручную, по фиксированному расписанию или на закате и восходе. И я не раз видел желающих применить именно последний вариант для смены цветов ОС или плана электропитания. Поскольку встроенного решения нет, эти люди обламываются и соглашаются на фиксированное время в планировщике заданий.
Приемы работы с ним я неоднократно показывал, и в этот раз применю, но чуть более изобретательно. Я подготовил набор скриптов PowerShell, которые создают необходимые задачи в планировщике и выполняют команды.
Для примера они задают на восходе светлые цвета ОС и приложений, а на закате – темные.
Вы сможете прописать свои команды в двух скриптах PowerShell или изменить созданные задачи в графическом интерфейсе taskschd.msc, настроив для себя запуск скриптов cmd, vbs и т.д. А для тех, кто захочет существенно изменить под свои цели исходные скрипты, в рассказе есть большой теоретический раздел.
Как это работает
Вкратце, схема несложная:
- В планировщике создается три задания.
- Основное задание от имени системы ежедневно получает из внешнего сервиса время восхода / заката и обновляет время запуска вспомогательных заданий.
- Вспомогательные задания от имени пользователя выполняются на восходе или закате.
В скриптах предусмотрены некоторые нюансы вроде выполнения пропущенной задачи без задержек.
Ограничения этого подхода
Чтобы все работало, необходимо соблюдения двух условий.
Доступность сервиса с гео-данными
Время восхода и заката берется с сервиса https://sunrise-sunset.org/. Эта ссылка была фактически первой в Google, но есть и другие бесплатные ресурсы — например, API метеорологического института Норвегии. Изменить скрипт под другой сервис очень просто, как вы увидите ниже.
Наличие у пользователя достаточных прав на выполнение команд
Вспомогательные задачи выполняются с наивысшими правами, доступными пользователю, от имени которого они запускаются. Аккаунту из группы администраторов все по плечу, но учетная запись с обычными правами не может выполнять задачи, требующие повышения прав.
В принципе, вспомогательные команды тоже можно выполнять от имени системы, что я разберу в теоретической части статьи. Но это годится только для изменения системных параметров (смена плана электропитания), и не подходит для внесения изменений в параметры текущего пользователя (смена цветовой схемы).
Практика
Я думаю, вам будет интереснее сразу перейти к практике, убедиться в работоспособности скриптов, а потом уже разбираться с теорией.
Содержимое архива
В архиве sunrise-sunset.zip четыре скрипта PowerShell.
СКРИПТ | НАЗНАЧЕНИЕ |
---|---|
set_tasks.ps1 | Создает три запланированных задания: • Sunrise-Sunset-Time (основное) • Sunrise • Sunset |
sunrise_sunset_time.ps1 | Выполняется от имени системы из основного задания: ежедневно по расписанию, при входе любого пользователя в систему и при выходе из сна/гибернации. Скрипт получает время восхода и заката, обновляет время запуска вспомогательных заданий и запускает их при необходимости.Например, если ПК был выключен вечером после заката, а включен днем после рассвета выполняется задание Sunrise. |
sunrise.ps1 | Выполняется из задания Sunrise для запуска скрипта на рассвете. |
sunset.ps1 | Выполняется из задания Sunset для запуска скрипта на закате. |
Содержимое двух последних скриптов вы можете менять на свое усмотрение.
Создание задач в планировщике
- Скачайте архив sunrise-sunset.zip и распакуйте в папку, в которой в дальнейшем будут храниться скрипты. Если вы впоследствии поменяете путь к скриптам, придется править задачи в планировщике или пересоздавать их.
- Откройте скрипт sunrise_sunset_time.ps1 в текстовом редакторе, укажите широту и долготу своего местоположения (карты Google или Яндекс в помощь) и сохраните файл.
- В проводнике перейдите в папку со скриптами, из меню Файл выберите Запустить Windows Powershell от имени администратора и выполните команду ниже (также можно явно задать путь к папке параметром
-Path
):
powershell -ExecutionPolicy Bypass '.\set_tasks.ps1'
Это все! В планировщике появятся три задачи с именами, начинающимися на Sun
. Если он был открыт во время выполнения команды, нажмите F5.
Цвета ОС и приложений могут сразу же поменяться — например, они станут темными, если команда выполнялась после заката.
Изменение задач под себя (основы)
Вам достаточно прописать свои команды PowerShell в скриптах sunrise.ps1 и sunset.ps1. Сейчас там просто изменяются два параметра реестра, например, на закате:
#Переключиться на темные цвета
#Windows
New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize -Name SystemUsesLightTheme -Value 0 -Type Dword -Force
#приложений
New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize -Name AppsUseLightTheme -Value 0 -Type Dword -Force
Если вас это чем-то смущает или не устраивает, откройте одноименные задания в планировщике и пропишите нужные команды и их аргументы на вкладке Действия. Например, в качестве команды вы можете просто указать полный путь к CMD-файлу.
Более детальный разбор заданий в теоретической части статьи.
Теория
Этот раздел для тех, кто хочет лучше понять, как все работает, а также разобраться в нюансах планирования задач с PowerShell и содержимом скриптов. Их по сути два:
- sunrise_sunset_time.ps1 – это небольшой скрипт, который получает гео-данные, обновляет время запуска задач и запускает их, если необходимо.
- set_tasks.ps1 используется однократно для создания задач планировщика. Он выглядит объемнее, но лишь из-за специфики командлетов модуля scheduledtasks и количества комментариев.
Получение и обработка гео-данных
Сервис Sunrise-Sunset отдает JSON по ссылке, принимающей параметры широты, долготы и даты.
$lat = "55.753595"
$lng = "37.621031"
$today = Get-Date -f "yyyy-MM-dd"
$j = Invoke-RestMethod "https://api.sunrise-sunset.org/json?lat=$($lat)&lng=$($lng)&date=$($today)&formatted=0"
$j.results.sunrise
В API поддерживается указание сегодняшнего дня параметром &date=today
, однако сервис завязан на UTC. Поэтому на запрос с таким параметром, например, в 00:01 по Москве, сервис ответит вчерашними данными по московскому времени.
Поэтому в скрипте дата задается явно с помощью формата командлета Get-Date, что я уже показывал в блоге. Затем командлет Invoke-RestMethod получает данные, а дальше просто выбираются сведения из нужного узла. Для наглядной работы с узлами можно отформатировать полученный JSON.
Аналогично для сервиса норвежского метеоинститута команды были бы такие:
$n = Invoke-RestMethod -uri "https://api.met.no/weatherapi/sunrise/2.0/.json?lat=$($lat)&lon=$($lng)&date=$($today)&offset=+03:00"
$n.location.time.sunrise.time
Это было просто.
Создание и изменение запланированных заданий в PowerShell
Я не раз показывал в блоге работу с schtasks (вот хороший пример). С помощью ключей schtasks формируются параметры задания, а само оно создается, опрашивается, изменяется или удаляется одной командой. Без справки с ключами не разобраться, но в итоге получается команда в одну строку.
В PowerShell сделаны отдельные командлеты для работы с заданиями, в частности для:
- управления (создание, удаление, экспорт и т.д.)
- настройки, т.е. как минимум по одному командлету для каждой вкладки задания планировщика, если апеллировать к графическому интерфейсу (параметры командлетов уже определяют конкретные настройки на вкладке)
В результате для создания или изменения задачи требуется несколько строк кода. И хотя названия командлетов лучше понятны интуитивно, чем ключи schtasks, от всего этого рябит в глазах.
Создание
Давайте посмотрим на процесс создания задачи Sunrise-Sunset-Time (фрагмент скрипта set_tasks.ps1). Переменные фактически соответствуют английским названиям вкладок в интерфейсе планировщика, а код я подробно прокомментировал.
#Переменные
$path = (Get-Location).path
$system = "NT AUTHORITY\SYSTEM"
#Создание задания
$taskname = "Sunrise-Sunset-Time"
#Общие: выполнять с наивысшими правами от имени системы вне зависимости от входа
$principal = New-ScheduledTaskPrincipal -UserId $system -LogonType ServiceAccount
#Триггеры
$trigger = @(
#Запускать ежедневно сразу после полуночи (не пробуждая ПК)
$(New-ScheduledTaskTrigger -Daily -At 00:01),
#Запускать при входе пользователя в систему
$(New-ScheduledTaskTrigger -AtLogon)
)
#Параметры: запускать при работе от батареи; запускать немедленно если пропущено
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -StartWhenAvailable
#Команда...
$execute = "powershell"
#... и ее параметры командной строки
$argument = "-ExecutionPolicy Bypass -WindowStyle Hidden -file $path\sunrise_sunset_time.ps1"
#Действие: "команда + параметры командной строки"
$action = New-ScheduledTaskAction -Execute $execute -Argument $argument
#Создать задание в планировщике
Register-ScheduledTask -TaskName $taskname -Action $action -Trigger $trigger -Settings $settings -Principal $principal
Читать это можно снизу вверх. Сама задача создается командлетом Register-ScheduledTask. Но сначала ее настройки формируются с помощью нескольких командлетов. При этом у New-ScheduledTaskAction, отвечающего за действие задачи, предусмотрены раздельные параметры для команды и ее ключей.
Также обратите внимание, как задается несколько триггеров одновременно. К сожалению, у командлета New-ScheduledTaskTrigger не предусмотрен параметр для триггера по событию в журнале. Поэтому триггер на событие выхода из сна / гибернации добавляется обходным путем уже после создания задачи (см. код в скрипте).
Изменение
С этим несколько проще. Задача Sunrise-Sunset-Time запускает скрипт sunrise_sunset_time.ps1, в котором есть такой фрагмент:
$j = Invoke-RestMethod "https://api.sunrise-sunset.org/json?lat=$($lat)&lng=$($lng)&date=$($today)&formatted=0"
$sunrise = New-ScheduledTaskTrigger -Once -At $j.results.sunrise
Set-ScheduledTask -TaskName "Sunrise" -Trigger $sunrise
Полученное из гео-сервиса время заносится в переменную, а она в свою очередь используется для формирования триггера командлетом New-ScheduledTaskTrigger. Затем этот триггер передается командлету Set-ScheduledTask, изменяющему задание.
Разобраться в модуле scheduledtasks с наскока затруднительно. Надо практиковаться, читать документацию и гуглить в тяжелых случаях.
Изменение заданий под себя (нюансы планировщика)
Есть несколько моментов, которые нужно учитывать в зависимости от ваших целей и пользователей, для которых должны отрабатывать задания. Это больше относится к планировщику, нежели к PowerShell.
Запуск в зависимости от выполненного входа
Основное задание запускается от имени SYSTEM вне зависимости от входа. Для такого случая у командлета New-ScheduledTaskPrincipal есть параметр -LogonType
, задающий служебный аккаунт.
$system = "NT AUTHORITY\SYSTEM"
$principal = New-ScheduledTaskPrincipal -UserId $system -LogonType ServiceAccount
Вспомогательные задания выполняются в контексте пользователя, только когда он выполнил вход. Допустим, в некий момент времени ПК включен, но пользователь не вошел в систему. В этом случае основной скрипт отработает и обновит время запуска вспомогательных. Дальше допустим, что пользователь вошел в систему позже запланированного времени.
Если бы у основного задания был только триггер на расписание, пропущенные задания выполнялись бы спустя несколько минут после входа пользователя. Однако благодаря триггеру на вход в систему, основной скрипт снова запустится и форсирует запуск вспомогательного, если необходимо. Аналогично работает и сценарий выхода из сна/гибернации.
Если вы хотите выполнять команды от имени своей учетной записи вне зависимости от ее входа, на вкладке Общие нажмите Выполнять для всех пользователей – ОК – введите пароль своего аккаунта.
Если ваши команды изменяют параметры системы, а не пользователя (например, меняют план электропитания), вы можете перевести вспомогательные задания в системный контекст. Для этого на вкладке Общие нажмите Изменить и введите имя СИСТЕМА или SYSTEM в зависимости от языка вашей ОС.
Запуск в контексте другого пользователя
Скрипт set-tasks.ps1 создает вспомогательные задания для пользователя, который его запускает, т.е. для вашей учетной записи. Вы можете указать вместо себя другого пользователя при запуске скрипта параметром -User
в формате "Имя_компьютера\Имя_пользователя"
. Например, для пользователя на картинке выше:
powershell -ExecutionPolicy Bypass '.\set-tasks.ps1' -User "DESKTOP-EHPD2RM\Admin" -Path "C:\MyScripts"
Если задания уже созданы, на вкладке Общие нажмите Изменить и введите имя пользователя.
Запуск для нескольких пользователей
Чтобы вспомогательные скрипты работали для нескольких пользователей в их контексте, можно создавать события в журнале. Либо так:
- В планировщике экспортировать задачи Sunrise и Sunset и импортировать их с другими именами, а затем указать для клонов нужных пользователей.
- Добавить в скрипт sunrise_sunset_time.ps1 команды для обновления времени клонов и их запуска в сценарии «пропущено назначенное время выполнения задания».
Если вам это необходимо, попрактикуйтесь самостоятельно.
Запуск без моргания окон PowerShell
Несмотря на -WindowStyle Hidden
в аргументах скриптов, при их запуске из планировщика могут появляться окна PowerShell.
Этого не происходит для заданий, которые выполняются вне зависимости от входа пользователя в систему (в частности, для основного скрипта Sunrise-Sunset-Time).
Отсюда следует несколько решений на выбор для вспомогательных заданий. Вы можете:
- выполнять их от имени системы, если изменяются параметры ОС, а не пользователя
- выполнять их от своего имени, сохранив учетные данные
- запускать из планировщика скрипт VBS, из которого в свою очередь вызывается скрипт PowerShell
Первые два способа я рассмотрел выше, а третий показывал в своем канале Telegram
Диагностика
Концептуально решение должно работать начиная с Windows 7 (с WMF 5.1), но я тестировал только на русской и английской Windows 10 1909. На случай, если что-то пойдет не так, я подобрал рекомендации по отладке скриптов и заданий планировщика.
В рамках диагностики:
- выполняйте все команды и скрипты от имени администратора
- после запуска заданий планировщика нажимайте F5, чтобы обновить картину
Основное задание не обновляет вспомогательные
- Выполните задание вручную и убедитесь, что изменилось время его последнего запуска, а операция завершилась успешно. Ошибки могут означать недостаток прав на выполнение команды или неверную конфигурацию действия.
- Измените время запуска вспомогательного задания и запустите основное, а затем проверьте, поменялось ли время вспомогательного.
- Если время не изменилось, возможно, не удается достучаться до гео-сервиса. Откройте ссылку ниже в браузере, и если она открывается, выполните в PowerShell:
Invoke-RestMethod 'https://api.sunrise-sunset.org/json?lat=55.751510&lng=37.618877&date=today&formatted=0'
Если данные отображаются, нужно разбираться с дальнейшей логикой скрипта sunrise_sunset_time.ps1.
Вспомогательные задания не работают
- См. п.1 выше.
- Выполняйте скрипты sunrise.ps1 и sunset.ps1 или их команды в PowerShell и смотрите, что происходит. Если не работает, дело не в планировщике.
- Пробуйте прописать в скриптах что-нибудь простое, например, запись текущей даты в текстовый файл:
Get-Date | Out-File -FilePath C:\scripts\test.txt -Force
4. Если это работает, выполняйте одноименные задания в планировщике.
Помощь
Если вам необходима помощь, предоставьте в комментариях:
- Экспортированные задания планировщика плюс скрипты sunrise.ps1 и sunset.ps1 (если меняли). Все должно быть упаковано в ZIP и залито на Яндекс.Диск / OneDrive / Google Drive.
- Команды и ошибки при их выполнении в текстовом виде на https://pastebin.com/, в скриншотах – на хостинге картинок.
- Подробное описание проблемы и отчет по мотивам диагностики выше.
Заключение
Если бы в планировщике уже было некое задание с триггерами на восходе и закате, можно было бы цепляться к нему или клонировать путем экспорта/импорта, но я такого не нашел. Наверное, ночной свет использует API встроенной в систему службы местоположения, что было бы логично.
Скрипт создания задач планировщика выглядит сложновато для неподготовленного человека. Но лезть в него необязательно, поскольку параметры созданных задач можно изменять в графическом интерфейсе taskschd.msc. Все остальное укладывается в дюжину строк кода PowerShell.
При этом мой подход несложно адаптировать под другие задачи, выполняющиеся на основе различных гео-данных, прогноза погоды и вообще любых сведений, подтягиваемых из интернета.
Из JSON очень легко извлекать необходимые сведения, а в более сложных случаях можно обрабатывать вывод Invoke-WebRequest.