Public/04_AD_GPO/Get-VBUnusedGPOs.ps1

# ============================================================
# FUNCTION : Get-VBUnusedGPOs
# VERSION : 1.0.0
# CHANGED : 10-04-2026 -- Initial VB-compliant release
# AUTHOR : Vibhu
# PURPOSE : Identifies Group Policy Objects that are not linked to any domain or OU
# ENCODING : UTF-8 with BOM
# ============================================================

<#
.SYNOPSIS
    Identifies Group Policy Objects that are not linked to any domain or OU.
 
.DESCRIPTION
    Scans all GPOs in the domain and identifies those that have no links to any organizational units
    or domains. Returns detailed information including version numbers and creation/modification times.
 
.PARAMETER ComputerName
    Domain Controller to query. Defaults to local machine. Accepts pipeline input.
 
.PARAMETER Credential
    Alternate credentials for the GPO query.
 
.EXAMPLE
    Get-VBUnusedGPOs
 
.EXAMPLE
    Get-VBUnusedGPOs -ComputerName DC01
 
.EXAMPLE
    'DC01','DC02' | Get-VBUnusedGPOs -Credential (Get-Credential)
 
.OUTPUTS
    [PSCustomObject]: ComputerName, Name, UserVersion, ComputerVersion, Created, Modified, ID, Status, CollectionTime
 
.NOTES
    Version : 1.0.0
    Author : Vibhu
    Modified : 10-04-2026
    Category : AD / GPO
#>


function Get-VBUnusedGPOs {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('Name', 'Server', 'Host')]
        [string[]]$ComputerName = $env:COMPUTERNAME,
        [PSCredential]$Credential
    )
    begin {
        Import-Module GroupPolicy -ErrorAction Stop
    }



    process {
        foreach ($computer in $ComputerName) {
            try {
                # Step 1 -- Prepare GPO query parameters
                $GpoParams = @{}
                if ($computer -ne $env:COMPUTERNAME) {
                    $GpoParams['Server'] = $computer
                }
                if ($Credential) {
                    $GpoParams['Credential'] = $Credential
                }

                # Step 2 -- Retrieve all GPOs
                $AllGpos = Get-GPO -All @GpoParams

                # Step 3 -- Identify unused GPOs
                $UnusedGpos = @()

                foreach ($gpo in $AllGpos) {
                    # Step 4 -- Generate XML report for each GPO
                    $XmlReport = Get-GPOReport -Guid $gpo.Id -ReportType Xml @GpoParams

                    # Step 5 -- Load into XML object
                    [xml]$Doc = $XmlReport

                    # Step 6 -- Count link nodes under GPO/LinksTo
                    $LinkCount = $Doc.GPO.LinksTo.Link.Count

                    # Step 7 -- Add to unused list if no links found
                    if ($LinkCount -eq 0) {
                        $UnusedGpos += [PSCustomObject]@{
                            ComputerName    = $computer
                            Name            = $gpo.DisplayName
                            UserVersion     = [int]$Doc.GPO.UserVersion
                            ComputerVersion = [int]$Doc.GPO.ComputerVersion
                            Created         = $gpo.CreationTime
                            Modified        = $gpo.ModificationTime
                            ID              = $gpo.Id
                            Status          = 'Success'
                            CollectionTime  = (Get-Date).ToString('dd-MM-yyyy HH:mm:ss')
                        }
                    }
                }

                # Step 8 -- Return results
                if ($UnusedGpos.Count -eq 0) {
                    [PSCustomObject]@{
                        ComputerName   = $computer
                        Message        = "No unused GPOs found."
                        Status         = 'Success'
                        CollectionTime = (Get-Date).ToString('dd-MM-yyyy HH:mm:ss')
                    }
                } else {
                    $UnusedGpos | Sort-Object Created
                }
            } catch {
                [PSCustomObject]@{
                    ComputerName   = $computer
                    Error          = $_.Exception.Message
                    Status         = 'Failed'
                    CollectionTime = (Get-Date).ToString('dd-MM-yyyy HH:mm:ss')
                }
            }
        }
    }
}