В крупном домене Active Directory количество групповых политик (GPO) может исчисляться десятками и сотнями. Когда нужно найти, в какой именно политике задан конкретный параметр — например, «Interactive logon: Machine inactivity limit» — просматривать каждый GPO вручную через консоль управления групповыми политиками (GPMC) слишком долго. Гораздо быстрее сделать это скриптом на PowerShell.
Суть подхода
Модуль GroupPolicy предоставляет командлет Get-GPOReport, который для любой политики может сформировать подробный XML-отчёт со всеми настроенными в ней параметрами — от политик безопасности до административных шаблонов. Идея скрипта проста:
- Получить список всех GPO в домене (
Get-GPO -All). - Для каждой политики сформировать XML-отчёт (
Get-GPOReport -ReportType Xml). - Найти в отчёте нужную строку или параметр — по точному совпадению, маске (
*,?) или регулярному выражению. - Вывести название GPO, конкретную настройку и её значение — чтобы не листать отчёт вручную.
Что умеет готовый скрипт
- Поиск по маске или regex. Можно искать как по простому шаблону (
*inactivity*), так и по точному регулярному выражению — например, чтобы учитывать разное количество пробелов в названии параметра. - Настройки прямо в начале файла. Не нужно запоминать параметры командной строки — строка поиска, домен и путь для выгрузки результатов задаются переменными в верхней части скрипта, скрипт можно сразу запускать целиком.
- Вывод конкретного параметра и его значения, а не просто факта «совпадение найдено». Скрипт разбирает XML-структуру отчёта и показывает, например:
Настройка: Machine inactivity limit,Параметры: State=Enabled; SettingNumber=900. - Прогресс и тайминг по каждой политике. Для каждого GPO выводится время, потраченное на получение и разбор отчёта — это помогает быстро понять, если поиск «зависает» на конкретной политике или на конкретном контроллере домена.
- Параллельная обработка. Вместо последовательного опроса сотен GPO по одному скрипт может обрабатывать несколько политик одновременно (через
ForEach-Object -Parallelв PowerShell 7+ или runspace pool в PowerShell 5.1), что заметно сокращает общее время поиска в больших доменах. - Экспорт результатов в CSV — для дальнейшего анализа, отчётности или передачи коллегам.
Типичные сценарии применения
- Найти все GPO, где настроена конкретная политика безопасности (например, «Machine inactivity limit», «Password must meet complexity requirements», «Account lockout threshold»).
- Проверить, не задан ли один и тот же параметр в нескольких конфликтующих политиках.
- Провести аудит перед изменением или удалением GPO — убедиться, что важная настройка не потеряется.
- Быстро локализовать источник проблемы, когда на компьютерах применяется неожиданное значение параметра, а вручную искать по десяткам политик через GPMC нет времени.
Итог
Такой скрипт превращает рутинную ручную проверку десятков GPO через графический интерфейс в задачу на несколько секунд-минут: вы задаёте, что ищете, запускаете скрипт — и получаете точный список политик с названием параметра и его значением, а не просто «где-то там написано это слово».
# ============================================================
# НАСТРОЙКИ СКРИПТА
# ============================================================
$SearchString = "*Interactive logon*"
$UseRegex = $false
$DomainName = $env:USERDNSDOMAIN
$OutputPath = "C:\temp\gpo_search_results.csv"
$UseParallel = $true
$MaxThreads = 8
# ============================================================
# ДАЛЬШЕ КОД НЕ ТРОГАЕМ
# ============================================================
if ($UseRegex) {
$pattern = $SearchString
}
else {
$escaped = [regex]::Escape($SearchString)
$escaped = $escaped -replace '\\\*', '.*' -replace '\\\?', '.'
$pattern = $escaped
}
Write-Host "==============================================="
Write-Host "Домен поиска : $DomainName"
Write-Host "Строка поиска : $SearchString $(if ($UseRegex) {'(regex)'} else {'(маска)'})"
Write-Host "Regex-паттерн : $pattern"
Write-Host "PS версия : $($PSVersionTable.PSVersion)"
Write-Host "Параллельно : $UseParallel (потоков: $MaxThreads)"
Write-Host "==============================================="
Write-Host ""
Import-Module GroupPolicy -SkipEditionCheck -ErrorAction Stop
Write-Host "Получаем список всех GPO в домене..."
$swTotal = [System.Diagnostics.Stopwatch]::StartNew()
$allGposInDomain = Get-GPO -All -Domain $DomainName
Write-Host "Найдено GPO: $($allGposInDomain.Count). Список получен за $([math]::Round($swTotal.Elapsed.TotalSeconds,1)) сек.`n"
$total = $allGposInDomain.Count
$results = [System.Collections.Concurrent.ConcurrentBag[object]]::new()
Write-Host "Начинаем поиск...`n"
if ($UseParallel -and $PSVersionTable.PSVersion.Major -ge 7) {
# -------- PowerShell 7+: весь код обработки — прямо внутри -Parallel --------
$counterSync = [ref]0
$allGposInDomain | ForEach-Object -Parallel {
$gpo = $_
$pattern = $using:pattern
$DomainName = $using:DomainName
$bag = $using:results
# В каждом новом runspace модуль GroupPolicy надо импортировать заново
Import-Module GroupPolicy -SkipEditionCheck -ErrorAction SilentlyContinue
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$out = [PSCustomObject]@{
GPOName = $gpo.DisplayName
GUID = $gpo.Id
Elapsed = 0
Matched = $false
SettingName = $null
Parameters = $null
Error = $null
}
try {
[xml]$report = Get-GPOReport -Guid $gpo.Id -ReportType Xml -Domain $DomainName -ErrorAction Stop
$reportRaw = $report.OuterXml
if ($reportRaw -match $pattern) {
$out.Matched = $true
$allLeafNodes = $report.SelectNodes("//*[not(*)]")
foreach ($node in $allLeafNodes) {
if ($node.InnerText -match $pattern) {
$parent = $node.ParentNode
$nameNode = $parent.SelectSingleNode("*[local-name()='Name']")
$settingName = if ($nameNode) { $nameNode.InnerText } else { $node.LocalName }
$paramLine = ($parent.ChildNodes | Where-Object { $_.NodeType -eq 'Element' } |
ForEach-Object { "$($_.LocalName)=$($_.InnerText)" }) -join "; "
$out.SettingName = $settingName
$out.Parameters = $paramLine
break
}
}
}
}
catch {
$out.Error = $_.Exception.Message
}
$sw.Stop()
$out.Elapsed = [math]::Round($sw.Elapsed.TotalSeconds, 1)
# Живой прогресс прямо из потока
$status = if ($out.Error) { "ОШИБКА" } elseif ($out.Matched) { "НАЙДЕНО" } else { "нет совпадений" }
Write-Host "$($gpo.DisplayName) — $status ($($out.Elapsed) сек)"
if ($out.Matched) {
Write-Host " -> Настройка : $($out.SettingName)"
Write-Host " Параметры : $($out.Parameters)"
}
if ($out.Error) {
Write-Host " -> $($out.Error)"
}
if ($out.Matched) { $bag.Add($out) }
} -ThrottleLimit $MaxThreads
}
else {
# -------- Последовательный режим (PS 5.1 или UseParallel=$false) --------
$counter = 0
foreach ($gpo in $allGposInDomain) {
$counter++
Write-Host "[$counter/$total] Проверяем: $($gpo.DisplayName) ..." -NoNewline
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$matched = $false
$settingName = $null
$paramLine = $null
$errMsg = $null
try {
[xml]$report = Get-GPOReport -Guid $gpo.Id -ReportType Xml -Domain $DomainName -ErrorAction Stop
$reportRaw = $report.OuterXml
if ($reportRaw -match $pattern) {
$matched = $true
$allLeafNodes = $report.SelectNodes("//*[not(*)]")
foreach ($node in $allLeafNodes) {
if ($node.InnerText -match $pattern) {
$parent = $node.ParentNode
$nameNode = $parent.SelectSingleNode("*[local-name()='Name']")
$settingName = if ($nameNode) { $nameNode.InnerText } else { $node.LocalName }
$paramLine = ($parent.ChildNodes | Where-Object { $_.NodeType -eq 'Element' } |
ForEach-Object { "$($_.LocalName)=$($_.InnerText)" }) -join "; "
break
}
}
}
}
catch {
$errMsg = $_.Exception.Message
}
$sw.Stop()
$elapsed = [math]::Round($sw.Elapsed.TotalSeconds, 1)
if ($errMsg) {
Write-Host " ОШИБКА ($elapsed сек)" -ForegroundColor Red
Write-Host " -> $errMsg" -ForegroundColor Red
}
elseif ($matched) {
Write-Host " НАЙДЕНО ($elapsed сек)" -ForegroundColor Green
Write-Host " -> Настройка : $settingName" -ForegroundColor Yellow
Write-Host " Параметры : $paramLine" -ForegroundColor Cyan
$results.Add([PSCustomObject]@{
GPOName = $gpo.DisplayName; GUID = $gpo.Id
SettingName = $settingName; Parameters = $paramLine; Elapsed = $elapsed
})
}
else {
Write-Host " нет совпадений ($elapsed сек)" -ForegroundColor DarkGray
}
}
}
$swTotal.Stop()
$resultsArray = $results.ToArray()
Write-Host "`n==============================================="
Write-Host "Поиск завершён за $([math]::Round($swTotal.Elapsed.TotalSeconds,1)) сек."
Write-Host "Найдено совпадений: $($resultsArray.Count) из $total GPO"
Write-Host "==============================================="
if ($OutputPath -ne "" -and $resultsArray.Count -gt 0) {
$resultsArray | Select-Object GPOName, GUID, SettingName, Parameters, Elapsed |
Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
Write-Host "Результаты сохранены в: $OutputPath"
}
$resultsArray | Select-Object GPOName, SettingName, Parameters, Elapsed | Format-Table -AutoSize
Similar Posts:
- Как искать внутри GPO групповых политик.
- Как найти программу и версию с помощью powershell
- Как вывести список пользователей у которых не при монтировался диск fslogix и переименовать FriendlyName
- Как найти и безопасно удалить старые .bak/.trn файлы бэкапов MSSQL с помощью PowerShell
- Как удалить группы из коллекции rds rdp windows
