How to List Unlinked GPOs in a Domain

A Group Policy object (GPO) can be associated with one or more Active Directory containers, such as a site, domain, or organizational unit. This association is referred to as the GPO's link. For a GPO's settings to be applied at a given level, the GPO must be linked to the level. Therefore, it's critical to track what GPOs are linked and to what level, and to know which GPOs are not linked at all. This article illustrates using the Group Policy Management Console (GPMC) Class Library to list all unlinked GPOs for a specified domain. The first section details step-by-step instructions on implementing the code, while the example that follows is a working Visual C# console application that can be modified to fit your specific needs.

To list the unlinked GPOs in a domain

  1. Add the Microsoft.GroupPolicy.Management assembly to your project.

  2. Insert a using directive for the Microsoft.GroupPolicy namespace.

    using Microsoft.GroupPolicy;
    
  3. Instantiate a GPDomain object representing the domain that contains, or will contain, the target GPO. The following code snippet shows two different ways of instantiating a GPDomain object. The first example calls the default GPDomain constructor, which takes no parameters and uses the domain of the current user. The second example shows a hard-coded value being passed as the domain name that will be used in the construction of the GPDomain object.

    // Use current user’s domain 
    GPDomain domain = new GPDomain();
    
    // Use supplied domain name 
    GPDomain domain = new GPDomain("MyDomain");
    
  4. Get a collection representing all of the GPOs. This is done by instantiating a GPSearchCriteria object and then calling the GPDomain.SearchGPOs method. Because no filtering criteria was added to the GPSearchCriteria object (via its Add method), the returned collection from GPDomain.SearchGPOs will be all GPOs for the domain.

    // Get all GPOs in the domain
    GPSearchCriteria searchCriteria = new GPSearchCriteria();
    GpoCollection gpos = domain.SearchGpos(searchCriteria);
    
  5. Iterate over the GPO collection. As with any .NET collection, this task is accomplished quite easily via the foreach command.

    foreach(Gpo gpo in gpos)
    {
    ...
    }
    
  6. Within the foreach loop, acquire the SOM collection for each GPO. An SOM (Scope of Management) reflects where and when a GPO might apply. Therefore, the SOM will tell us where a given GPO is linked, or if it's linked at all. To get the SOM collection for a given GPO, you must first instantiate a GPSearchCriteria object. You then add a rule to the object (via the GPSearchCriteria.Add method) stating that you want all of the specified GPO's SOM links. Once you've done that, call the GPDomain.SearchSoms method, passing to it the GPSearchCriteria object. The GPDomain.SearchSoms method returns a SomCollection object. Inspecting the SomCollection object's Count property will indicate how many links have been established for the GPO in question.

    // Search for SOMs where this GPO is linked
    searchCriteria = new GPSearchCriteria();
    searchCriteria.Add(SearchProperty.SomLinks, SearchOperator.Contains, gpo);
    SomCollection soms = domain.SearchSoms(gpo);
    
    // If the SomCollection.Count property, that tells us that no links
    // exist for the specified GPO 
    if (soms.Count == 0) 
    {
      // Perform the logic here that you want for the unlinked GPO - such as 
      // writing the GPO DisplayName to a file for later reporting purposes.
    }
    

Example

The following is the complete code listing for a Microsoft Visual C# console application that lists all unlinked GPOs for a specified domain where the syntax is as follows. (Note that if no domain is specified, the current domain is used by default.)

ListUnlinkedGPOsInDomain [/d domainName]

/*----------------------------------------------------------------------
This file is part of the Microsoft GPMC API Code Samples.

DISCLAIMER OF WARRANTY: THIS CODE AND INFORMATION ARE PROVIDED “AS-IS.”  
YOU BEAR THE RISK OF USING IT.  MICROSOFT GIVES NO EXPRESS WARRANTIES, 
GUARANTEES OR CONDITIONS.  YOU MAY HAVE ADDITIONAL CONSUMER RIGHTS 
UNDER YOUR LOCAL LAWS WHICH THIS AGREEMENT CANNOT CHANGE.  
TO THE EXTENT PERMITTED UNDER YOUR LOCAL LAWS, MICROSOFT EXCLUDES 
THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
PURPOSE AND NON-INFRINGEMENT.
----------------------------------------------------------------------*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.GroupPolicy;

namespace GpmcSamples
{
  class ListUnlinkedGPOsInDomain
  {
    string domainName;

    static void Main(string[] args)
    {
      ListUnlinkedGPOsInDomain program = new ListUnlinkedGPOsInDomain();
      program.Run(args);
    }
    void Run(string[] args)
    {
      GPDomain domain = null;
      int unlinkedGPOs = -1; // means we didn't get a valid value from ListUnlinkedGPOs
      
      try
      {
        if (ParseParameters(args))
        {
          domain = SetDomainObject();
          unlinkedGPOs = ListUnlinkedGPOs(domain);
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }
      finally
      {
        PrintSummary(domain, unlinkedGPOs);
      }
    }

    bool ParseParameters(string[] args)
    {
      // Check for syntax help request
      if (Array.IndexOf(args, "/?") != -1)
      {
        return PrintSyntax();
      }

      int idx;

      // Get the domain name. If not supplied, we will use the current domain
      this.domainName = String.Empty;
      if ((idx = Array.IndexOf(args, "/d")) != -1)
      {
        // Save the user-specified domain name for later use in instantiating the GPDomain object
        this.domainName = args[idx + 1];
      }

      return true; // successful parsing
    }

    bool PrintSyntax()
    {
      Console.WriteLine("Syntax: ListUnlinkedGPOsInDomain [/d domainName]\r\n" +
                        "(Note that the doman defaults to the current domain if one is not specified.)");
      return false; // if we got here the command line parsing failed
    }

    GPDomain SetDomainObject()
    {
      GPDomain domain = null;
      
      // Get a GPDomain object
      Console.WriteLine("Connecting to domain '{0}'...",
                        (domainName.Length > 0) ? domainName : "[default domain]");
      if (domainName.Length > 0)
      {
        domain = new GPDomain(domainName);
      }
      else
      {
        domain = new GPDomain();
      }
      Console.WriteLine("Successfully connected to {0}", domain.DomainName);

      return domain;
    }

    int ListUnlinkedGPOs(GPDomain domain)
    {
      int unlinkedGPOs = 0;
      
      // Get all GPOs in the domain
      GPSearchCriteria searchCriteria = new GPSearchCriteria();
      GpoCollection gpos = domain.SearchGpos(searchCriteria);
      
      foreach(Gpo gpo in gpos)
      {
        // Search for SOMs where this GPO is linked
        searchCriteria = new GPSearchCriteria();
        searchCriteria.Add(SearchProperty.SomLinks, SearchOperator.Contains, gpo);
        SomCollection soms = domain.SearchSoms(gpo);
      
        // If the SomCollection.Count property, that tells us that no links
        // exist for the specified GPO 
        if (soms.Count == 0) 
        {
          Console.WriteLine("\t{0}: {1}", ++unlinkedGPOs, gpo.DisplayName);
        }
      }
      
      return unlinkedGPOs;
    }

    void PrintSummary(GPDomain domain, int unlinkedGPOs)
    {
      if (unlinkedGPOs > -1)
      {
        Console.WriteLine("{0} Unlinked GPO{1} found in domain {2}",
                          unlinkedGPOs, 
                          (unlinkedGPOs == 1 ? "" : "s"),
                          domain.DomainName);
      }
    }
  }
}