- Get-Command — найти любой командлет по маске, не вспоминая точное название
- Select-Object — выбрать нужные поля, создать вычисляемые свойства на лету
- Where-Object — фильтровать любой поток данных по условию
- ForEach-Object — обработать каждый элемент, в PowerShell 7 — параллельно
- Group-Object — агрегация и аналитика без Excel за 30 секунд
- Measure-Object — сумма, среднее, минимум, максимум в одну строку
- Compare-Object — сравнить конфиги, списки пользователей, состояние служб
- Export-Csv / Import-Csv — выгрузить результат в файл, загрузить обратно
- Get-Member — узнать что умеет любой объект, не открывая документацию
- Sort-Object — отсортировать по любому полю, включая вычисляемое
Всё это работает в конвейере. Комбинируй — и 90% рутины закрывается в 5-10 строк.
Зачем вообще разбираться в командлетах PowerShell глубже чем Get-Help
Командлеты PowerShell — это не просто команды. Это объектный конвейер, где каждый шаг получает структурированные данные и передаёт их дальше. Знакомо? Ты открываешь PowerShell, набираешь Get-Service, смотришь на стену текста — и закрываешь. GUI быстрее. Понятнее. Привычнее.
Это продолжается до первого раза когда тебе нужно сравнить состояние служб на 50 серверах. Или найти все процессы которые жрут больше 500 МБ памяти. Или выгрузить в CSV список пользователей с их последним логином за последние 90 дней. Тут GUI внезапно становится очень медленным.
Что получишь в этой статье: 10 командлетов с реальными примерами из продакшна, объяснением зачем нужен каждый шаг, и комбинированными конвейерами которые можно копировать сразу. Времени займёт 20-30 минут. На выходе — шпаргалка которая останется открытой в браузере.
Что нужно: PowerShell 5.1 (встроен в Windows 10/11/Server 2019+) или PowerShell 7.x для параллельных вычислений и новых операторов. Права администратора для части примеров с процессами и службами.
| Версия | Платформа | Ключевые отличия |
|---|---|---|
| Windows PowerShell 5.1 | Windows только | Встроен, совместим со всеми модулями Windows |
| PowerShell 7.4 LTS | Windows, Linux, macOS | ForEach-Object -Parallel, тернарный оператор, быстрее |
| PowerShell 7.5+ | Windows, Linux, macOS | ConvertTo-CliXml, актуальные исправления веб-командлетов |
На момент публикации актуальна версия PowerShell 7.5. Перед установкой проверь свежие релизы на github.com/PowerShell/PowerShell/releases.
Как работает конвейер PowerShell — схема за 2 минуты
Прежде чем разбирать командлеты по одному — вот как они взаимодействуют. PowerShell не передаёт текст между командами как bash. Он передаёт объекты. Это меняет всё.
Каждый командлет получает полноценный .NET объект со всеми свойствами и методами. Не строку которую надо парсить регулярками, а структуру. Where-Object фильтрует по реальным свойствам объекта. Select-Object выбирает нужные поля. Export-Csv сохраняет результат.
1. Get-Command: найти командлет не зная его имени
Забыл название команды. Помнишь только что она связана с сетью или с пользователями. Лезть в Google? Не надо.
# Поиск по маске в имени
Get-Command *service*
# Поиск всех командлетов в конкретном модуле
Get-Command -Module NetTCPIP
# Только командлеты, не функции и не алиасы
Get-Command -CommandType Cmdlet *net*
# Посмотреть параметры конкретной команды
Get-Command Get-Service | Select-Object -ExpandProperty Parameters
Почему это работает лучше Google: результат актуален для твоей версии PowerShell и установленных модулей. Установил модуль Az для Azure — все его командлеты сразу видны через Get-Command -Module Az.*. Установил ExchangeOnline — аналогично.
# Найти все командлеты для работы с Azure виртуальными машинами
Get-Command -Module Az.Compute *VM*
# Сколько командлетов в конкретном модуле
Get-Command -Module ActiveDirectory | Measure-Object
2. Select-Object: выбрать поля и создать новые прямо в конвейере
Большинство используют Select-Object только чтобы выбрать несколько колонок. Это как использовать швейцарский нож только чтобы открыть бутылку.
Самая сильная фича — вычисляемые свойства. Создаёшь новое поле прямо в конвейере, без промежуточных переменных.
# Базовый выбор полей
Get-Process | Select-Object Name, CPU, WorkingSet
# Вычисляемое свойство - конвертация байт в мегабайты
Get-Process | Select-Object Name,
@{Name="Memory_MB"; Expression={[math]::Round($_.WorkingSet / 1MB, 2)}},
@{Name="CPU_sec"; Expression={[math]::Round($_.CPU, 1)}},
@{Name="Heavy"; Expression={$_.WorkingSet -gt 200MB}}
# Первые и последние N элементов
Get-EventLog -LogName System -Newest 1000 | Select-Object -First 10
Get-EventLog -LogName System -Newest 1000 | Select-Object -Last 10
# Уникальные значения одного поля
Get-Service | Select-Object Status -Unique
Вычисляемые свойства работают везде где принимается объект: в Sort-Object, Group-Object, Format-Table, Export-Csv. Один раз написал — пропускаешь через весь конвейер.
# Практический пример: топ-10 процессов по памяти с читаемыми числами
Get-Process |
Select-Object Name,
@{Name="RAM_MB"; Expression={[math]::Round($_.WorkingSet / 1MB, 1)}},
@{Name="CPU_min"; Expression={[math]::Round($_.CPU / 60, 2)}},
Id |
Sort-Object RAM_MB -Descending |
Select-Object -First 10 |
Format-Table -AutoSize
3. Where-Object: фильтрация с человеческим лицом
Фильтрация — это 80% работы с данными в PowerShell. Where-Object фильтрует объекты в конвейере по любому условию. В PowerShell 7 синтаксис стал проще.
# Классика - работает в PS 5.1 и PS 7
Get-Service | Where-Object {$_.Status -eq "Running"}
# Упрощённый синтаксис PS 7 для одного условия
Get-Service | Where-Object Status -eq "Running"
# Несколько условий
Get-Process | Where-Object {
$_.CPU -gt 100 -and
$_.WorkingSet -gt 100MB -and
$_.Name -notlike "*chrome*"
}
# Фильтр по шаблону
Get-Service | Where-Object Name -like "*sql*"
# Оператор -in: проверить принадлежность к списку
$critical = "WinRM", "EventLog", "W32Time", "Netlogon"
Get-Service | Where-Object Name -in $critical | Select-Object Name, Status
# Фильтр по наличию непустого свойства
Get-Process | Where-Object {$_.MainWindowTitle} |
Select-Object Name, MainWindowTitle, Id
Операторы сравнения которые используются чаще всего:
| Оператор | Значение | Пример |
|---|---|---|
-eq |
Равно | Status -eq "Running" |
-ne |
Не равно | Status -ne "Stopped" |
-gt / -lt |
Больше / меньше | CPU -gt 50 |
-like |
Маска с * и ? | Name -like "*sql*" |
-match |
Регулярное выражение | Name -match "^sql\d+" |
-in |
Входит в список | Name -in $list |
-contains |
Список содержит значение | $list -contains "value" |
4. ForEach-Object: обработать каждый элемент, и сделать это быстро
Цикл foreach в PowerShell хорош. ForEach-Object в конвейере — лучше. Он не ждёт пока предыдущий командлет отдаст все объекты: обрабатывает по одному по мере поступления. На больших выборках это заметно.
# Базовый перебор с форматированием
Get-Service | ForEach-Object {
$color = if ($_.Status -eq "Running") {"Green"} else {"Red"}
Write-Host "$($_.Name): $($_.Status)" -ForegroundColor $color
}
# Создать кастомные объекты из существующих
Get-ChildItem C:\Windows -Filter *.log | ForEach-Object {
[PSCustomObject]@{
Name = $_.Name
Size_KB = [math]::Round($_.Length / 1KB, 1)
Modified = $_.LastWriteTime.ToString("dd.MM.yyyy HH:mm")
OlderThan30 = $_.LastWriteTime -lt (Get-Date).AddDays(-30)
}
} | Sort-Object Size_KB -Descending
А теперь самое интересное. PowerShell 7 добавил параметр -Parallel. Это не просто синтаксический сахар — это реальная многопоточность.
# Параллельный пинг 20 хостов - PS 7 только
$hosts = "srv01","srv02","srv03","srv04","srv05",
"srv06","srv07","srv08","srv09","srv10"
$results = $hosts | ForEach-Object -Parallel {
$ping = Test-Connection -ComputerName $_ -Count 1 -Quiet
[PSCustomObject]@{
Host = $_
Online = $ping
Time = Get-Date -Format "HH:mm:ss"
}
} -ThrottleLimit 10
$results | Sort-Object Host | Format-Table -AutoSize
Без -Parallel этот скрипт пингует хосты последовательно. 10 хостов с таймаутом 1 секунда — это 10+ секунд. С -Parallel -ThrottleLimit 10 — все за 1-2 секунды. На 100 хостах разница становится принципиальной.
# Проверка свободного места на удалённых серверах - параллельно
$servers = Get-Content "servers.txt"
$diskInfo = $servers | ForEach-Object -Parallel {
$disk = Get-PSDrive C -PSProvider FileSystem
[PSCustomObject]@{
Server = $_
Free_GB = [math]::Round($disk.Free / 1GB, 2)
Used_GB = [math]::Round($disk.Used / 1GB, 2)
Low = $disk.Free -lt 10GB
}
} -ThrottleLimit 20
$diskInfo | Where-Object Low | Format-Table -AutoSize
5. Group-Object: агрегация без Excel
Нужно понять закономерность в данных? Сколько служб в каком статусе? Какие источники ошибок самые активные? Кто из пользователей чаще всего падает в Event Log? Group-Object отвечает за секунды.
# Распределение служб по статусу
Get-Service | Group-Object Status -NoElement |
Select-Object Count, Name |
Sort-Object Count -Descending
# Топ источников ошибок за последний час
Get-EventLog -LogName System -EntryType Error -After (Get-Date).AddHours(-1) |
Group-Object Source |
Select-Object Count, Name |
Sort-Object Count -Descending |
Select-Object -First 10
# Группировка с вычислениями - память по компании-производителю
Get-Process | Where-Object {$_.Company} |
Group-Object Company |
Select-Object Name, Count,
@{Name="TotalRAM_MB"; Expression={
[math]::Round(($_.Group | Measure-Object WorkingSet -Sum).Sum / 1MB, 1)
}} |
Sort-Object TotalRAM_MB -Descending |
Select-Object -First 10
Группировка по вычисляемому выражению — это мощь которую не все знают. Можешь группировать по первой букве, по диапазону значений, по дате без времени.
# Группировка событий по дате (без времени)
Get-EventLog -LogName System -Newest 500 |
Group-Object {$_.TimeGenerated.Date.ToString("dd.MM.yyyy")} |
Select-Object Name, Count |
Sort-Object Name
# Группировка файлов по расширению с суммой размеров
Get-ChildItem C:\Windows -File |
Group-Object Extension |
Select-Object Name, Count,
@{Name="Total_MB"; Expression={
[math]::Round(($_.Group | Measure-Object Length -Sum).Sum / 1MB, 2)
}} |
Sort-Object Total_MB -Descending |
Select-Object -First 10 |
Format-Table -AutoSize
6. Sort-Object: сортировка по чему угодно
Казалось бы — что тут сложного? Но у Sort-Object есть несколько приёмов которые реально экономят время.
# Многоуровневая сортировка - сначала статус, потом имя
Get-Service | Sort-Object Status, Name | Format-Table Name, Status -AutoSize
# Обратная сортировка - топ процессов по CPU
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 Name, CPU, Id
# Сортировка по вычисляемому выражению
Get-ChildItem C:\Windows -File |
Sort-Object @{Expression={$_.Length / 1MB}; Descending=$true} |
Select-Object -First 10 Name,
@{Name="Size_MB"; Expression={[math]::Round($_.Length / 1MB, 2)}}
# Уникальные значения через Sort-Object -Unique
Get-EventLog -LogName System -Newest 100 |
Select-Object -ExpandProperty Source |
Sort-Object -Unique
Одна хитрость которую редко используют: Sort-Object можно передать массив хэш-таблиц для сложной сортировки с разными направлениями на разных полях.
# Сортировка: сначала по статусу по убыванию, потом по имени по возрастанию
Get-Service | Sort-Object @{Expression="Status"; Descending=$true}, @{Expression="Name"; Descending=$false} |
Select-Object -First 20 Name, Status | Format-Table -AutoSize
7. Measure-Object: статистика одной строкой
Раньше для подсчёта суммы нужно было писать цикл. Для среднего — ещё один. Measure-Object считает сумму, среднее, минимум, максимум и количество в одну строку.
# Простой подсчёт объектов
Get-Service | Measure-Object
# Статистика по памяти всех процессов
Get-Process | Measure-Object WorkingSet -Sum -Average -Minimum -Maximum
# Читаемый вывод
$mem = Get-Process | Measure-Object WorkingSet -Sum -Average
Write-Host "Всего RAM: $([math]::Round($mem.Sum / 1GB, 2)) GB"
Write-Host "Среднее на процесс: $([math]::Round($mem.Average / 1MB, 1)) MB"
# Подсчёт строк в файлах PowerShell
Get-ChildItem -Filter *.ps1 -Recurse | ForEach-Object {
$lines = Get-Content $_.FullName | Measure-Object -Line
[PSCustomObject]@{
File = $_.Name
Lines = $lines.Lines
Dir = $_.DirectoryName
}
} | Sort-Object Lines -Descending | Select-Object -First 10 | Format-Table -AutoSize
# Размер папки
$size = Get-ChildItem C:\Windows -Recurse -File -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum
Write-Host "C:\Windows: $([math]::Round($size.Sum / 1GB, 2)) GB"
Одна строка. Никаких $total = 0; foreach ($item in $items) { $total += $item.Length }. Это то что PowerShell делает лучше bash.
8. Compare-Object: детектив для конфигов и списков
Одна из самых недооценённых команд. Что изменилось на сервере за ночь? Какие службы есть на SRV01 но нет на SRV02? Какие файлы не скопировались в бэкап? Compare-Object отвечает.
# Сравнить два списка строк
$listA = @("alpha","beta","gamma","delta")
$listB = @("beta","gamma","epsilon","zeta")
Compare-Object $listA $listB
# есть только в listB
Индикатор <= означает «есть в первом списке, нет во втором». Индикатор => — наоборот.
# Что изменилось в службах за последние 5 минут
$before = Get-Service | Select-Object Name, Status
Start-Sleep -Seconds 300
$after = Get-Service | Select-Object Name, Status
$changes = Compare-Object $before $after -Property Name, Status
if ($changes) {
Write-Host "Изменения в службах:" -ForegroundColor Yellow
$changes | Format-Table
} else {
Write-Host "Изменений нет" -ForegroundColor Green
}
# Сравнить конфигурацию двух серверов
$srv1services = Invoke-Command -ComputerName SRV01 {
Get-Service | Select-Object Name, Status, StartType
}
$srv2services = Invoke-Command -ComputerName SRV02 {
Get-Service | Select-Object Name, Status, StartType
}
Compare-Object $srv1services $srv2services -Property Name, Status, StartType |
Format-Table -AutoSize
# Аудит: файлы которые есть в источнике но не в бэкапе
$source = Get-ChildItem D:\Data -Recurse -File | Select-Object Name, Length, LastWriteTime
$backup = Get-ChildItem E:\Backup -Recurse -File | Select-Object Name, Length, LastWriteTime
Compare-Object $source $backup -Property Name, Length |
Where-Object {$_.SideIndicator -eq "<="} |
Select-Object -ExpandProperty InputObject |
Export-Csv "missing_in_backup.csv" -NoTypeInformation
Последний пример - это реальный инструмент для проверки бэкапа. Запускаешь после каждого копирования и получаешь список того что не попало. Пять строк кода заменяют часы ручной сверки.
9. Export-Csv и Import-Csv: шлюз во внешний мир
PowerShell хорош, но результаты нужно куда-то девать. В Excel для руководства. В базу данных для хранения. Обратно в PowerShell через неделю для сравнения. Export-Csv и Import-Csv закрывают эту задачу.
# Экспорт с датой в имени файла
Get-Service |
Select-Object Name, Status, StartType, DisplayName |
Export-Csv -Path "services_$(Get-Date -Format 'yyyyMMdd_HHmm').csv" `
-NoTypeInformation `
-Encoding UTF8
# Import и анализ сохранённых данных
$saved = Import-Csv "services_20250501_1430.csv"
$current = Get-Service | Select-Object Name, Status
# Сравниваем то что было с тем что есть сейчас
Compare-Object $saved $current -Property Name, Status |
Format-Table -AutoSize
Параметр -NoTypeInformation убирает первую строку с типом объекта. Без него Excel видит мусор в первой строке. -Encoding UTF8 нужен если есть кириллица.
# Отчёт об ошибках за сутки с экспортом
$yesterday = (Get-Date).AddDays(-1)
Get-EventLog -LogName System -EntryType Error -After $yesterday |
Select-Object TimeGenerated, Source, EventID, Message |
Export-Csv "errors_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation -Encoding UTF8
Write-Host "Экспортировано в errors_$(Get-Date -Format 'yyyyMMdd').csv"
# Импорт и типизация данных
$report = Import-Csv "report.csv" | ForEach-Object {
[PSCustomObject]@{
Name = $_.Name
Date = [DateTime]::Parse($_.Date)
Value = [int]$_.Value
Size = [double]$_.Size
}
}
# Теперь можно фильтровать по типизированным полям
$report | Where-Object {$_.Date -gt (Get-Date).AddDays(-7)} |
Measure-Object Value -Sum -Average
Один момент который регулярно ломает импорт: CSV хранит всё как строки. После Import-Csv поле "100" - это строка "100", а не число. Если надо сортировать или считать - приводи типы явно, как в примере выше.
10. Get-Member: документация которая всегда с тобой
Получил объект. Не знаешь что с ним делать. Google, Stack Overflow, Microsoft Docs. Или можно спросить у самого объекта. Get-Member показывает все свойства и методы любого объекта PowerShell.
# Что умеет объект Get-Service
Get-Service | Get-Member
# Только свойства
Get-Process | Get-Member -MemberType Property
# Только методы
Get-Date | Get-Member -MemberType Method
# Найти по части имени
Get-ChildItem | Get-Member *time*
(Get-Service)[0] | Get-Member -Name *status*
Вывод показывает три вещи: тип (Property, Method, ScriptMethod), имя, и определение. В определении виден тип возвращаемого значения и параметры метода.
# Узнать методы строк
"hello world" | Get-Member -MemberType Method | Select-Object Name, Definition
# Найти метод для замены
"hello world" | Get-Member -MemberType Method | Where-Object Name -like "*replace*"
# Практика: что умеет объект даты
Get-Date | Get-Member -MemberType Method | Where-Object Name -like "*string*" |
Select-Object Name, Definition
# Результат подскажет что можно вызвать (Get-Date).ToString("dd.MM.yyyy")
$d = Get-Date
$d.ToString("dd.MM.yyyy HH:mm")
Вот для чего это реально нужно: ты получаешь объект из незнакомого командлета или модуля. Вместо того чтобы читать документацию - пропускаешь через Get-Member и за 30 секунд понимаешь что там внутри и как это использовать.
Реальные конвейеры: комбинируем всё вместе
Отдельные командлеты - это инструменты. Конвейер - это рабочий процесс. Вот три готовых скрипта которые решают реальные задачи.
Анализ нагрузки: топ процессов по CPU и памяти
# Топ-15 процессов, отсортированных по потреблению памяти
Get-Process |
Where-Object {$_.CPU -gt 0} |
Select-Object Name, Id,
@{Name="RAM_MB"; Expression={[math]::Round($_.WorkingSet / 1MB, 1)}},
@{Name="CPU_min"; Expression={[math]::Round($_.CPU / 60, 2)}},
@{Name="Threads"; Expression={$_.Threads.Count}} |
Sort-Object RAM_MB -Descending |
Select-Object -First 15 |
Format-Table -AutoSize
Мониторинг дисков: найти папки которые занимают больше всего места
# Анализ папок первого уровня в C:\
Get-ChildItem C:\ -Directory | ForEach-Object {
$size = Get-ChildItem $_.FullName -Recurse -File -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum
[PSCustomObject]@{
Folder = $_.Name
Size_GB = [math]::Round($size.Sum / 1GB, 2)
Files = $size.Count
}
} |
Sort-Object Size_GB -Descending |
Select-Object -First 10 |
Format-Table -AutoSize
Аудит служб: что запущено на двух серверах и чем они отличаются
# Сравнение состояния служб на двух серверах
$srv1 = Invoke-Command -ComputerName SRV01 {
Get-Service | Select-Object Name, Status
}
$srv2 = Invoke-Command -ComputerName SRV02 {
Get-Service | Select-Object Name, Status
}
$diff = Compare-Object $srv1 $srv2 -Property Name, Status
if ($diff) {
Write-Host "Различия между SRV01 и SRV02:" -ForegroundColor Yellow
$diff | Format-Table Name, Status, SideIndicator -AutoSize
$diff | Export-Csv "srv_diff_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
} else {
Write-Host "Серверы идентичны" -ForegroundColor Green
}
Полный отчёт об ошибках: анализ Event Log за сутки с экспортом
$since = (Get-Date).AddHours(-24)
$errors = Get-EventLog -LogName System -EntryType Error -After $since
Write-Host "Найдено ошибок за 24 часа: $($errors.Count)" -ForegroundColor Red
# Топ источников
$errors |
Group-Object Source |
Select-Object Count, Name |
Sort-Object Count -Descending |
Select-Object -First 10 |
Format-Table -AutoSize
# Экспорт полного списка
$errors |
Select-Object TimeGenerated, Source, EventID, Message |
Export-Csv "system_errors_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation -Encoding UTF8
Write-Host "Экспортировано в system_errors_$(Get-Date -Format 'yyyyMMdd').csv" -ForegroundColor Green
Troubleshooting: что идёт не так и как это починить
| Симптом | Причина | Решение |
|---|---|---|
| Where-Object ничего не возвращает хотя данные есть | Свойство содержит пробелы или другой тип данных чем ожидается | Проверь тип: $obj.Status.GetType(). Для enum сравнивай через [System.ServiceProcess.ServiceControllerStatus]::Running |
| Export-Csv создаёт кракозябры в Excel | Кодировка UTF-8 без BOM, Excel не определяет её автоматически | Используй -Encoding UTF8BOM (PS 7) или открывай через "Данные - Из текста" с указанием UTF-8 |
| ForEach-Object -Parallel не видит переменные из внешней области | Каждый поток - отдельный runspace, внешние переменные недоступны | Используй $using:varName внутри блока -Parallel |
| Compare-Object сравнивает объекты как равные хотя они разные | Не указан -Property, сравниваются ссылки а не значения | Всегда указывай -Property Name, Status явно |
| Get-EventLog не работает в PowerShell 7 | Get-EventLog удалён в PS 7, доступен только в Windows PowerShell 5.1 | Используй Get-WinEvent -LogName System - работает в обеих версиях |
| Measure-Object возвращает null для Sum | Нет объектов в конвейере или свойство содержит null | Добавь -ErrorAction SilentlyContinue и проверяй $result.Sum -ne $null |
| Sort-Object сортирует числа как строки (10 < 2) | Свойство имеет тип [string] а не [int] | Приведи тип: Sort-Object {[int]$_.Port} |
ForEach-Object -Parallel: как правильно передавать переменные
# НЕПРАВИЛЬНО - $threshold недоступен внутри потока
$threshold = 100MB
Get-Process | ForEach-Object -Parallel {
if ($_.WorkingSet -gt $threshold) { Write-Output $_.Name } # ошибка!
} -ThrottleLimit 5
# ПРАВИЛЬНО - используй $using:
$threshold = 100MB
Get-Process | ForEach-Object -Parallel {
if ($_.WorkingSet -gt $using:threshold) { Write-Output $_.Name }
} -ThrottleLimit 5
Get-WinEvent вместо Get-EventLog в PowerShell 7
# Аналог Get-EventLog -LogName System -EntryType Error -Newest 100
Get-WinEvent -LogName System -MaxEvents 100 |
Where-Object {$_.LevelDisplayName -eq "Error"} |
Select-Object TimeCreated, ProviderName, Id, Message |
Format-Table -AutoSize -Wrap
Альтернативы: когда командлеты не лучший выбор
PowerShell хорош для работы с объектами Windows. Но есть задачи где другие инструменты быстрее.
| Задача | PowerShell | Альтернатива | Когда использовать альтернативу |
|---|---|---|---|
| Парсинг больших текстовых логов | Get-Content | Select-String | grep (Linux), findstr (Windows) | Файлы больше 1 ГБ: Select-String читает весь файл в память |
| Работа с REST API | Invoke-RestMethod | curl, Python requests | Сложная аутентификация OAuth2/JWT, работа с потоковыми ответами |
| Управление AD | Get-ADUser, Set-ADUser | LDAP напрямую, Python ldap3 | Массовые операции 10000+ объектов: модуль AD добавляет заметный overhead |
| Работа с Excel | Export-Csv + ImportExcel модуль | Python openpyxl, pandas | Сложное форматирование, формулы, сводные таблицы |
Модуль ImportExcel стоит установить отдельно: он позволяет создавать настоящие Excel-файлы с форматированием прямо из PowerShell, без установки Excel на сервере.
# Установка ImportExcel
Install-Module ImportExcel -Scope CurrentUser
# Экспорт прямо в .xlsx с автофильтром
Get-Service |
Select-Object Name, Status, StartType, DisplayName |
Export-Excel "services_report.xlsx" -AutoSize -AutoFilter -FreezeTopRow
Лучшие практики: пиши скрипты которые работают не только у тебя
Одна строка в скрипте без комментария может стоить часа работы через год. Это не метафора - это воспоминание.
# Плохо: что делает этот конвейер?
Get-Service | Where-Object {$_.Status -eq 4 -and $_.StartType -ne 2} | ForEach-Object { $_.Stop() }
# Хорошо: сразу понятно что происходит
# Остановить все автоматические службы которые сейчас работают
# Status 4 = Running, StartType 2 = Automatic
Get-Service |
Where-Object {$_.Status -eq "Running" -and $_.StartType -eq "Automatic"} |
ForEach-Object {
Write-Host "Останавливаю: $($_.Name)"
$_.Stop()
}
Несколько правил которые экономят нервы:
- Всегда указывай
-ErrorAction SilentlyContinueдля команд которые могут вернуть ошибку при отсутствии объектов. Без него один пустой результат ломает весь конвейер. - Добавляй
-WhatIfперед деструктивными операциями (Stop-Service, Remove-Item). Посмотри что будет сделано, потом делай. - Логируй длинные скрипты через
Start-Transcript/Stop-Transcript. После инцидента будешь рад что есть лог. - Разбивай длинные конвейеры на именованные переменные.
$runningServices = Get-Service | Where-Object...читается лучше чем 8 пайпов в одну строку.
# Логирование скрипта
Start-Transcript -Path "C:\Logs\maintenance_$(Get-Date -Format 'yyyyMMdd_HHmm').log"
try {
# Основная логика
Write-Host "Начало обслуживания: $(Get-Date)"
$services = Get-Service | Where-Object {$_.Status -eq "Stopped" -and $_.StartType -eq "Automatic"}
Write-Host "Автоматических служб в статусе Stopped: $($services.Count)"
$services | ForEach-Object {
Write-Host "Запускаю: $($_.Name)"
Start-Service $_.Name -ErrorAction SilentlyContinue
}
Write-Host "Завершено: $(Get-Date)"
} catch {
Write-Host "Ошибка: $($_.Exception.Message)" -ForegroundColor Red
} finally {
Stop-Transcript
}
FAQ: вопросы которые задают чаще всего
Чем PowerShell отличается от CMD и когда использовать что?
CMD работает с текстом и запускает программы. PowerShell работает с .NET объектами. Это принципиальное различие: в CMD ты парсишь строки регулярками, в PowerShell работаешь с типизированными свойствами напрямую. CMD быстрее запускается и достаточен для простых задач: скопировать файл, запустить программу, проверить переменную среды. PowerShell нужен как только задача выходит за рамки одной команды: фильтрация, агрегация, работа с AD, сравнение данных, автоматизация.
Почему ForEach-Object работает медленно на больших объёмах данных?
Стандартный ForEach-Object - однопоточный. На 10000 объектов это заметно. Первое решение - ForEach-Object -Parallel в PowerShell 7: реальная многопоточность с настраиваемым пулом потоков через -ThrottleLimit. Второе решение: если объекты независимы и задача IO-bound (сетевые запросы, дисковые операции) - -Parallel даёт линейный прирост пропорционально числу потоков. Для CPU-bound задач ThrottleLimit не должен превышать число ядер.
Как сохранить результаты PowerShell чтобы посмотреть потом?
Три варианта в зависимости от задачи. Export-Csv - для табличных данных которые нужно открыть в Excel или загрузить в базу. Export-Clixml - для сохранения объектов с полной структурой, потом можно импортировать и работать с ними как с живыми объектами. ConvertTo-Json | Out-File - для передачи между системами или хранения в виде JSON. Для логов используй Start-Transcript: пишет всё что происходит в консоли в текстовый файл.
Как запустить PowerShell скрипт если пишет про политику выполнения?
По умолчанию в Windows запуск .ps1 скриптов ограничен. Проверь текущую политику: Get-ExecutionPolicy. Для разработки и администрирования на рабочей станции: Set-ExecutionPolicy RemoteSigned -Scope CurrentUser. Это разрешает запуск локальных скриптов и скриптов с цифровой подписью из сети. Политику Unrestricted на серверах не ставь - это отключает все проверки. Для одноразового запуска без изменения политики: powershell.exe -ExecutionPolicy Bypass -File script.ps1.
Как ускорить работу с Active Directory через PowerShell?
Главное правило: фильтруй на стороне AD, не локально. Get-ADUser -Filter {Department -eq "IT"} передаёт фильтр в LDAP-запрос и возвращает только нужных пользователей. Get-ADUser -Filter * | Where-Object {$_.Department -eq "IT"} тянет всех пользователей домена и фильтрует локально. На 50000 пользователей разница в скорости - в 10-20 раз. Дополнительно: используй параметр -Properties с явным перечислением нужных атрибутов. Без него возвращается только базовый набор, но с полным набором тянется лишнее.
Что делать если командлет не найден но модуль установлен?
Сначала проверь: Get-Module -ListAvailable *ModuleName*. Если модуль есть - импортируй явно: Import-Module ModuleName. Если ошибка про политику выполнения - смотри пункт выше. Если модуль установлен для другого пользователя или в другой области видимости (AllUsers vs CurrentUser) - запусти PowerShell с правами администратора или переустанови с нужным -Scope. Для модулей Az: Connect-AzAccount нужно вызывать до использования командлетов ресурсов.
Что дальше
Если ты дочитал до этого места - у тебя теперь есть инструменты для 90% задач автоматизации Windows. Get-Command чтобы найти нужный командлет. Where-Object и Select-Object чтобы работать с данными. ForEach-Object чтобы обработать каждый элемент, в том числе параллельно. Compare-Object для аудита. Export-Csv чтобы вынести результат наружу.
Следующий шаг - это функции и модули. Возьми конвейер который ты написал для анализа дисков или сравнения служб - оберни в функцию с параметрами. Добавь [CmdletBinding()] и [Parameter(Mandatory)]. Сохрани в .psm1 файл. Теперь это твой инструмент который работает на любом сервере куда ты скопировал папку с модулем. Именно так делается нормальная автоматизация.
Оставайтесь на связи
Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.
Подписаться на IT-Аптеку →


