August 24th, 2014

Weekend Scripter: Use PowerShell to Troubleshoot Group Policy

Doctor Scripto
Scripter

Summary: Guest blogger, Alex Verboon, talks about using Windows PowerShell to troubleshoot delays in Group Policy performance.

Microsoft Scripting Guy, Ed Wilson, is here. Today we have a new guest blogger, Alex Verboon. I was reading Alex’s blog page. I liked what he had to say and I invited him to contribute a guest blog post here. You can read his blog at Anything about IT.

Now, here is Alex…

For years, I have been reading and consuming content from the Hey, Scripting Guy! Blog, so it's a big pleasure to give something back. Today I want to share with you a Windows PowerShell script I put together recently that collects the Group Policy processing time on a client. The script might come in handy when you are troubleshooting or reviewing system startup performance or to prove that it's not Group Policy that's causing delays.

If you are not familiar with troubleshooting Group Policy, I recommend that you read the TechNet article Troubleshooting Group Policy Using Event Logs.

The script accepts multiple computer names as input, so you can specify multiple computers that are located in different locations if you're interested to find out what the Group Policy processing time is within the different locations of the company.

Here is what the script does for each computer specified and when online:

  • Retrieves the last end policy processing event of the user that logged on to that computer
  • Retrieves the last end policy processing event for the computer
  • Gets the unique activity ID for the instance of the user Group Policy processing cycle
  • Retrieves the domain controller name from event 5308 from the user processing cycle
  • Retrieves network information from event 5314 from the user processing cycle
  • Gets the unique activity ID for the instance of the computer Group Policy processing cycle
  • Retrieves the domain controller name from event 5308 from the computer processing cycle
  • Retrieves network information from event 5314 from the computer processing cycle

The script also writes the following computer information to a custom object:

  • Computer end policy processing event ID
  • Event ID description
  • Event message
  • Time stamp
  • Activity ID
  • Policy processing time (in seconds)
  • Computer name
  • Bandwidth (in Kbps)
  • Slow link (yes or no)
  • Domain controller used

And it writes the following user information to a custom object:

  • User end policy processing event ID
  • Event ID description
  • Event message
  • Time stamp
  • Activity ID
  • Policy processing time (in seconds)
  • Bandwidth (in Kbps)
  • Slow link (yes or no)
  • Domain controller used

If the script is launched with the –Computer parameter, the script output is:

Image of command output

When specifying the optional –ShowDetails switch, the script output is:

Image of command output

Following is the full script. You can download this script from the Script Center Repository: Get-GPProcessingTime.

function Get-GPProcessingtime

