- Create a self-signed certificate with PowerShell - Thu, Aug 9 2018
- Prevent copying of an Active Directory attribute when duplicating a user account - Thu, Mar 29 2018
- Find and delete unlinked (orphaned) GPOs with PowerShell - Thu, Mar 15 2018
Update: The method discussed in this post only works for GPOs that are linked to OUs because Get-GPOReport isn't able to handle Site-linked GPOs. Don't use the script if you have Site-linked GPOs!
What causes orphaned GPOs?
Many admins extensively test GPOs before implementing them. Sometimes they simply unlink a test GPO instead of deleting it. Policies continuously change in most networks. If certain policies become obsolete, admins tend to just unlink them because the corresponding policies might become relevant again at a later time.
In my experience, this happens quite often, and many admins don't really care about unlinked GPOs. They just create new GPOs over and over again until nobody has any idea which GPOs are still used and which became orphaned years ago.
After years of changes, depending on the complexity of your environment, you may have hundreds or even thousands of unlinked GPOs. This leads to performance issues and messes up your GPO infrastructure. In can confuse adminsand lead to configuration errors.
Who wants to go through all these GPOs in the Group Management Console (GPMC) to find orphaned GPOs and delete them? Fortunately, PowerShell makes it relatively easy to automate the GPO cleanup process or just send notifications if GPOs become orphaned.
Finding unlinked GPOs with PowerShell
If you link GPOs to Sites you couldn’t use this method, as it is described in this post. If you want to use this method, you have to exclude Site linked GPOs based on a filter that identify those. It could be based on a Naming convention.
To find unlinked GPOs, we will use the Get-GPO and Get-GPOReport commands, which belong to the Group Policy module that is a part of RSAT. You can import the module with this command:
Import-Module GroupPolicy
To retrieve all GPOs, we use the -All parameter and use Get-GPOReport to cast them to an XML object. We pipe the result to Select-String, which finds us all lines without "<LinkTo>".
Get-GPO -All | Sort-Object displayname | Where-Object { If ( $_ | Get-GPOReport -ReportType XML | Select-String -NotMatch "<LinksTo>" ) {$_.DisplayName } }
The following command creates a list of all unlinked GPOs:
To log these GPOs to a text file, you just have to add this line after $_.DisplayName:
| Out-File "c:\admin\GPOBackup \UnLinkedGPOs.txt" –Append
We will use it later to log GPOs.
Deleting GPOs with PowerShell
Because you might not want to delete GPOs before creating a backup, we need a variable for our backup location.
$BackupPath = "c:\admin\GPOBackup"
The next command creates the backup:
Backup-GPO -Name $_.DisplayName -Path $BackupPath
To create a report of unlinked GPOs, we have to use Get-GPOReport again. However, instead of using XML, we will set the ReportType to HTML.
Get-GPOReport -Name $_.DisplayName -ReportType Html -Path "c:\admin\GPOBackup\$Date\$($_.DisplayName).html"
As the last step, we delete those GPOs using the Remove-GPO cmdlet:
$_.Displayname | Remove-GPO -Confirm
Let´s put all of these parts together. The only thing I added is a $Date variable to identify backups from different days.
Import-Module GroupPolicy $Date = Get-Date -Format dd_MM_yyyy $BackupPath = "c:\admin\GPOBackup\$Date" if (-Not(Test-Path -Path $BackupPath)) { New-Item -ItemType Directory $BackupPath -Force} Get-GPO -All | Sort-Object displayname | Where-Object { If ( $_ | Get-GPOReport -ReportType XML | Select-String -NotMatch "<LinksTo>" ) { Backup-GPO -Name $_.DisplayName -Path $BackupPath Get-GPOReport -Name $_.DisplayName -ReportType Html -Path "c:\admin\GPOBackup\$Date\$($_.DisplayName).html" $_.DisplayName | Out-File "c:\admin\GPOBackup\$Date\UnLinkedGPOs.txt" -Append $_.Displayname | remove-gpo -Confirm } }
You can run this script as a scheduled task to automate GPO cleanup.
If you simply want to create a report without automatically removing GPOs, you can remove $_.Displayname | remove-gpo –Confirm from the command. You can then check your backup location to take a closer look at orphaned GPOs. You can also send e-mails that inform you about new, unlinked GPOs. This is accomplished with the Send-MailMessage cmdlet that I added at the end of the script.
Subscribe to 4sysops newsletter!
Import-Module GroupPolicy $Date = Get-Date -Format dd_MM_yyyy $BackupPath = "c:\admin\GPOBackup" if (-Not(Test-Path -Path $BackupPath)) { New-Item -ItemType Directory c:\admin\GPOBackup -Force} Get-GPO -All | Sort-Object displayname | Where-Object { If ( $_ | Get-GPOReport -ReportType XML | Select-String -NotMatch "<LinksTo>" ) { $_.DisplayName | Out-File $BackupPath + "UnLinkedGPOs$Date.txt" -Append #Set E-mail variables. $EmailFrom = "gporeport@domain.com" $EmailTo = "Tim@domain.com" $Subject = "Unlinked GPO Report" $Body = (Get-Content $BackupPath\UnLinkedGPOs$Date.txt | Out-String) $SMTPServer = "smtp.domain.com" #Send Email Send-MailMessage -Subject $Subject -Body $Body -SmtpServer $SMTPServer -Priority High -To $EmailTo -From $EmailFrom } }
Conclusion
I highly recommend cleaning up your Group Policy environment regularly to reduce complexity and simplify the work of Group Policy admins for your network. I hope my script helps you automate this task!
Hello,
I ran this script (without deleting GPOs), it reported some GPOs as non-linked, but in fact, those are linked to AD sites, not OU.
So if had included the delete command, i would have deleted active GPOs …
Would you mind having a look at it please ?
Hi Vincent,
using Get-GPOReport doesn´t include Sites. You can use the mentioned method, if you linking GPOs to a domain or OUs. If you want to use it, you have to exlclude site-linked GPOs based on naming or searching for those and compare them.
Hi,
Thanks for your reply.
I was asking just to be sure i was using your script the right way.
Regards,
When I use the Following script it giving an error
"Cannot validate argument on parameter 'Name'. The argument is null or empty"
I am giving input file as I need particular GPOs needs to back up not all in domain
Hi Devi
Could you please check the current content of E:\GPO\GPONames.txt.
From the error message the only inference that can be drawn is, it contains some whitespace at the beginning of the file.
Thanks.. its working now
where the links are preserved if I use -keeplinks when the GPO itself is deleted.
HRESULT: 0x80072035)
by accident i deleted 'default domain policy'
orphaned and missing.
There is an enterprise level tool called Gytpol Validator which does this on all endpoints in an organization
You said this doesn't include site-linked GPOs. Someone please demonstrate HOW to find site-linked GPOs, so that we can exclude them. How do we know a GPO is "site-linked?"
Thanks.