"Быстрый
<br />
Самые полезные командлеты PowerShell для ежедневной работы:</p>
<ul>
<li><strong>Get-Command</strong> — найти любой командлет по маске, не вспоминая точное название</li>
<li><strong>Select-Object</strong> — выбрать нужные поля, создать вычисляемые свойства на лету</li>
<li><strong>Where-Object</strong> — фильтровать любой поток данных по условию</li>
<li><strong>ForEach-Object</strong> — обработать каждый элемент, в PowerShell 7 — параллельно</li>
<li><strong>Group-Object</strong> — агрегация и аналитика без Excel за 30 секунд</li>
<li><strong>Measure-Object</strong> — сумма, среднее, минимум, максимум в одну строку</li>
<li><strong>Compare-Object</strong> — сравнить конфиги, списки пользователей, состояние служб</li>
<li><strong>Export-Csv / Import-Csv</strong> — выгрузить результат в файл, загрузить обратно</li>
<li><strong>Get-Member</strong> — узнать что умеет любой объект, не открывая документацию</li>
<li><strong>Sort-Object</strong> — отсортировать по любому полю, включая вычисляемое</li>
</ul>
<p>Всё это работает в конвейере. Комбинируй — и 90% рутины закрывается в 5-10 строк.<br />
<h2>Зачем вообще разбираться в командлетах PowerShell глубже чем Get-Help</h2>
<p>Командлеты <a href="https://it-apteka.com/aktivacija-windows-11-i-office-cherez-powershell-komandy-skripty-diagnostika/" title="Активация Windows 11 и Office через PowerShell: команды, скрипты, диагностика" target="_blank" rel="noopener" data-wpil-monitor-id="2616">PowerShell — это не просто команды</a>. Это объектный конвейер, где каждый шаг получает структурированные данные и передаёт их дальше. Знакомо? Ты открываешь PowerShell, набираешь <code>Get-Service</code>, смотришь на стену текста — и закрываешь. GUI быстрее. Понятнее. Привычнее.</p>
<p>Это продолжается до первого раза когда тебе нужно сравнить состояние служб на 50 серверах. Или найти все процессы которые жрут больше 500 МБ памяти. Или выгрузить в CSV список пользователей с их последним логином за последние 90 дней. Тут GUI внезапно становится очень медленным.</p>
<p>Что получишь в этой статье: 10 командлетов с реальными примерами из продакшна, объяснением зачем нужен каждый шаг, и комбинированными конвейерами которые можно копировать сразу. Времени займёт 20-30 минут. На выходе — шпаргалка которая останется открытой в браузере.</p>
<p>Что нужно: PowerShell 5.1 (встроен в <a class="wpil_keyword_link" href="https://it-apteka.com/category/windows-server/" target="_blank" rel="noopener" title="Windows Server" data-wpil-keyword-link="linked" data-wpil-monitor-id="2612">Windows</a> 10/11/Server 2019+) или PowerShell 7.x для параллельных вычислений и новых операторов. Права администратора для части примеров с процессами и службами.</p>
<table>
<thead>
<tr>
<th>Версия</th>
<th>Платформа</th>
<th>Ключевые отличия</th>
</tr>
</thead>
<tbody>
<tr>
<td>Windows PowerShell 5.1</td>
<td>Windows только</td>
<td>Встроен, совместим со всеми модулями Windows</td>
</tr>
<tr>
<td>PowerShell 7.4 LTS</td>
<td>Windows, <a class="wpil_keyword_link" href="https://it-apteka.com/category/linux/" target="_blank" rel="noopener" title="Linux" data-wpil-keyword-link="linked" data-wpil-monitor-id="2611">Linux</a>, macOS</td>
<td>ForEach-Object -Parallel, тернарный оператор, быстрее</td>
</tr>
<tr>
<td>PowerShell 7.5+</td>
<td>Windows, Linux, macOS</td>
<td>ConvertTo-CliXml, актуальные исправления веб-командлетов</td>
</tr>
</tbody>
</table>
<p>На момент публикации актуальна версия PowerShell 7.5. Перед установкой проверь свежие релизы на <a href="https://github.com/PowerShell/PowerShell/releases" target="_blank" rel="nofollow noopener noreferrer">github.com/PowerShell/PowerShell/releases</a>.</p>
<p><!-- ============================================================ --><br />
<!-- Архитектура конвейера --><br />
<!-- ============================================================ --></p>
<h2>Как работает конвейер PowerShell — схема за 2 минуты</h2>
<p>Прежде чем разбирать командлеты по одному — вот как они взаимодействуют. PowerShell не передаёт текст между командами как <a class="wpil_keyword_link" href="https://it-apteka.com/tag/bash/" target="_blank" rel="noopener" title="Bash" data-wpil-keyword-link="linked" data-wpil-monitor-id="2609">bash</a>. Он передаёт объекты. Это меняет всё.</p>
<pre class="mermaid">
%%{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
</pre>
<p>Каждый командлет получает полноценный .NET объект со всеми свойствами и методами. Не строку которую надо парсить регулярками, а структуру. <code>Where-Object</code> фильтрует по реальным свойствам объекта. <code>Select-Object</code> выбирает нужные поля. <code>Export-Csv</code> сохраняет результат.</p>
<p><!-- ============================================================ --><br />
<!-- 2. КОМАНДЛЕТЫ - основной блок --><br />
<!-- ============================================================ --></p>
<h2>1. Get-Command: найти командлет не зная его имени</h2>
<p>Забыл название команды. Помнишь только что она связана с сетью или с пользователями. Лезть в Google? Не надо.</p>
<pre><code class="language-powershell">
# Поиск по маске в имени
Get-Command *service*
# Поиск всех командлетов в конкретном модуле
Get-Command -Module NetTCPIP
# Только командлеты, не функции и не алиасы
Get-Command -CommandType Cmdlet *net*
# Посмотреть параметры конкретной команды
Get-Command Get-Service | Select-Object -ExpandProperty Parameters
</code></pre>
<p>Почему это работает лучше Google: результат актуален для твоей версии PowerShell и установленных модулей. Установил модуль Az для Azure — все его командлеты сразу видны через <code>Get-Command -Module Az.*</code>. Установил ExchangeOnline — аналогично.</p>
<pre><code class="language-powershell">
# Найти все командлеты для работы с Azure виртуальными машинами
Get-Command -Module Az.Compute *VM*
# Сколько командлетов в конкретном модуле
Get-Command -Module ActiveDirectory | Measure-Object
</code></pre>
"Запомни
<br />
Get-Command ищет по всем установленным модулям, включая те которые ещё не импортированы. PowerShell импортирует нужный модуль автоматически при первом вызове командлета.<br />
<p><!-- ============================================================ --></p>
<h2>2. Select-Object: выбрать поля и создать новые прямо в конвейере</h2>
<p>Большинство используют <code>Select-Object</code> только чтобы выбрать несколько колонок. Это как использовать швейцарский нож только чтобы открыть бутылку.</p>
<p>Самая сильная фича — вычисляемые свойства. Создаёшь новое поле прямо в конвейере, без промежуточных переменных.</p>
<pre><code class="language-powershell">
# Базовый выбор полей
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
</code></pre>
<p>Вычисляемые свойства работают везде где принимается объект: в <code>Sort-Object</code>, <code>Group-Object</code>, <code>Format-Table</code>, <code>Export-Csv</code>. Один раз написал — пропускаешь через весь конвейер.</p>
<pre><code class="language-powershell">
# Практический пример: топ-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
</code></pre>
<p><!-- ============================================================ --></p>
<h2>3. Where-Object: фильтрация с человеческим лицом</h2>
<p>Фильтрация — это 80% работы с данными в PowerShell. <code>Where-Object</code> фильтрует объекты в конвейере по любому условию. В PowerShell 7 синтаксис стал проще.</p>
<pre><code class="language-powershell">
# Классика - работает в 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
</code></pre>
<p>Операторы сравнения которые используются чаще всего:</p>
<table>
<thead>
<tr>
<th>Оператор</th>
<th>Значение</th>
<th>Пример</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-eq</code></td>
<td>Равно</td>
<td><code>Status -eq "Running"</code></td>
</tr>
<tr>
<td><code>-ne</code></td>
<td>Не равно</td>
<td><code>Status -ne "Stopped"</code></td>
</tr>
<tr>
<td><code>-gt / -lt</code></td>
<td>Больше / меньше</td>
<td><code>CPU -gt 50</code></td>
</tr>
<tr>
<td><code>-like</code></td>
<td>Маска с * и ?</td>
<td><code>Name -like "*sql*"</code></td>
</tr>
<tr>
<td><code>-match</code></td>
<td>Регулярное выражение</td>
<td><code>Name -match "^sql\d+"</code></td>
</tr>
<tr>
<td><code>-in</code></td>
<td>Входит в список</td>
<td><code>Name -in $list</code></td>
</tr>
<tr>
<td><code>-contains</code></td>
<td>Список содержит значение</td>
<td><code>$list -contains "value"</code></td>
</tr>
</tbody>
</table>
"Where-Object
<br />
Where-Object фильтрует объекты уже после получения. Если командлет поддерживает параметр -Filter — используй его. Get-ADUser -Filter {Enabled -eq $true} работает на стороне AD. Get-ADUser | Where-Object {$_.Enabled} тянет всех пользователей и потом фильтрует локально. Разница на 10000 пользователей — ощутимая.<br />
<p><!-- ============================================================ --></p>
<h2>4. ForEach-Object: обработать каждый элемент, и сделать это быстро</h2>
<p>Цикл <code>foreach</code> в PowerShell хорош. <code>ForEach-Object</code> в конвейере — лучше. Он не ждёт пока предыдущий командлет отдаст все объекты: обрабатывает по одному по мере поступления. На больших выборках это заметно.</p>
<pre><code class="language-powershell">
# Базовый перебор с форматированием
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
</code></pre>
<p>А теперь самое интересное. PowerShell 7 добавил параметр <code>-Parallel</code>. Это не просто синтаксический сахар — это реальная многопоточность.</p>
<pre><code class="language-powershell">
# Параллельный пинг 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
</code></pre>
<p>Без <code>-Parallel</code> этот <a class="wpil_keyword_link" href="https://it-apteka.com/category/scripts/" target="_blank" rel="noopener" title="Скрипты" data-wpil-keyword-link="linked" data-wpil-monitor-id="2613">скрипт</a> пингует хосты последовательно. 10 хостов с таймаутом 1 секунда — это 10+ секунд. С <code>-Parallel -ThrottleLimit 10</code> — все за 1-2 секунды. На 100 хостах разница становится принципиальной.</p>
<pre><code class="language-powershell">
# Проверка свободного места на удалённых серверах - параллельно
$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
</code></pre>
"ThrottleLimit
<br />
ThrottleLimit определяет сколько потоков работают одновременно. Значение 5 — по умолчанию. 20-50 — нормально для сетевых задач. Не ставь 200+: создание потока стоит ресурсов, и на локальных задачах прирост исчезает. Для CPU-bound задач ThrottleLimit лучше держать равным количеству ядер.<br />
<p><!-- ============================================================ --></p>
<h2>5. Group-Object: агрегация без Excel</h2>
<p>Нужно понять закономерность в данных? Сколько служб в каком статусе? Какие источники ошибок самые активные? Кто из пользователей чаще всего падает в Event Log? <code>Group-Object</code> отвечает за секунды.</p>
<pre><code class="language-powershell">
# Распределение служб по статусу
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
</code></pre>
<p>Группировка по вычисляемому выражению — это мощь которую не все знают. Можешь группировать по первой букве, по диапазону значений, по дате без времени.</p>
<pre><code class="language-powershell">
# Группировка событий по дате (без времени)
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
</code></pre>
<p><!-- ============================================================ --></p>
<h2>6. Sort-Object: сортировка по чему угодно</h2>
<p>Казалось бы — что тут сложного? Но у <code>Sort-Object</code> есть несколько приёмов которые реально экономят время.</p>
<pre><code class="language-powershell">
# Многоуровневая сортировка - сначала статус, потом имя
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
</code></pre>
<p>Одна хитрость которую редко используют: <code>Sort-Object</code> можно передать массив хэш-таблиц для сложной сортировки с разными направлениями на разных полях.</p>
<pre><code class="language-powershell">
# Сортировка: сначала по статусу по убыванию, потом по имени по возрастанию
Get-Service | Sort-Object @{Expression="Status"; Descending=$true}, @{Expression="Name"; Descending=$false} |
Select-Object -First 20 Name, Status | Format-Table -AutoSize
</code></pre>
<p><!-- ============================================================ --></p>
<h2>7. Measure-Object: статистика одной строкой</h2>
<p>Раньше для подсчёта суммы нужно было писать цикл. Для среднего — ещё один. <code>Measure-Object</code> считает сумму, среднее, минимум, максимум и количество в одну строку.</p>
<pre><code class="language-powershell">
# Простой подсчёт объектов
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"
</code></pre>
<p>Одна строка. Никаких <code>$total = 0; foreach ($item in $items) { $total += $item.Length }</code>. Это то что PowerShell делает лучше bash.</p>
<p><!-- ============================================================ --></p>
<h2>8. Compare-Object: детектив для конфигов и списков</h2>
<p>Одна из самых недооценённых команд. Что изменилось на сервере за ночь? Какие службы есть на SRV01 но нет на SRV02? Какие файлы не скопировались в бэкап? <code>Compare-Object</code> отвечает.</p>
<pre><code class="language-powershell">
# Сравнить два списка строк
$listA = @("alpha","beta","gamma","delta")
$listB = @("beta","gamma","epsilon","zeta")
Compare-Object $listA $listB
# <= есть только в listA
# => есть только в listB
</code></pre>
<p>Индикатор <code><=</code> означает «есть в первом списке, нет во втором». Индикатор <code>=></code> — наоборот.</p>
<pre><code class="language-powershell">
# Что изменилось в службах за последние 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
</code></pre>
<p>Последний <a href="https://it-apteka.com/rukovodstvo-po-optimizacii-postgresql-i-mysql-5-realnyh-primerov-s-gotovymi-skriptami/" title="Руководство по оптимизации PostgreSQL и MySQL: 5 реальных примеров с готовыми скриптами" target="_blank" rel="noopener" data-wpil-monitor-id="2615">пример - это реальный</a> инструмент для проверки бэкапа. Запускаешь после каждого копирования и получаешь список того что не попало. Пять строк кода заменяют часы ручной сверки.</p>
"Важно
<br />
По умолчанию сравнение регистронезависимое. Если нужно различать "Server01" и "server01" — добавь параметр -CaseSensitive.<br />
<p><!-- ============================================================ --></p>
<h2>9. Export-Csv и Import-Csv: шлюз во внешний мир</h2>
<p>PowerShell хорош, но результаты нужно куда-то девать. В Excel для руководства. В базу данных для хранения. Обратно в PowerShell через неделю для сравнения. <code>Export-Csv</code> и <code>Import-Csv</code> закрывают эту задачу.</p>
<pre><code class="language-powershell">
# Экспорт с датой в имени файла
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
</code></pre>
<p>Параметр <code>-NoTypeInformation</code> убирает первую строку с типом объекта. Без него Excel видит мусор в первой строке. <code>-Encoding UTF8</code> нужен если есть кириллица.</p>
<pre><code class="language-powershell">
# Отчёт об ошибках за сутки с экспортом
$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
</code></pre>
<p>Один момент который регулярно ломает импорт: CSV хранит всё как строки. После <code>Import-Csv</code> поле "100" - это строка "100", а не число. Если надо сортировать или считать - приводи типы явно, как в примере выше.</p>
<p><!-- ============================================================ --></p>
<h2>10. Get-Member: документация которая всегда с тобой</h2>
<p>Получил объект. Не знаешь что с ним делать. Google, Stack Overflow, Microsoft Docs. Или можно спросить у самого объекта. <code>Get-Member</code> показывает все свойства и методы любого объекта PowerShell.</p>
<pre><code class="language-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*
</code></pre>
<p>Вывод показывает три вещи: тип (Property, Method, ScriptMethod), имя, и определение. В определении виден тип возвращаемого значения и параметры метода.</p>
<pre><code class="language-powershell">
# Узнать методы строк
"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")
</code></pre>
<p>Вот для чего это реально нужно: ты получаешь объект из незнакомого командлета или модуля. Вместо того чтобы читать документацию - пропускаешь через <code>Get-Member</code> и за 30 секунд понимаешь что там внутри и как это использовать.</p>
<p><!-- ============================================================ --><br />
<!-- 3. ПРОВЕРКА И КОМБИНАЦИИ --><br />
<!-- ============================================================ --></p>
<h2>Реальные конвейеры: комбинируем всё вместе</h2>
<p>Отдельные командлеты - это инструменты. Конвейер - это рабочий процесс. Вот три готовых скрипта которые решают реальные задачи.</p>
<h3>Анализ нагрузки: топ процессов по CPU и памяти</h3>
<pre><code class="language-powershell">
# Топ-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
</code></pre>
<h3>Мониторинг дисков: найти папки которые занимают больше всего места</h3>
<pre><code class="language-powershell">
# Анализ папок первого уровня в 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
</code></pre>
<h3>Аудит служб: что запущено на двух серверах и чем они отличаются</h3>
<pre><code class="language-powershell">
# Сравнение состояния служб на двух серверах
$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
}
</code></pre>
<h3>Полный отчёт об ошибках: анализ Event Log за сутки с экспортом</h3>
<pre><code class="language-powershell">
$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
</code></pre>
<p><!-- ============================================================ --><br />
<!-- 4. ОСЛОЖНЕНИЯ / TROUBLESHOOTING --><br />
<!-- ============================================================ --></p>
<h2>Troubleshooting: что идёт не так и как это починить</h2>
<table>
<thead>
<tr>
<th>Симптом</th>
<th>Причина</th>
<th>Решение</th>
</tr>
</thead>
<tbody>
<tr>
<td>Where-Object ничего не возвращает хотя данные есть</td>
<td>Свойство содержит пробелы или другой тип данных чем ожидается</td>
<td>Проверь тип: <code>$obj.Status.GetType()</code>. Для enum сравнивай через <code>[System.ServiceProcess.ServiceControllerStatus]::Running</code></td>
</tr>
<tr>
<td>Export-Csv создаёт кракозябры в Excel</td>
<td>Кодировка UTF-8 без BOM, Excel не определяет её автоматически</td>
<td>Используй <code>-Encoding UTF8BOM</code> (PS 7) или открывай через "Данные - Из текста" с указанием UTF-8</td>
</tr>
<tr>
<td>ForEach-Object -Parallel не видит переменные из внешней области</td>
<td>Каждый поток - отдельный runspace, внешние переменные недоступны</td>
<td>Используй <code>$using:varName</code> внутри блока -Parallel</td>
</tr>
<tr>
<td>Compare-Object сравнивает объекты как равные хотя они разные</td>
<td>Не указан -Property, сравниваются ссылки а не значения</td>
<td>Всегда указывай <code>-Property Name, Status</code> явно</td>
</tr>
<tr>
<td>Get-EventLog не работает в PowerShell 7</td>
<td>Get-EventLog удалён в PS 7, доступен только в Windows PowerShell 5.1</td>
<td>Используй <code>Get-WinEvent -LogName System</code> - работает в обеих версиях</td>
</tr>
<tr>
<td>Measure-Object возвращает null для Sum</td>
<td>Нет объектов в конвейере или свойство содержит null</td>
<td>Добавь <code>-ErrorAction SilentlyContinue</code> и проверяй <code>$result.Sum -ne $null</code></td>
</tr>
<tr>
<td>Sort-Object сортирует числа как строки (10 < 2)</td>
<td>Свойство имеет тип [string] а не [int]</td>
<td>Приведи тип: <code>Sort-Object {[int]$_.Port}</code></td>
</tr>
</tbody>
</table>
<h3>ForEach-Object -Parallel: как правильно передавать переменные</h3>
<pre><code class="language-powershell">
# НЕПРАВИЛЬНО - $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
</code></pre>
<h3>Get-WinEvent вместо Get-EventLog в PowerShell 7</h3>
<pre><code class="language-powershell">
# Аналог 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
</code></pre>
<p><!-- ============================================================ --><br />
<!-- 5. АЛЬТЕРНАТИВЫ --><br />
<!-- ============================================================ --></p>
<h2>Альтернативы: когда командлеты не лучший выбор</h2>
<p><a href="https://it-apteka.com/powershell-skripty-v-windows-kak-sozdat-zapustit-i-avtomatizirovat-vypolnenie/" title="PowerShell скрипты в Windows: как создать, запустить и автоматизировать выполнение" target="_blank" rel="noopener" data-wpil-monitor-id="2617">PowerShell хорош для работы с объектами Windows</a>. Но есть задачи где другие инструменты быстрее.</p>
<table>
<thead>
<tr>
<th>Задача</th>
<th>PowerShell</th>
<th>Альтернатива</th>
<th>Когда использовать альтернативу</th>
</tr>
</thead>
<tbody>
<tr>
<td>Парсинг больших текстовых логов</td>
<td>Get-Content | Select-String</td>
<td>grep (Linux), findstr (Windows)</td>
<td>Файлы больше 1 ГБ: Select-String читает весь файл в память</td>
</tr>
<tr>
<td>Работа с REST API</td>
<td>Invoke-RestMethod</td>
<td>curl, Python requests</td>
<td>Сложная аутентификация OAuth2/JWT, работа с потоковыми ответами</td>
</tr>
<tr>
<td>Управление AD</td>
<td>Get-ADUser, Set-ADUser</td>
<td>LDAP напрямую, Python ldap3</td>
<td>Массовые операции 10000+ объектов: модуль AD добавляет заметный overhead</td>
</tr>
<tr>
<td>Работа с Excel</td>
<td>Export-Csv + ImportExcel модуль</td>
<td>Python openpyxl, pandas</td>
<td>Сложное форматирование, формулы, сводные таблицы</td>
</tr>
</tbody>
</table>
<p>Модуль <a href="https://github.com/dfinke/ImportExcel" target="_blank" rel="nofollow noopener noreferrer">ImportExcel</a> стоит установить отдельно: он позволяет создавать настоящие Excel-файлы с форматированием прямо из PowerShell, без установки Excel на сервере.</p>
<pre><code class="language-powershell">
# Установка ImportExcel
Install-Module ImportExcel -Scope CurrentUser
# Экспорт прямо в .xlsx с автофильтром
Get-Service |
Select-Object Name, Status, StartType, DisplayName |
Export-Excel "services_report.xlsx" -AutoSize -AutoFilter -FreezeTopRow
</code></pre>
<p><!-- ============================================================ --><br />
<!-- 6. ПРОФИЛАКТИКА И ЛУЧШИЕ ПРАКТИКИ --><br />
<!-- ============================================================ --></p>
<h2>Лучшие практики: пиши скрипты которые работают не только у тебя</h2>
<p>Одна строка в скрипте без комментария может стоить часа работы через год. Это не метафора - это воспоминание.</p>
<pre><code class="language-powershell">
# Плохо: что делает этот конвейер?
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()
}
</code></pre>
<p>Несколько правил которые экономят нервы:</p>
<ul>
<li>Всегда указывай <code>-ErrorAction SilentlyContinue</code> для команд которые могут вернуть ошибку при отсутствии объектов. Без него один пустой результат ломает весь конвейер.</li>
<li>Добавляй <code>-WhatIf</code> перед деструктивными операциями (Stop-Service, Remove-Item). Посмотри что будет сделано, потом делай.</li>
<li>Логируй длинные скрипты через <code>Start-Transcript</code> / <code>Stop-Transcript</code>. После инцидента будешь рад что есть лог.</li>
<li>Разбивай длинные конвейеры на именованные переменные. <code>$runningServices = Get-Service | Where-Object...</code> читается лучше чем 8 пайпов в одну строку.</li>
</ul>
<pre><code class="language-powershell">
# Логирование скрипта
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
}
</code></pre>
<p><!-- ============================================================ --><br />
<!-- 7. FAQ --><br />
<!-- ============================================================ --></p>
<h2>FAQ: вопросы которые задают чаще всего</h2>
<h3>Чем PowerShell отличается от CMD и когда использовать что?</h3>
<p>CMD работает с текстом и запускает программы. PowerShell работает с .NET объектами. Это принципиальное различие: в CMD ты парсишь строки регулярками, в PowerShell работаешь с типизированными свойствами напрямую. CMD быстрее запускается и достаточен для простых задач: скопировать файл, запустить программу, проверить переменную среды. PowerShell нужен как только задача выходит за рамки одной команды: фильтрация, агрегация, работа с AD, сравнение данных, автоматизация.</p>
<h3>Почему ForEach-Object работает медленно на больших объёмах данных?</h3>
<p>Стандартный <code>ForEach-Object</code> - однопоточный. На 10000 объектов это заметно. Первое решение - <code>ForEach-Object -Parallel</code> в PowerShell 7: реальная многопоточность с настраиваемым пулом потоков через <code>-ThrottleLimit</code>. Второе решение: если объекты независимы и задача IO-bound (сетевые запросы, дисковые операции) - <code>-Parallel</code> даёт линейный прирост пропорционально числу потоков. Для CPU-bound задач ThrottleLimit не должен превышать число ядер.</p>
<h3>Как сохранить результаты PowerShell чтобы посмотреть потом?</h3>
<p>Три варианта в зависимости от задачи. <code>Export-Csv</code> - для табличных данных которые нужно открыть в Excel или загрузить в базу. <code>Export-Clixml</code> - для сохранения объектов с полной структурой, потом можно импортировать и работать с ними как с живыми объектами. <code>ConvertTo-Json | Out-File</code> - для передачи между системами или хранения в виде JSON. Для логов используй <code>Start-Transcript</code>: пишет всё что происходит в консоли в текстовый файл.</p>
<h3>Как запустить PowerShell скрипт если пишет про политику выполнения?</h3>
<p>По умолчанию в Windows запуск .<a class="wpil_keyword_link" href="https://it-apteka.com/tag/powershell/" target="_blank" rel="noopener" title="PowerShell" data-wpil-keyword-link="linked" data-wpil-monitor-id="2614">ps1</a> скриптов ограничен. Проверь текущую политику: <code>Get-ExecutionPolicy</code>. Для разработки и администрирования на рабочей станции: <code>Set-ExecutionPolicy RemoteSigned -Scope CurrentUser</code>. Это разрешает запуск локальных скриптов и скриптов с цифровой подписью из <a class="wpil_keyword_link" href="https://it-apteka.com/category/networks/" target="_blank" rel="noopener" title="Сети" data-wpil-keyword-link="linked" data-wpil-monitor-id="2610">сети</a>. Политику <code>Unrestricted</code> на серверах не ставь - это отключает все проверки. Для одноразового запуска без изменения политики: <code>powershell.exe -ExecutionPolicy Bypass -File script.ps1</code>.</p>
<h3>Как ускорить работу с Active Directory через PowerShell?</h3>
<p>Главное правило: фильтруй на стороне AD, не локально. <code>Get-ADUser -Filter {Department -eq "IT"}</code> передаёт фильтр в LDAP-запрос и возвращает только нужных пользователей. <code>Get-ADUser -Filter * | Where-Object {$_.Department -eq "IT"}</code> тянет всех пользователей домена и фильтрует локально. На 50000 пользователей разница в скорости - в 10-20 раз. Дополнительно: используй параметр <code>-Properties</code> с явным перечислением нужных атрибутов. Без него возвращается только базовый набор, но с полным набором тянется лишнее.</p>
<h3>Что делать если командлет не найден но модуль установлен?</h3>
<p>Сначала проверь: <code>Get-Module -ListAvailable *ModuleName*</code>. Если модуль есть - импортируй явно: <code>Import-Module ModuleName</code>. Если ошибка про политику выполнения - смотри пункт выше. Если модуль установлен для другого пользователя или в другой области видимости (AllUsers vs CurrentUser) - запусти PowerShell с правами администратора или переустанови с нужным <code>-Scope</code>. Для модулей Az: <code>Connect-AzAccount</code> нужно вызывать до использования командлетов ресурсов.</p>
<h2>Что дальше</h2>
<p>Если ты дочитал до этого места - у тебя теперь есть инструменты для 90% задач автоматизации Windows. Get-Command чтобы найти нужный командлет. Where-Object и Select-Object чтобы работать с данными. ForEach-Object чтобы обработать каждый элемент, в том числе параллельно. Compare-Object для аудита. Export-Csv чтобы вынести результат наружу.</p>
<p>Следующий шаг - это функции и модули. Возьми конвейер который ты написал для анализа дисков или сравнения служб - оберни в функцию с параметрами. Добавь <code>[CmdletBinding()]</code> и <code>[Parameter(Mandatory)]</code>. Сохрани в .psm1 файл. Теперь это твой инструмент который работает на любом сервере куда ты скопировал папку с модулем. Именно так делается нормальная автоматизация.</p>
"Если
<br />
Комментарии открыты. Если конвейер не работает, ошибка непонятная, или нужен пример под конкретную задачу — описывай ситуацию. Разберёмся.<br />
Быстрый ответ: какие командлеты 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 файл. Теперь это твой инструмент который работает на любом сервере куда ты скопировал папку с модулем. Именно так делается нормальная автоматизация.
Если что-то не заработало - пиши
Комментарии открыты. Если конвейер не работает, ошибка непонятная, или нужен пример под конкретную задачу — описывай ситуацию. Разберёмся.