PowerShell скрипты это текстовые файлы с расширением .ps1, в которых записан набор команд. Чтобы скрипт запустился в Windows 10 или 11, нужно один раз разрешить выполнение скриптов для своего пользователя командой Set-ExecutionPolicy RemoteSigned -Scope CurrentUser и вызывать скрипт по пути, например .\имя.ps1.
Примеры ниже прогнаны в Windows PowerShell 5.1 на Windows 10 с русскими сообщениями об ошибках. PowerShell 7 на этой машине не ставили.

- Что такое скрипт PowerShell и чем он отличается от bat-файла
- В чём писать скрипты: Блокнот, ISE или VS Code
- Как создать скрипт и сохранить файл .ps1
- Как запустить скрипт PowerShell
- «Выполнение сценариев отключено в этой системе»: как разрешить скрипты
- Скачанный скрипт не запускается при RemoteSigned
- Переменные и типы данных
- Условия if и операторы сравнения
- Циклы foreach, for и while
- Функции
- Параметры скрипта и полезный пример
- Обработка ошибок: try и catch
- Частые вопросы о скриптах PowerShell
- Какое расширение у файла скрипта PowerShell?
- Как запустить скрипт PowerShell из командной строки cmd?
- Чем RemoteSigned отличается от Unrestricted?
- Почему скрипт работает в PowerShell 7, а в Windows PowerShell нет?
- Как запустить скрипт в фоне?
Что такое скрипт PowerShell и чем он отличается от bat-файла
В Windows уже есть Windows PowerShell 5.1 (powershell.exe), новых функций в него больше не добавляют. PowerShell 7 ставится отдельно, рядом с ним, запускается командой pwsh и работает также в Linux и macOS.
Команды bat-файла и командной строки cmd обмениваются текстом. Командлеты PowerShell принимают и возвращают объекты .NET со свойствами, и с помощью конвейера файлы можно отфильтровать по дате изменения без разбора строк вывода: Get-ChildItem | Where-Object { $_.LastWriteTime -lt $limit }. Внешние программы вроде ping в скрипте тоже работают, но их вывод приходит обычными строками.
Имена командлетов (cmdlet) строятся по схеме «глагол-существительное»: Get-Process, Get-Service, Copy-Item, Remove-Item. Справку по команде показывает Get-Help, но на свежей Windows 10 файлов справки нет: Get-Help Get-ChildItem -Examples выводит только имя, псевдонимы и замечание «Команде Get-Help не удалось найти файлы справки для этого командлета на этом компьютере». Полную справку скачивает Update-Help в окне PowerShell, запущенном от имени администратора; без прав её можно открыть в браузере командой Get-Help Get-ChildItem -Online.
В чём писать скрипты: Блокнот, ISE или VS Code
Для пары строк хватит Блокнота. Для написания скриптов посложнее нужен редактор с подсветкой и отладкой.
Windows PowerShell ISE (powershell_ise.exe), интегрированная среда сценариев, входит в Windows 10 (в новых сборках её можно удалить как компонент) и позволяет запустить выделенную часть скрипта. Новых функций в ней больше не будет, и PowerShell 7 она не поддерживает.
Visual Studio Code с расширением PowerShell работает и с PowerShell 7: для этой версии его называют основной средой написания скриптов. Скрипты он по умолчанию сохраняет в UTF-8 без BOM, поэтому для кириллицы в 5.1 кодировку придётся сменить, как показано ниже. Новые скрипты мы пишем в VS Code.
Как поставить и настроить сам редактор, мы разбирали в статье про VS Code для Python: установка та же, меняется только расширение.
Как создать скрипт и сохранить файл .ps1
Создайте папку C:\ps, откройте редактор и наберите первый скрипт:
# hello.ps1
$name = "мир"
Write-Output "Привет, $name!"
Write-Output "Версия: $($PSVersionTable.PSVersion)"
Сохраните его как C:\ps\hello.ps1. В Блокноте в поле типа выберите «Все файлы» вместо «Текстовые документы (*.txt)», ISE и так создаёт новый скрипт с расширением .ps1. Проверить имя можно командой Get-ChildItem C:\ps.
Кодировка важна, если в скрипте есть кириллица. Windows PowerShell 5.1 читает скрипт без BOM в старой кодировке ANSI (на нашей русской Windows 10 это кодовая страница 1251). Блокнот в Windows 10 с версии 1903 по умолчанию сохраняет в UTF-8 без BOM, VS Code тоже. Тот же скрипт в UTF-8 без BOM выводит такое:
Привет, мир!
Версия: 5.1.19041.6456
Скрипты с кириллицей сохраняйте в UTF-8 с BOM. В Блокноте это пункт «UTF-8 со спецификацией» в списке «Кодировка» окна сохранения. В VS Code щёлкните по названию кодировки в строке состояния, выберите Save with Encoding, затем UTF-8 with BOM. В PowerShell 7 файлы без BOM читаются нормально, ошибка бывает только в 5.1.

Как запустить скрипт PowerShell
Чтобы запустить скрипт, откройте консоль PowerShell (меню «Пуск», введите powershell), перейдите в папку и вызовите скрипт с путём .\:
cd C:\ps
.\hello.ps1
На свежей Windows 10 вместо приветствия придёт ошибка про политику выполнения, о ней следующий раздел. Когда скрипты разрешены, вывод такой:
Привет, мир!
Версия: 5.1.19041.6456
Без .\ PowerShell не запускает скрипт из текущей папки, даже если он лежит рядом: так задумано ради безопасности. На команду hello.ps1 консоль ответит «Имя «hello.ps1» не распознано как имя командлета, функции, файла сценария или выполняемой программы. Проверьте правильность написания имени, а также наличие и правильность пути, после чего повторите попытку».
Если в полном пути есть пробелы, используйте кавычки и оператор вызова &. Без & строка в кавычках для PowerShell просто текст, и консоль печатает её обратно:
& "C:\my scripts\hello.ps1"
"C:\my scripts\hello.ps1"
Привет, мир!
Версия: 5.1.19041.6456
C:\my scripts\hello.ps1
Из командной строки cmd, планировщика заданий или ярлыка скрипт можно запустить через powershell.exe с параметром -File. Параметр -ExecutionPolicy Bypass снимает ограничения только для этого запуска и в настройках ничего не меняет:
powershell -ExecutionPolicy Bypass -File "C:\ps\hello.ps1"
Для PowerShell 7 то же самое пишут через pwsh -File. Двойной щелчок скрипт тоже не запускает, на нашей Windows 10 он просто открывается в Блокноте.
«Выполнение сценариев отключено в этой системе»: как разрешить скрипты
Если политику выполнения никто не менял, первая попытка запустить скрипт в Windows 10 кончается так:
.\hello.ps1 : Невозможно загрузить файл C:\ps\hello.ps1, так как выполнение сценариев отключено в этой системе. Для получения дополнительных сведений см. about_Execution_Policies по адресу https:/go.microsoft.com/fwlink/?LinkID=135170.
строка:1 знак:1
+ .\hello.ps1
+ ~~~~~~~~~~~
+ CategoryInfo : Ошибка безопасности: (:) [], PSSecurityException
+ FullyQualifiedErrorId : UnauthorizedAccess
Виновата политика выполнения (execution policy). Если её никто не задавал, в клиентских версиях Windows действует Restricted: отдельные команды выполнять можно, файлы сценариев нельзя. На Windows Server в той же ситуации действует RemoteSigned. Посмотреть настройки по всем областям:
Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser Undefined
LocalMachine Undefined
Везде Undefined, то есть ничего не задано, и действует правило по умолчанию. Команда Get-ExecutionPolicy без -List показывает итог, у нас это Restricted.
Области проверяются сверху вниз. MachinePolicy и UserPolicy задаёт групповая политика, и она перекрывает всё остальное: если администратор настроил её на рабочем компьютере, командой Set-ExecutionPolicy её не перебить. Дальше идут Process (текущее окно), CurrentUser и LocalMachine (все пользователи, нужны права администратора).
| Политика | Свои скрипты | Скачанные из интернета |
|---|---|---|
| Restricted | нельзя | нельзя |
| AllSigned | только с цифровой подписью | только с цифровой подписью |
| RemoteSigned | можно | только с подписью или после Unblock-File |
| Unrestricted | можно | с предупреждением |
| Bypass | можно | без вопросов |
Чтобы разрешить запуск скриптов для себя, без прав администратора, выполните команду:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Перезапускать PowerShell не нужно, новая политика действует сразу и сохраняется до следующего изменения. Без -Scope команда меняет область LocalMachine, и в обычном окне без прав администратора она не пройдёт. Unrestricted и постоянный Bypass мы бы не ставили: свои скрипты при RemoteSigned и так работают, а файлы с пометкой загрузки из интернета без подписи запустятся только после явного разблокирования.
Если не хотите менять настройки системы, достаточно разрешить запуск скриптов в текущем окне. После его закрытия всё вернётся как было:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
.\hello.ps1
Привет, мир!
Версия: 5.1.19041.6456
На ту же ошибку натыкаются при активации виртуального окружения Python скриптом Activate.ps1, этот случай разобран в статье про ModuleNotFoundError.
Убрать свою настройку можно командой Set-ExecutionPolicy -ExecutionPolicy Undefined -Scope CurrentUser. Если в других областях ничего не задано, снова начнёт действовать политика по умолчанию, на клиентской Windows это Restricted. Учтите, что политика выполнения не защищает от злоумышленника: содержимое скрипта можно вставить в окно PowerShell построчно, она лишь страхует от случайного запуска.
У Windows PowerShell 5.1 и PowerShell 7 настройки хранятся в разных местах. Разрешили скрипты в powershell.exe, а в pwsh.exe они по-прежнему запрещены, и наоборот. В Linux и macOS политика всегда Unrestricted, поменять её нельзя.

