Командлеты PowerShell: 10 команд которые ты будешь использовать каждый день

командлеты PowerShell
Быстрый ответ: какие командлеты PowerShell нужны в первую очередь
Самые полезные командлеты PowerShell для ежедневной работы:
  • 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. Он передаёт объекты. Это меняет всё.

%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#ffffff',
    'primaryTextColor': '#1e293b',
    'primaryBorderColor': '#94a3b8',
    'lineColor': '#64748b',
    'fontSize': '15px',
    'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
  },
  'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart LR
    A["Get-Service\nПолучить объекты"] --> B["Where-Object\nОтфильтровать"]
    B --> C["Select-Object\nВыбрать поля"]
    C --> D["Sort-Object\nОтсортировать"]
    D --> E["Export-Csv\nСохранить"]
    style A fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style B fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#c2410c
    style C fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style D fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style E fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d

Каждый командлет получает полноценный .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
Запомни про Get-Command
Get-Command ищет по всем установленным модулям, включая те которые ещё не импортированы. PowerShell импортирует нужный модуль автоматически при первом вызове командлета.

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"
Where-Object и производительность
Where-Object фильтрует объекты уже после получения. Если командлет поддерживает параметр -Filter — используй его. Get-ADUser -Filter {Enabled -eq $true} работает на стороне AD. Get-ADUser | Where-Object {$_.Enabled} тянет всех пользователей и потом фильтрует локально. Разница на 10000 пользователей — ощутимая.

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
ThrottleLimit - не ставь слишком высоко
ThrottleLimit определяет сколько потоков работают одновременно. Значение 5 — по умолчанию. 20-50 — нормально для сетевых задач. Не ставь 200+: создание потока стоит ресурсов, и на локальных задачах прирост исчезает. Для CPU-bound задач ThrottleLimit лучше держать равным количеству ядер.

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

Последний пример - это реальный инструмент для проверки бэкапа. Запускаешь после каждого копирования и получаешь список того что не попало. Пять строк кода заменяют часы ручной сверки.

Важно про Compare-Object и регистр
По умолчанию сравнение регистронезависимое. Если нужно различать «Server01» и «server01» — добавь параметр -CaseSensitive.

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-Аптеку →

Мы ВКонтакте

IT-Аптека — советы, новости и помощь рядом.

Вступить в группу ВКонтакте →
Поделитесь:

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх