Scripts/Get-ControlledGpoStatus.ps1
<#
.SYNOPSIS Check Windows domain GPOs and AGPM server controlled GPOs are in sync .DESCRIPTION Enumerates all GPOs in a Windows domain and controlled GPOs on an AGPM server. Checks are run on the discovered GPOs to determine if the version deployed to the domain matches that on the AGPM server. Matching of GPOs between the Windows domain and the AGPM server is performed by GPO GUID. If a domain or AGPM controlled GPO does not have an associated matching GPO this is flagged in the output. For GPOs which are matched the following inconsistencies are flagged: - Name mismatch - Computer policy version mismatch - User policy version mismatch - WMI filter name mismatch .PARAMETER AgpmServer The fully qualified domain name of the AGPM server for which controlled GPOs will be enumerated. If a domain was not specified, the default AGPM server will be used as configured in the AGPM Client. If a domain was specified, the default AGPM server will be "agpm.<domain>". .PARAMETER Domain The fully qualified domain name of the Windows domain for which GPOs will be enumerated. The default is the domain of the host system. .EXAMPLE Get-ControlledGpoStatus Enumerates all GPOs on the host system's domain against the default AGPM server and verifies they are in sync. .NOTES The following PowerShell modules are required: - GroupPolicy Required to enumerate Windows domain GPOs. Windows Server: Provided by the Group Policy Management feature. Windows clients: Installed via the Remote Server Administration Tools. - Microsoft.Agpm Required to enumerate AGPM server controlled GPOs. This module is provided by the AGPM Client software. Not compatible with PowerShell Core as the dependency modules are themselves not compatible. .LINK https://github.com/ralish/PSWinGlue #> #Requires -Version 3.0 [CmdletBinding()] [OutputType([PSCustomObject[]])] Param( [ValidateNotNullOrEmpty()] [String]$Domain, [ValidateNotNullOrEmpty()] [String]$AgpmServer ) $PowerShellCore = New-Object -TypeName Version -ArgumentList 6, 0 if ($PSVersionTable.PSVersion -ge $PowerShellCore -and $PSVersionTable.Platform -ne 'Win32NT') { throw '{0} is only compatible with Windows.' -f $MyInvocation.MyCommand.Name } # Check required modules are present $RequiredModules = @('GroupPolicy', 'Microsoft.Agpm') foreach ($Module in $RequiredModules) { Write-Verbose -Message ('Checking module is available: {0}' -f $Module) if (!(Get-Module -Name $Module -ListAvailable -Verbose:$false)) { throw 'Required module not available: {0}' -f $Module } } $Results = New-Object -TypeName 'Collections.Generic.List[PSCustomObject]' $TypeName = 'PSWinGlue.ControlledGpoStatus' Update-TypeData -TypeName $TypeName -DefaultDisplayPropertySet @('Name', 'Status') -Force # Use the "default" AGPM server for the domain if ($Domain -and !$AgpmServer) { $AgpmServer = 'agpm.{0}' -f $Domain Write-Warning -Message ('Using default AGPM server: {0}' -f $AgpmServer) } # Retrieve domain GPOs try { if ($Domain) { $DomainGPOs = @(Get-GPO -All -Domain $Domain) } else { $DomainGPOs = @(Get-GPO -All) } } catch { throw $_ } # Retrieve AGPM GPOs try { $DefaultArchiveChanged = $false # Overriding the DefaultArchive registry value is unfortunately necessary # as the Microsoft.Agpm PowerShell module does not respect any configured # per-domain servers listed under the Domains key, unlike the MMC snap-in. if ($AgpmServer) { $AgpmRegPath = 'HKLM:\Software\Microsoft\AGPM' if (Test-Path -Path $AgpmRegPath) { $AgpmReg = Get-Item -Path $AgpmRegPath -ErrorAction Stop } else { $AgpmReg = New-Item -Path $AgpmRegPath -ErrorAction Stop } if ($AgpmReg.Property.Contains('DefaultArchive')) { $OriginalDefaultArchive = $AgpmReg.GetValue('DefaultArchive') } if ($AgpmServer -notmatch ':[1-9][0-9]*$') { $DefaultArchive = '{0}:4600' -f $AgpmServer } else { $DefaultArchive = $AgpmServer } Set-ItemProperty -Path $AgpmRegPath -Name 'DefaultArchive' -Value $DefaultArchive -ErrorAction Stop $DefaultArchiveChanged = $true } if ($Domain) { $AgpmGPOs = @(Get-ControlledGpo -Domain $Domain -ErrorAction Stop) } else { $AgpmGPOs = @(Get-ControlledGpo -ErrorAction Stop) } } catch { throw $_ } finally { if ($DefaultArchiveChanged) { Set-ItemProperty -Path $AgpmRegPath -Name 'DefaultArchive' -Value $OriginalDefaultArchive } } # Check the status of all AGPM controlled GPOs foreach ($AgpmGPO in $AgpmGPOs) { $Result = [PSCustomObject]@{ PSTypeName = $TypeName Name = $AgpmGPO.Name AGPM = $AgpmGPO Domain = $null Status = @() } $DomainGPO = $DomainGPOs | Where-Object Id -EQ $AgpmGPO.ID.TrimStart('{').TrimEnd('}') if ($DomainGPO) { $Result.Domain = $DomainGPO } else { $Result.Status = @('Only exists in AGPM') $Results.Add($Result) continue } # Check display name is in sync if ($AgpmGPO.Name -ne $DomainGPO.DisplayName) { $Result.Status += @('Name mismatch') } # Check computer policy is in sync # # The casting is necessary as the AGPM version properties are strings. if ([Int]$AgpmGPO.ComputerVersion -lt $DomainGPO.Computer.DSVersion) { $Result.Status += @('Domain computer policy is newer (Import)') } elseif ([Int]$AgpmGPO.ComputerVersion -gt $DomainGPO.Computer.DSVersion) { $Result.Status += @('AGPM computer policy is newer (Deploy)') } # Check user policy is in sync # # The casting is necessary as the AGPM version properties are strings. if ([Int]$AgpmGPO.UserVersion -lt $DomainGPO.User.DSVersion) { $Result.Status += @('Domain user policy is newer (Import)') } elseif ([Int]$AgpmGPO.UserVersion -gt $DomainGPO.User.DSVersion) { $Result.Status += @('AGPM user policy is newer (Deploy)') } # Check WMI filter is in sync if ($AgpmGPO.WmiFilterName -or $DomainGPO.WmiFilter) { if ($AgpmGPO.WmiFilterName -ne $DomainGPO.WmiFilter.Name) { $Result.Status += @('WMI filter mismatch') } } if (!$Result.Status) { $Result.Status = @('OK') } $Results.Add($Result) } # Add any domain GPOs not controlled by AGPM if ($AgpmGPOs.Count -gt 0) { $MissingGPOs = $DomainGPOs | Where-Object Id -NotIn $AgpmGPOs.ID.TrimStart('{').TrimEnd('}') } else { $MissingGPOs = $DomainGPOs } foreach ($MissingGPO in $MissingGPOs) { $Result = [PSCustomObject]@{ PSTypeName = $TypeName Name = $MissingGPO.DisplayName AGPM = $null Domain = $MissingGPO Status = @('Only exists in Domain') } $Results.Add($Result) } return ($Results.ToArray() | Sort-Object -Property Name) |