Скачанный скрипт не запускается при RemoteSigned
Edge и некоторые другие программы помечают скачанный файл как пришедший из интернета. Требование подписи при RemoteSigned касается только файлов с такой пометкой. Допустим, из браузера скачан downloaded.ps1 с одной строкой Write-Output "Скачанный скрипт работает". Запуск закончится так:
.\downloaded.ps1 : Не удается загрузить файл C:\ps\downloaded.ps1. Файл C:\ps\downloaded.ps1 не имеет цифровой подписи. Невозможно выполнить сценарий в указанной системе. Для получения дополнительных сведений о выполнении сценариев и настройке политики выполнения см. about_Execution_Policies по адресу https:/go.microsoft.com/fwlink/?LinkID=135170.
строка:1 знак:1
+ .\downloaded.ps1
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : Ошибка безопасности: (:) [], PSSecurityException
+ FullyQualifiedErrorId : UnauthorizedAccess
Сначала прочитайте код скрипта. Если в нём нет ничего подозрительного, снимите пометку командлетом Unblock-File, политику менять не придётся:
Unblock-File -Path .\downloaded.ps1
.\downloaded.ps1
Скачанный скрипт работает
Пометку ставят не все программы: файлы, загруженные через curl.exe или Invoke-WebRequest, могут её не получить и тогда запускаются как созданные на этом компьютере. Поэтому чужой скрипт читайте до запуска независимо от того, как он скачан.
Переменные и типы данных
Имя переменной начинается с $, объявлять тип не обязательно: PowerShell определит его по значению. Массив создают через @():
$count = 3
$title = "Отчёт"
$files = @("a.txt", "b.log", "c.txt")
$count.GetType().Name
$files.Count
"${title}: $count"
"5" + 3
5 + "3"
Int32
3
Отчёт: 3
53
8
В двух последних строках тип результата задал левый операнд: строка плюс число склеилась, число плюс строка сложилось. В двойных кавычках переменные подставляются, в одинарных нет. Фигурные скобки в ${title} нужны из-за двоеточия: строку "$title: $count" PowerShell не разберёт и ответит «Недопустимая ссылка на переменную. За знаком : не следует допустимый знак имени переменной».
Условия if и операторы сравнения
Ветки записываются через if, elseif (слитно) и else, а для сравнения используют операторы с дефисом:
$size = 120
if ($size -gt 100) {
"большой файл"
} elseif ($size -gt 10) {
"средний файл"
} else {
"маленький файл"
}
"PowerShell" -eq "powershell"
"PowerShell" -ceq "powershell"
"report.txt" -like "*.txt"
большой файл
True
False
True
| Оператор | Значение |
|---|---|
-eq, -ne | равно, не равно |
-gt, -ge | больше, больше или равно |
-lt, -le | меньше, меньше или равно |
-like | сравнение с шаблоном * и ? |
-match | регулярное выражение |
-and, -or, -not | логические И, ИЛИ, НЕ |
Строки по умолчанию сравниваются без учёта регистра, для точного сравнения есть -ceq. Знак > использовать для сравнения нельзя: в PowerShell он перенаправляет вывод в файл, и ошибки в этом месте не будет:
if ($size > 100) { "больше ста" } else { "не больше ста" }
Get-Content .\100
Remove-Item .\100
не больше ста
120
Условие записало значение $size в новый файл 100 и вернуло пустоту, поэтому сработала ветка else.
Циклы foreach, for и while
foreach перебирает коллекцию, for нужен, когда важен счётчик, while крутится, пока условие верно:
foreach ($f in $files) {
if ($f -like "*.txt") { "текстовый: $f" }
}
for ($i = 1; $i -le 3; $i++) { "шаг $i" }
$n = 1
while ($n -lt 100) { $n *= 2 }
$n
1..6 | Where-Object { $_ % 2 -eq 0 } | ForEach-Object { $_ * 10 }
текстовый: a.txt
текстовый: c.txt
шаг 1
шаг 2
шаг 3
128
20
40
60
Последняя строка показывает другой способ, через конвейер: Where-Object оставляет чётные числа, ForEach-Object умножает каждое, а $_ обозначает текущий объект. Чем отличаются циклы с условием в других языках, подробно разобрано в статье про while, for и do while.

