Public/Search-GPO.ps1
|
function Search-GPO { <# .SYNOPSIS Searches GPO reports and SYSVOL scripts for a literal string. .DESCRIPTION Scans GPO XML metadata and Startup, Shutdown, Logon, and Logoff scripts. Emits structured result objects. Use -ShowDetails to select the detailed default display view without changing the returned object contract. .PARAMETER SearchTerm The literal string to search for within GPO reports and script content. .PARAMETER ShowDetails Selects the detailed default display view for returned result objects. .EXAMPLE Search-GPO -SearchTerm "Printer" .EXAMPLE Search-GPO "Deploy" -ShowDetails .RELATEDLINKS https://deepwiki.com/DailenG/Search-GPO #> [CmdletBinding()] param( [Parameter(Position = 0, ValueFromPipeline = $true)] [string]$SearchTerm, [switch]$ShowDetails ) begin { try { Import-Module -Name GroupPolicy -ErrorAction Stop } catch { throw "Search-GPO requires the GroupPolicy module. Install RSAT Group Policy Management tools. $($_.Exception.Message)" } } process { if ([string]::IsNullOrWhiteSpace($SearchTerm)) { try { $SearchTerm = Read-Host "Enter the server name or string to search for" } catch { throw "Could not read input. Run with: Search-GPO -SearchTerm 'Name'. $($_.Exception.Message)" } } if ([string]::IsNullOrWhiteSpace($SearchTerm)) { return } $DomainDNS = $env:USERDNSDOMAIN if ([string]::IsNullOrWhiteSpace($DomainDNS)) { throw "USERDNSDOMAIN is not set. Run Search-GPO from a domain-joined session." } $EscapedSearchTerm = [regex]::Escape($SearchTerm) $SysvolBase = "\\$DomainDNS\SYSVOL\$DomainDNS\Policies" $Timer = [System.Diagnostics.Stopwatch]::StartNew() $Results = [System.Collections.Generic.List[object]]::new() $Failures = [System.Collections.Generic.List[object]]::new() Write-Information -MessageData "Initializing GPO scan for: $SearchTerm" -InformationAction Continue Write-Verbose "Targeting Domain: $DomainDNS" Write-Verbose "SYSVOL Root: $SysvolBase" $AllGpos = @(Get-GPO -All -ErrorAction Stop) $TotalCount = $AllGpos.Count for ($Index = 0; $Index -lt $TotalCount; $Index++) { $Gpo = $AllGpos[$Index] $Current = $Index + 1 $Percent = [math]::Round(($Current / $TotalCount) * 100, 0) $Timestamp = Get-Date -Format "HH:mm:ss" $GpoName = $Gpo.DisplayName $GpoGuid = ([guid]$Gpo.Id).ToString('B').ToUpperInvariant() $PolicyPath = Join-Path -Path $SysvolBase -ChildPath $GpoGuid $MatchSource = [System.Collections.Generic.List[string]]::new() $MatchEvidence = [System.Collections.Generic.List[string]]::new() $LinkPaths = "--- NONE ---" Write-Progress -Activity "Scanning GPOs (Elapsed: $($Timer.Elapsed.ToString('mm\:ss')))" ` -Status "[$Timestamp] Analyzing [$Current/$TotalCount]: $GpoName" ` -PercentComplete $Percent Write-Verbose "[$Timestamp] START: $GpoName [ID: $($Gpo.Id)]" try { Write-Verbose " -> Metadata: analyzing XML settings." [xml]$GpoXml = Get-GPOReport -Guid $Gpo.Id -ReportType Xml -ErrorAction Stop $XmlText = $GpoXml.OuterXml if ($XmlText.IndexOf($SearchTerm, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) { $MatchSource.Add("GPO Settings") Write-Verbose " Match found in metadata XML." if ($XmlText -match "([^>]{0,50}$EscapedSearchTerm[^<]{0,50})") { $MatchEvidence.Add("XML Snippet: ...$($Matches[1])...") } } $ActiveLinks = @($GpoXml.GPO.LinksTo) | Where-Object { $_.Enabled -eq "true" } if ($ActiveLinks) { $LinkPaths = $ActiveLinks.SOMPath -join "; " } Write-Verbose " -> Link status: $(if ($ActiveLinks) { 'Linked' } else { 'Unlinked' })" } catch { $Failures.Add([pscustomobject]@{ GPOName = $GpoName Stage = "GPO Settings" Path = $null Error = $_.Exception.Message }) $LinkPaths = "--- ERROR ---" Write-Warning "Search-GPO could not read metadata for GPO '$GpoName': $($_.Exception.Message)" } $ScriptFolders = @( [pscustomobject]@{ Label = "Machine\Scripts\Startup"; Path = Join-Path -Path $PolicyPath -ChildPath "Machine\Scripts\Startup" } [pscustomobject]@{ Label = "Machine\Scripts\Shutdown"; Path = Join-Path -Path $PolicyPath -ChildPath "Machine\Scripts\Shutdown" } [pscustomobject]@{ Label = "User\Scripts\Logon"; Path = Join-Path -Path $PolicyPath -ChildPath "User\Scripts\Logon" } [pscustomobject]@{ Label = "User\Scripts\Logoff"; Path = Join-Path -Path $PolicyPath -ChildPath "User\Scripts\Logoff" } ) foreach ($Folder in $ScriptFolders) { try { if (-not (Test-Path -LiteralPath $Folder.Path -PathType Container)) { continue } Write-Verbose " -> Script folder: $($Folder.Label)" $FileMatches = @(Get-ChildItem -LiteralPath $Folder.Path -File -Recurse -ErrorAction Stop | Select-String -Pattern $EscapedSearchTerm -ErrorAction Stop) if ($FileMatches.Count -gt 0) { if ($MatchSource -notcontains "Script Code") { $MatchSource.Add("Script Code") } Write-Verbose " Match found in script content." foreach ($FileMatch in $FileMatches) { $MatchEvidence.Add("File ($($FileMatch.Filename)) line $($FileMatch.LineNumber): $($FileMatch.Line.Trim())") } } } catch { $Failures.Add([pscustomobject]@{ GPOName = $GpoName Stage = "Script Code" Path = $Folder.Path Error = $_.Exception.Message }) Write-Warning "Search-GPO could not scan '$($Folder.Label)' for GPO '$GpoName': $($_.Exception.Message)" } } if ($MatchSource.Count -gt 0) { $Result = [pscustomobject]@{ GPOName = $GpoName IsLinked = $LinkPaths -notin "--- NONE ---", "--- ERROR ---" Source = $MatchSource -join ", " MatchDetails = $MatchEvidence -join " | " LastModified = $Gpo.ModificationTime LinkPaths = $LinkPaths } $Result.PSObject.TypeNames.Insert(0, "SearchGPO.Result") if ($ShowDetails) { $Result.PSObject.TypeNames.Insert(0, "SearchGPO.Result.Detail") } $Results.Add($Result) } Write-Verbose "[$Timestamp] FINISH: $GpoName" } $Timer.Stop() Write-Progress -Activity "Scanning GPOs" -Completed $FailureDetails = $Failures.ToArray() foreach ($Result in $Results) { $Result | Add-Member -NotePropertyName ScanFailureCount -NotePropertyValue $FailureDetails.Count $Result | Add-Member -NotePropertyName ScanFailures -NotePropertyValue $FailureDetails } if ($FailureDetails.Count -gt 0) { Write-Warning "Search-GPO completed with $($FailureDetails.Count) failed scan step(s). See preceding warnings and the ScanFailures property on each result." } if ($Results.Count -gt 0) { Write-Information -MessageData "Scan complete: found $($Results.Count) match(es) in $($Timer.Elapsed.ToString('mm\:ss'))." -InformationAction Continue $Results } else { Write-Information -MessageData "No matches found for '$SearchTerm' across $TotalCount GPOs." -InformationAction Continue } } } |