Public/Get-AdOrphanGPO.ps1

Function Get-AdOrphanGPO {
    <#
        .SYNOPSIS
            Identify and optionally remove orphaned Group Policy Objects (GPOs).

        .DESCRIPTION
            This script identifies orphaned GPOs that do not have corresponding GPTs in the SYSVOL folder and optionally removes them.

        .PARAMETER RemoveOrphanGPOs
            Indicates whether the script should remove the identified orphaned GPOs.

        .EXAMPLE
            Get-OrphanedGPOs -RemoveOrphanGPOs $True

        .INPUTS
            None. You cannot pipe objects to this script.

        .OUTPUTS
            System.String. Outputs the names of orphaned GPOs or the results of deletion actions.

        .NOTES
            Used Functions:
                Name | Module
                ---------------------------------------|--------------------------
                Get-ADObject | ActiveDirectory
                Get-ChildItem | Microsoft.PowerShell.Management
                Get-GPO | GroupPolicy
                Import-Module | Microsoft.PowerShell.Core
                Write-Verbose | Microsoft.PowerShell.Utility
                Write-Error | Microsoft.PowerShell.Utility
                Get-FunctionDisplay | EguibarIT.DelegationPS & EguibarIT.HousekeepingPS

        .NOTES
            Version: 1.3
            DateModified: 20/Feb/2024
            LasModifiedBy: Vicente Rodriguez Eguibar
                vicente@eguibar.com
                Eguibar Information Technology S.L.
                http://www.eguibarit.com

    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    [OutputType([String])]

    param(
        [Parameter(Mandatory = $false)]
        [bool]$RemoveOrphanGPOs = $false
    )

    Begin {
       $txt = ($Variables.HeaderHousekeeping -f
            (Get-Date).ToShortDateString(),
            $MyInvocation.Mycommand,
            (Get-FunctionDisplay -Hashtable $PsBoundParameters -Verbose:$False)
        )
        Write-Verbose -Message $txt

        # Verify the Active Directory module is loaded
        if (-not (Get-Module -Name ActiveDirectory)) {
            Import-Module ActiveDirectory -Force -Verbose:$false
        } #end If

        ##############################
        # Variables Definition

        # Construct SYSVOL path and GPC DN
        $unc = '\\{0}\SYSVOL\{0}\Policies' -f $Variables.DnsFqdn
        $GPOPoliciesDN = 'CN=Policies,CN=System,{0}' -f $Variables.defaultNamingContext

    } #end Begin

    Process {
        # Get all GPOs from AD and remove CN=
        $gpos = Get-ADObject -LDAPFilter '(objectClass=groupPolicyContainer)' -SearchBase $GPOPoliciesDN |
            ForEach-Object { $_.Name.Replace('CN=', '') }

        # Get all GPTs from SYSVOL
        $gpts = Get-ChildItem -Path $unc -Directory |
            Where-Object { $_.Name -ne 'PolicyDefinitions' } |
            ForEach-Object { $_.Name }

        # Find orphaned GPOs
        $OrphanedGPOs = $gpos | Where-Object { $_ -notin $gpts }

        Write-Verbose '"Found {0} Orphaned GPOs"' -f $OrphanedGPOs.Count
        Write-Verbose $OrphanedGPOs

        # Option to remove orphaned GPOs
        if ($RemoveOrphanGPOs) {
            $OrphanedGPOs | ForEach-Object {
                $gpoGuid = [Guid]$_
                $gpo = Get-GPO -Guid $gpoGuid
                if ($PSCmdlet.ShouldProcess($gpo.DisplayName, 'Delete')) {
                    Write-Verbose 'Deleting Orphaned GPO: {0}' -f $gpo.DisplayName
                    $gpo.Delete()
                } #end If
            } #end ForEach-Object
        } #end If
    } #end Process

    End {
        Write-Verbose -Message "Function $($MyInvocation.InvocationName) finished getting and removing Orphan GPOs."
        Write-Verbose -Message ''
        Write-Verbose -Message '-------------------------------------------------------------------------------'
        Write-Verbose -Message ''
    } #end End
}