Функции
Функция объявляется словом function, параметры описывают в блоке param, и дальше её можно использовать как обычную команду. Писать return не обязательно: в результат попадает всё, что функция вывела и не сохранила в переменную.
function Get-Square {
param([int]$Number)
$Number * $Number
}
Get-Square -Number 7
Get-Square 4
function Add-Numbers($a, $b) { $a + $b }
Add-Numbers 2 3
Add-Numbers(2, 3)
49
16
5
2
3
Последний вызов выглядит как в Python, но работает иначе. Аргументы PowerShell разделяет пробелом, поэтому (2, 3) превратился в массив и целиком ушёл в $a, а $b остался пустым. Функция вернула массив, и консоль напечатала его элементы по одному. Называйте функции по той же схеме «глагол-существительное», что и командлеты: список допустимых глаголов выводит Get-Verb.
Параметры скрипта и полезный пример
У скрипта тоже бывает блок param, и стоять он должен первым, выше него только комментарии. Пример: скрипт удаляет из папки файлы старше заданного числа дней, например старые логи.
<#
.SYNOPSIS
Удаляет из папки файлы старше заданного числа дней.
.EXAMPLE
.\Clean-OldFiles.ps1 -Path C:\logs -Days 30 -WhatIf
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[string]$Path,
[int]$Days = 7
)
if (-not (Test-Path -Path $Path -PathType Container)) {
Write-Error "Папка не найдена: $Path"
exit 1
}
$limit = (Get-Date).AddDays(-$Days)
$old = @(Get-ChildItem -Path $Path -File | Where-Object { $_.LastWriteTime -lt $limit })
foreach ($file in $old) {
if ($PSCmdlet.ShouldProcess($file.Name, "Удалить файл")) {
Remove-Item -Path $file.FullName
Write-Output "Удалён: $($file.Name)"
}
}
Write-Output "Файлов старше $Days дн.: $($old.Count)"
Сохраните его как C:\ps\Clean-OldFiles.ps1 в UTF-8 с BOM. Комментарий в начале служит справкой: Get-Help .\Clean-OldFiles.ps1 -Examples покажет описание и пример. [Parameter(Mandatory)] делает путь обязательным, у $Days значение по умолчанию 7. Строка SupportsShouldProcess добавляет скрипту параметр -WhatIf.
Для проверки создадим несколько тестовых логов возрастом 2, 10 и 40 дней:
New-Item -ItemType Directory -Path C:\ps\logs -Force | Out-Null
foreach ($d in 2, 10, 40) {
$f = New-Item -Path "C:\ps\logs\app-$d.log" -ItemType File -Force
$f.LastWriteTime = (Get-Date).AddDays(-$d)
}
Скрипт, который что-то удаляет, сначала запускайте с -WhatIf: он покажет, какие действия собирается выполнить, и ничего не тронет.
.\Clean-OldFiles.ps1 -Path C:\ps\logs -WhatIf
.\Clean-OldFiles.ps1 -Path C:\ps\logs -Days 30
WhatIf: Выполнение операции "Удалить файл" над целевым объектом "app-10.log".
WhatIf: Выполнение операции "Удалить файл" над целевым объектом "app-40.log".
Файлов старше 7 дн.: 2
Удалён: app-40.log
Файлов старше 30 дн.: 1
exit 1 в начале скрипта сообщает вызывающей программе о сбое: по умолчанию код возврата 0, ненулевой обычно означает ошибку. В PowerShell он попадает в $LASTEXITCODE, в cmd в %ERRORLEVEL%, и bat-файл может проверить, справился ли скрипт:
powershell -ExecutionPolicy Bypass -File C:\ps\Clean-OldFiles.ps1 -Path C:\nologs
echo %ERRORLEVEL%
C:\ps\Clean-OldFiles.ps1 : Папка не найдена: C:\nologs
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Clean-OldFiles.ps1
1