{

<#

.Synopsis

   Get Group Policy processing time from the Group Policy event log on local and remote computers

.DESCRIPTION

   The Get-GPProcessingtime cmdlet gets Group Policy processing time for the user and computer related

   Group Policies that are processed on the specified computer(s).

   The last user and computer Group Policy processing event is used.

.EXAMPLE

   Get-GPProcessingtime -Computer TestClient1,TestClient2,TestClient3

   Lists additional details of the Group Policy processing duration information

    Cmp_PrincipalSamName         Cmp_PolicyElaspedTimeInSeconds      Usr_PolicyElaspedTimeInSeconds    

    ——————–         ——————————      ——————————    

    CORP\TestClient1$               1                                   2                                 

    CORP\TestClient2$               2                                   2                                  

    CORP\TestClient3$               12                                  49

.EXAMPLE

    Get-GPProcessingtime -Computer TestClient1 -ShowDetails

    Cmp_ID                         : 8004

    Cmp_EventTypeDescription       : Successful computer manual refresh event

    Cmp_Message  : Completed manual processing of policy for computer CORP\TestClient1$ in 1 second.

    Cmp_TimeCreated                : 8/1/2014 8:13:40 PM

    Cmp_ActivityID                 : e872d44e-1b89-434a-bf79-b1875ec810cf

    Cmp_PolicyElaspedTimeInSeconds : 1

    Cmp_PrincipalSamName           : CORP\TestClient1$

    Cmp_BandwidthInkbps            : 185508

    Cmp_IsSlowLink                 : false

    Cmp_DomainController           : dc01.corp.com

    Usr_ID                         : 8001

    Usr_EventTypeDescription       : Successful user end event

    Usr_Message                    : Completed user logon policy processing for CORP\User1 in 2 seconds.

    Usr_TimeCreated                : 7/30/2014 4:55:42 PM

    Usr_ActivityID                 : 43ca8a03-2b98-4e92-8613-aad67990bca3

    Usr_PolicyElaspedTimeInSeconds : 2

    Usr_PrincipalSamName           : CORP\User1

    Usr_BandwidthInkbps            : 1200750

    Usr_IsSlowLink                 : false

    Usr_DomainController           : dc01.corp.com

.PARAMETER Computer

    Type the NetBIOS name, an Internet Protocol (IP) address, or the fully qualified domain name of the computer.

    The default value is the local computer.

    To get Group Policy processing information from remote computers, the firewall port for the event log service must be configured to allow remote access.

    This cmdlet does not rely on Windows PowerShell remoting. You can use the ComputerName parameter even if your computer is not configured to run remote commands.

.PARAMETER ShowDetails

    The ShowDetails switch is optional. When set, additonal Group Policy processing information is listed,    such as the type of the Group Policy processing event (periodic, manual), the Activity ID network bandwidth, and the domain controller being contacted.

.LINKS

# http://technet.microsoft.com/library/cc749336.aspx   

#>

[CmdletBinding()]

 Param(

    [Parameter(Mandatory=$False,

    ValueFromPipelineByPropertyName=$true,HelpMessage="Enter Computername(s)",

    Position=0)]

    [Alias("ipaddress","host")]

    [String[]]$Computer = "localhost",

    [switch]$ShowDetails

    )

begin{

    # User Event IDs

    $ugeventcodes = @{

    "8001" = "Successful user end event";

    "8003" = "Successful user network change event";

    "8005" = "Successful user manual refresh event";

    "8007" = "Successful user periodic refresh event";

    "7001" = "Error during user end event";

    "7003" = "Error during user network change event";

    "7005" = "Error during user manual refresh event";

    "7007" = "Error during user periodic refresh event";

    "6001" = "Warnings during user end event";

    "6003" = "Warnings during  user network change event";

    "6005" = "Warnings during  user manual refresh event";

    "6007" = "Warnings during  user periodic refresh event";

    }

    # Computer Event IDs

    $cgeventcodes = @{

    "8000" = "Successful computer end event";

    "8002" = "Successful computer network change event";

    "8004" = "Successful computer manual refresh event";

    "8006" = "Successful computer periodic refresh event";

    "7000" = "Error duing computer end event";

    "7002" = "Error during computer network change event";

    "7004" = "Error during computer manual refresh event";

    "7006" = "Error during computer periodic refresh event";

    "6000" = "Warnings during computer end event";

    "6002" = "Warnings during computer network change event";

    "6004" = "Warnings during  computer manual refresh event";

    "6006" = "Warnings during  computer periodic refresh event";

    }

}

Process{

    $compcount = $Computer.count

    $si=1

    $gpprocessresult = @()

    ForEach ($comp in $Computer)

    {

        If (Test-Connection -ComputerName "$comp" -Count 1 -Quiet)

            {

            $orgCulture = Get-Culture

            [System.Threading.Thread]::CurrentThread.CurrentCulture = New-Object "System.Globalization.CultureInfo" "en-US"

           # variable that holds all user related event IDs

            $ueventids = $ugeventcodes.Keys

            # Get last user GP processing event

            $uevent = Get-WinEvent -ComputerName $Comp -filterHashTable @{

             Providername = "Microsoft-Windows-GroupPolicy"

             ID = $($ueventids)

              } -ErrorAction SilentlyContinue | select -first 1 -ErrorAction SilentlyContinue

            # variable that holds all computer related IDs

            $ceventids = $cgeventcodes.Keys

            # Get last computer GP processing event

            $cevent = Get-WinEvent -ComputerName $Comp -filterHashTable @{

             Providername = "Microsoft-Windows-GroupPolicy"

             ID = $($ceventids)

             } -ErrorAction SilentlyContinue | select -first 1 -ErrorAction SilentlyContinue

            if (-not ([string]::IsNullOrEmpty($uevent)))

            {

                 # create a variable that holds the user event details

                 $ueventXML = [xml]$uevent.ToXml()

                 # Retrieve Event with EventID 5308 to get the domain controller name used when processing the user

                 $Query = ' <QueryList><Query Id="0" Path="Application"><Select Path="Microsoft-Windows-GroupPolicy/Operational">*[System/Correlation/@ActivityID="{CorrelationID}"] and *[System[(EventID="5308")]] </Select></Query></QueryList>'

                 $FilterXML = $Query.Replace("CorrelationID",$uevent.ActivityID)

                 $udc = Get-WinEvent -FilterXml $FilterXML -ComputerName $comp -ErrorAction SilentlyContinue | select -First 1 -ErrorAction SilentlyContinue

                 if (-not ([string]::IsNullOrEmpty($udc)))

                 {$udcevent = [xml]$udc.ToXml()

                 $udcname = $udcevent.event.EventData.Data[0]."#Text"

                 } 

                 # Retrieve Event with Event ID 5314 to get network information when processing user

                 $Query = ' <QueryList><Query Id="0" Path="Application"><Select Path="Microsoft-Windows-GroupPolicy/Operational">*[System/Correlation/@ActivityID="{CorrelationID}"] and *[System[(EventID="5314")]] </Select></Query></QueryList>'

                 $FilterXML = $Query.Replace("CorrelationID",$uevent.ActivityID)

                 $unw = Get-WinEvent -FilterXml $FilterXML -ComputerName $comp -ErrorAction SilentlyContinue | select -First 1 -ErrorAction SilentlyContinue

                 if (-not ([string]::IsNullOrEmpty($unw)))

                 {$unwevent = [xml]$unw.ToXml()

                 $uBandwidthInkbps = $unwevent.event.EventData.Data[0]."#Text"

                 $uIsSlowLink = $unwevent.event.EventData.Data[1]."#Text"

                 } 

            }

            if (-not ([string]::IsNullOrEmpty($cevent)))

            {

                 # create a variable that holds the computer event details

                 $ceventXML = [xml]$cevent.ToXml()

                 # Retrieve Event with EventID 5308 to get the domain controller name when processing the computer

                 $Query = ' <QueryList><Query Id="0" Path="Application"><Select Path="Microsoft-Windows-GroupPolicy/Operational">*[System/Correlation/@ActivityID="{CorrelationID}"] and *[System[(EventID="5308")]] </Select></Query></QueryList>'

                 $FilterXML = $Query.Replace("CorrelationID",$cevent.ActivityID)

                 $cdc = Get-WinEvent -FilterXml $FilterXML -ComputerName $comp -ErrorAction SilentlyContinue | select -First 1 -ErrorAction SilentlyContinue

                 if (-not ([string]::IsNullOrEmpty($cdc)))

                 {$cdcevent = [xml]$cdc.ToXml()

                 $cdcname = $cdcevent.event.EventData.Data[0]."#Text"

                 }

                 # Retrieve Event with EventID 5314 to get network information when processing computer

                 $Query = ' <QueryList><Query Id="0" Path="Application"><Select Path="Microsoft-Windows-GroupPolicy/Operational">*[System/Correlation/@ActivityID="{CorrelationID}"] and *[System[(EventID="5314")]] </Select></Query></QueryList>'

                 $FilterXML = $Query.Replace("CorrelationID",$cevent.ActivityID)

                 $cnw = Get-WinEvent -FilterXml $FilterXML -ComputerName $comp -ErrorAction SilentlyContinue | select -First 1 -ErrorAction SilentlyContinue

                 if (-not ([string]::IsNullOrEmpty($unw)))

                 {$cnwevent = [xml]$cnw.ToXml()

                 $cBandwidthInkbps = $cnwevent.event.EventData.Data[0]."#Text"

                 $cIsSlowLink = $cnwevent.event.EventData.Data[1]."#Text"

                 } 

           }

            $object = New-Object -TypeName PSObject

            $object | Add-Member -MemberType NoteProperty -Name Cmp_ID -Value $cevent.Id

            $object | Add-Member -MemberType NoteProperty -Name Cmp_EventTypeDescription -Value $cgeventcodes["$($cevent.Id)"]

            $object | Add-Member -MemberType NoteProperty -Name Cmp_Message -Value $cevent.Message

            $object | Add-Member -MemberType NoteProperty -Name Cmp_TimeCreated -Value $cevent.TimeCreated

            $object | Add-Member -MemberType NoteProperty -Name Cmp_ActivityID -Value $cevent.ActivityId

            $object | Add-Member -MemberType NoteProperty -Name Cmp_PolicyElaspedTimeInSeconds -Value ($cpe = If ([string]::IsNullOrEmpty($cevent)) {} else {  $ceventXML.Event.EventData.Data[0].'#text'})

            $object | Add-Member -MemberType NoteProperty -Name Cmp_PrincipalSamName -Value ($cpn = If ([string]::IsNullOrEmpty($cevent)) {} else { $ceventXML.Event.EventData.Data[2].'#text' })

            $object | Add-Member -MemberType NoteProperty -Name Cmp_BandwidthInkbps -Value $cBandwidthInkbps

            $object | Add-Member -MemberType NoteProperty -Name Cmp_IsSlowLink -Value $cIsSlowLink

            $object | Add-Member -MemberType NoteProperty -Name Cmp_DomainController -Value $cdcname

            $object | Add-Member -MemberType NoteProperty -Name Usr_ID -Value $uevent.Id

            $object | Add-Member -MemberType NoteProperty -Name Usr_EventTypeDescription -Value $ugeventcodes["$($uevent.Id)"]

            $object | Add-Member -MemberType NoteProperty -Name Usr_Message -Value $uevent.Message

            $object | Add-Member -MemberType NoteProperty -Name Usr_TimeCreated -Value $uevent.TimeCreated

            $object | Add-Member -MemberType NoteProperty -Name Usr_ActivityID -Value $uevent.ActivityId

            $object | Add-Member -MemberType NoteProperty -Name Usr_PolicyElaspedTimeInSeconds -Value ($Upe = If ([string]::IsNullOrEmpty($uevent)) {} else {  $ueventXML.Event.EventData.Data[0].'#text'})

            $object | Add-Member -MemberType NoteProperty -Name Usr_PrincipalSamName -Value ($upn = If ([string]::IsNullOrEmpty($uevent)) {} else { $ueventXML.Event.EventData.Data[2].'#text' })

            $object | Add-Member -MemberType NoteProperty -Name Usr_BandwidthInkbps -Value $uBandwidthInkbps

            $object | Add-Member -MemberType NoteProperty -Name Usr_IsSlowLink -Value $uIsSlowLink

            $object | Add-Member -MemberType NoteProperty -Name Usr_DomainController -Value $udcname

            $gpprocessresult += $object

            }

            Else

            {

                write-output "Client $comp is NOT reachable, skipping"

            }

            Write-Progress -Activity "Processing $comp" -Status "Processing $si of $compcount" -PercentComplete (($si / $compcount) * 100)

            $si++

       }

}

End{

    If ($ShowDetails.IsPresent -eq $false)

        {$gpprocessresult | Select Cmp_PrincipalSamName, Cmp_PolicyElaspedTimeInSeconds,Usr_PolicyElaspedTimeInSeconds}

    Else

        {$gpprocessresult}

    }

}

~Alex

Thank you, Alex, for an excellent post—well done.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Author

The "Scripting Guys" is a historical title passed from scripter to scripter. The current revision has morphed into our good friend Doctor Scripto who has been with us since the very beginning.

0 comments

Discussion are closed.