Getting Windows Events can be painfully slow, but Get-WinEvent and a good filter can really speed things up. Get-GPOEvents.ps1, below, looks for the most recent cycle of user an computer GPO events found in in the Microsoft\Windows\GroupPolicy log. This data can be quite valuable in troubleshooting GPO issues. It works by looking for the user events starting with 4005 and ending with 8005 and the computer events from 4004 to 8004. The FilterXML beginning at line 88 is for all events between two event records.
<# Alan Kaplan www.akaplan.com. Public version 12/24/2021 Get the most recent computer and user GPO Events from local or remote computer #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $Computer ) Write-Host "Getting GPO events from $computer" -foregroundColor green $OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding $para = [char]0x00b6 #Returns FQDN of current computer $MyPCname = ([System.Net.Dns]::GetHostByName(($env:computerName))).Hostname #This avoids unauthorized operation when using FQDN for the script localhost if ($computer -eq $MyPCname){$computer = 'localhost'} $UserStartEvent = '4005' $UserEndEvent = '8005' $ComputerStartEvent = '4004' $ComputerEndEvent = '8004' #loosely based on https://github.com/itadder/PoshLook/blob/dfc259c9fc14e6e80fc0a7ed63963a1aa1f788f6/scripts/helpers/wrapText.ps1 function wrapText { [CmdletBinding()] param ( [Parameter( Mandatory = $True, Position = 0, ValueFromPipeline = $true )] [string]$text, [byte]$width = 80 ) Begin { $o = '' $col = 0 } Process { $words = $text -split "\s+" foreach ( $word in $words ) { $col += $word.Length + 1 if ( $col -gt $width ) { $o += "$word`n" $col = $word.Length + 1 } Else { $o += "$Word " } } } End { $o } } Function Remove-LineBreaks($txt) { $(($txt -split '["\n\r"|"\r\n"|\n|\r]' | Where-Object { $_ } ) -join "$para ") | wrapText -width 150 } Function Get-FirstGPOEvent($IDNumber) { $queryXML = $XMLTemplate.Replace('EVTID', $IDNumber) Try { Get-WinEvent -ComputerName $computer -FilterXML $queryXML -MaxEvents 1 -ErrorAction Stop } catch { Write-Warning $_ Pause Exit } } $XMLTemplate = @' <QueryList> <Query Id="0" Path="Microsoft-Windows-GroupPolicy/Operational"> <Select Path="Microsoft-Windows-GroupPolicy/Operational">*[System[(EventID=EVTID)]]</Select> </Query> </QueryList> '@ $XMLIDTemplate = @' <QueryList> <Query Id="0" Path="Microsoft-Windows-GroupPolicy/Operational"> <Select Path="Microsoft-Windows-GroupPolicy/Operational">* [System[EventRecordID>=STARTID and EventRecordID<=ENDID]]</Select> </Query> </QueryList> '@ #Computer GPOs first applied at boot, then 90 - 120 minutes thereafter $ComputerStartRecord = (Get-FirstGPOEvent $ComputerStartEvent).RecordID $ComputerEndRecord = (Get-FirstGPOEvent $ComputerEndEvent).RecordID if ($ComputerStartRecord -gt $ComputerEndRecord) { Write-Warning 'Most recent GPO end event for computers not found, probably still applying. Please wait a few minutes and try again.' Pause Exit } #User GPOs first applied at logon, then 90 - 120 minutes thereafter $UserStartRecord = (Get-FirstGPOEvent $UserStartEvent).recordID $userEndRecord = (Get-FirstGPOEvent $UserEndEvent).recordID #Start event should always be a lower number than end event if ($UserStartRecord -gt $userEndRecord) { Write-Warning 'Most recent GPO end for users not found, may still be applying. Instead ending with most recent GPO event' Try { $userEndRecord = Get-WinEvent -ComputerName $computer -ProviderName 'Microsoft-Windows-GroupPolicy' -MaxEvents 1 -ErrorAction Stop } catch { Write-Warning $_ Pause Exit } } $UserRecordsXML = ($XMLIDTemplate.Replace('STARTID', $UserStartRecord)).Replace('ENDID', $userEndRecord) $UserEvents = Get-WinEvent -ComputerName $computer -FilterXML $UserRecordsXML -ErrorAction Stop | ForEach-Object { $_ | add-member -NotePropertyName 'GPO_Type' -NotePropertyValue 'User' -Force -PassThru | Select-Object TimeCreated, @{Name = "Record"; Expression = { [string]$_.RecordID } }, GPO_Type, @{Name = "EventID"; Expression = { [string]$_.ID } }, @{Name = "EvtMsg"; Expression = { Remove-LineBreaks $_.Message } } } $ComputerRecordsXML = ($XMLIDTemplate.Replace('STARTID', $ComputerStartRecord)).Replace('ENDID', $ComputerEndRecord) $computerEvents = Get-WinEvent -ComputerName $computer -FilterXML $ComputerRecordsXML -ErrorAction Stop | ForEach-Object { $_ | add-member -NotePropertyName 'GPO_Type' -NotePropertyValue 'Computer' -Force -PassThru | Select-Object TimeCreated, @{Name = "Record"; Expression = { [string]$_.RecordID } }, GPO_Type, @{Name = "EventID"; Expression = { [string]$_.ID } }, @{Name = "EvtMsg"; Expression = {Remove-LineBreaks $_.Message } } } $UserEvents + $computerEvents | sort-Object TimeCreated -Descending | Out-GridView -title "Most Recent Cycles of User and Computer GPO Events on $computer. Selected are sent to your clipboard" -PassThru | convertto-csv -NoTypeInformation | Set-Clipboard