Обработка ошибок: try и catch
Блок catch ловит только прерывающие ошибки. Отсутствие файла у Get-Content к ним не относится: PowerShell печатает сообщение и выполняет следующую строку. Чтобы ошибка дошла до catch, команде внутри try добавляют -ErrorAction Stop:
try {
Get-Content -Path C:\ps\nofile.txt -ErrorAction Stop
}
catch {
"Не прочитали файл: $($_.Exception.GetType().Name)"
}
Не прочитали файл: ItemNotFoundException
Без -ErrorAction Stop блок catch здесь не сработает: в консоль выведется сообщение «Не удается найти путь», и скрипт пойдёт дальше.

Частые вопросы о скриптах PowerShell
Какое расширение у файла скрипта PowerShell?
Скрипт сохраняют с расширением .ps1. Модуль скрипта хранится в .psm1, а .psd1 это файл данных PowerShell, в таком формате, например, пишут манифест модуля.
Как запустить скрипт PowerShell из командной строки cmd?
Командой powershell -File "C:\путь\скрипт.ps1", параметры скрипта пишутся после имени. Если политика ещё не настроена, добавьте -ExecutionPolicy Bypass: настройки системы при этом не изменятся.
Чем RemoteSigned отличается от Unrestricted?
Оба режима запускают ваши собственные скрипты. Скачанный скрипт без подписи RemoteSigned не пропустит, пока вы не снимете пометку через Unblock-File. Unrestricted запустит его, только спросив подтверждение.
Почему скрипт работает в PowerShell 7, а в Windows PowerShell нет?
Первая возможная причина в синтаксисе. Операторы && и ||, тернарный ?:, ?? и ??=, параметр ForEach-Object -Parallel появились только в PowerShell 7.0. На строку Write-Output a && Write-Output b Windows PowerShell 5.1 отвечает «Лексема «&&» не является допустимым разделителем операторов в этой версии». Ещё две причины: политика выполнения у powershell.exe и pwsh.exe настраивается отдельно, а скрипт в UTF-8 без BOM PowerShell 7 читает нормально, тогда как 5.1 принимает его за ANSI и портит кириллицу.
Как запустить скрипт в фоне?
С помощью Start-Job -FilePath C:\ps\hello.ps1: скрипт выполняется фоновым заданием, консоль остаётся свободной, а результат можно получить командой Receive-Job. Рабочая папка задания в Windows по умолчанию $HOME\Documents, поэтому пути внутри такого скрипта лучше писать полностью.








