Script to modify the defaultSecurityDescriptor attribute on the Group-Policy-Container schema class object

by Jeremy Saunders on June 29, 2016

Last week I published an article about the changes in the behavior of Group Policy processing after the deployment of security update MS16-072 under KB3163622. It included a script to assist with the remediation of Group Policy permissions: Script to report on and remediate the Group Policy security change in MS16-072.

Of course that’s not where it ends. What about new Group Policies? Do you create a procedure that requires you to add “Domain Computers” with Read permission every time you create a new Group Policy Object (GPO)? No…of course not!

What we need to do now is change the defaultSecurityDescriptor attribute on the Group-Policy-Container schema class object so that new GPOs are created with Domain Computers having Read permissions by default. Microsoft didn’t released an official script or method to do this, so here’s the next best thing.

I will note that Darren Mar-Elia created a blog post to show you how to manually do this. Jeremy Moskowitz also included these steps in his article.

I’m a big fan of scripting, and already had a script to this that I wrote back in 2011 using the Quest ActiveRoles Snapin Module. I wrote it for a delegated administrative model I implemented for a large University. I also recently found a great script from Peter HinchleySet Default Permissions for New Group Policy Objects. So I took both scripts and merged the ideas, basing a new script on Peter’s. The outcome was a very simple and repeatable way to modify the defaultSecurityDescriptor on the Group-Policy-Container schema class object without the need to go through the steps that Darren and Jeremy laid out.

Before running the script you (or the account you’re running the script as) must be a member of the Schema Admins group.

Run the script with no parameters (in report only mode) and it will report on the existing Security Descriptor, and what the new one will look like. So it’s basically showing you the projected outcome. The following screen shot shows that DC (the acronym for the well-known SID of Domain Computers) is added with Read permissions.

Group-Policy-Container DefaultSecurityDescriptor - Before Change

So how do we know that we’re applying Read permissions? Well I guess you need to understand a little bit about the Security Descriptor Definition Language (SDDL). Without going too deeply into SDDL in this article, the specific permission we are adding here is made up of the following 6 fields:

ACE Type:

  • A = Access Allowed

ACE Flags:

  • CI = Container Inherit


  • LC = List Contents
  • RP = Read All Properties
  • LO = List Object
  • RC = Read Permissions


  • (nothing added here, hence why the field is blank)

Inherited Object Type:

  • (nothing added here, hence why the field is blank)


  • DC = Domain Computers

Running the script again with the -Action parameter applies the change as demonstrated in the following screen shot.

Group-Policy-Container DefaultSecurityDescriptor - After Change

So now when you create a new Group Policy Object (GPO), Domain Computers will have Read permissions by default!

New GPO Permission Test

How easy is that?

I’ve made this script as generic as possible so that it can be used for other purposes too.

Here is the Modify-GroupPolicyContainer.ps1 (362 downloads)  script:

  This script will modify the defaultSecurityDescriptor attribute on the Group-Policy-Container
  schema class object, which ensures that a specific set of permissions are applied by default
  to new Group Policy Objects (GPOs).

  It's been setup to add groups with either/or:
  - Modify Permissions
  - Read Permissions

  This script was specifically written to overcome the Group Policy security change Microsoft
  made in security update MS16-072 (KB3163622) by ensuring that Domain Computers is set with
  default Read permissions.

  This is the defaultSecurityDescriptor of the Group-Policy-Container class object:

  - (A;CI;RPLCLORC;;;AU) = Authenticated Users
  - (OA;CI;CR;edacfd8f-ffb3-11d1-b41d-00a0c968f939;;AU) = Authenticated Users
  - (A;CI;LCRPLORC;;;ED) = Enterprise Domain Controllers

  This translates to the following:

  ACE Type:
  ACE Flags:
  - CI = CONTAINER INHERIT: Child objects that are containers, such as directories, inherit the ACE as an explicit ACE.

  - RC = Read Permissions
  - SD = Delete
  - WD = Modify Permissions
  - WO = Modify Owner
  - RP = Read All Properties
  - WP = Write All Properties
  - CC = Create All Child Objects
  - DC = Delete All Child Objects
  - LC = List Contents
  - SW = All Validated Writes
  - LO = List Object
  - DT = Delete Subtree
  - CR = All Extended Rights

  - DA = Domain Admins
  - EA = Enterprise Admins
  - CO = Creator Owner
  - SY = System
  - AU = Authenticated Users
  - ED = Enterprise Domain Controllers

  So we simply need to append these:
  - (A;CI;RPWPCCDCLCLOLORCWOWDSDDTSW;;;<Creator Owners Group Sid>)
  - (A;CI;LCRPLORC;;;<Read Only Group Sid>)

  Some good references to help you understand this further:

  This script is based on:
  - A script I originally wrote on 11th November 2011 using the Quest ActiveRoles Snapin Module
    for a delegated administrative model for a large University.
  - A script published by Peter Hinchley 10th Oct 2015: Set Default Permissions for New Group Policy Objects

  Syntax examples:

  - To execute the script in report only mode:

  - To execute the script and take action:
      Modify-GroupPolicyContainer.ps1 -Action

  Script name: Modify-GroupPolicyContainer.ps1
  Release 1.2
  Written by Jeremy Saunders ( 11th November 2011
  Modified by Jeremy Saunders ( 28th June 2016



# Set Powershell Compatibility Mode
Set-StrictMode -Version 2.0


# Set the Read Permissions Group Name.
$ReadOnlyGroup = "Domain Computers"

# Set the Modify Permissions Group Name.
$ModifyGroup = ""

# Set this to True to reset the base security descriptor to default settings.
$ResetDefault = $False
$defaultSecurityDescriptor = @"


# You must be a member of the Schema Admins group to perform this task.

# Get Group Membership of current user
try {  
  $groups = (([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups | %{
  } | Sort) -join "`r`n"
} catch { "Groups could not be retrieved." }

# Check if current user is a member of Schema Admins
$IsMemberOfSchemaAdmins = $False
ForEach ($group in $groups) {
  If ($group -like "*\Schema Admins*") {
    $IsMemberOfSchemaAdmins = $True

If ($IsMemberOfSchemaAdmins) {
  write-verbose "The current user is a member of the Schema Admins group." -verbose

  # Import the Active Directory Module
  Import-Module ActiveDirectory -WarningAction SilentlyContinue

  # Get Domain information.
  $domain = Get-ADDomain

  # Get Schema Master FSMO role holder.
  $SchemaMaster = (Get-ADForest -Server $domain.Forest).SchemaMaster
  write-verbose "The Schema Master is: $SchemaMaster" -verbose

  # Get the Naming Context (NC) for the Schema
  $schemaNamingContext = (Get-ADRootDSE).schemaNamingContext

  # Get existing security descriptor for group policy container from schema partition in Active Directory.
  $descriptor = ($container = Get-ADObject -Server $SchemaMaster "CN=Group-Policy-Container,$schemaNamingContext" -Properties defaultSecurityDescriptor).defaultSecurityDescriptor
  write-verbose "The existing security descriptor is:" -verbose

  If ($ResetDefault) {
    $descriptor = $defaultSecurityDescriptor
    write-verbose "Resetting the security descriptor to default:" -verbose

  If ($ReadOnlyGroup -ne "") {
    write-verbose "Adding the read only group to the security descriptor." -verbose

    switch ($ReadOnlyGroup)
      "Domain Computers"    { # Use the commonly used acronym of DC for the well-known SID
                              $reader = "DC";
      "Authenticated Users" { # Use the commonly used acronym of AU for the well-known SID
                              $reader = "AU";
      default               { # Get SID of ReadOnlyGroup.
                              $reader = New-Object System.Security.Principal.NTAccount($domain.NetBIOSName, "$ReadOnlyGroup") 
                              $reader = $reader.Translate([System.Security.Principal.SecurityIdentifier]).value

    # Set the access control entry for the Read Only group.
    $descriptor = $descriptor + "(A;CI;LCRPLORC;;;$reader)"

  If ($ModifyGroup -ne "") {
    write-verbose "Adding the modify group to the security descriptor." -verbose
    # Get SID of ModifyGroup.
    $modifier = New-Object System.Security.Principal.NTAccount($domain.NetBIOSName, "$ModifyGroup") 
    $modifier = $modifier.Translate([System.Security.Principal.SecurityIdentifier]).value
    # Set the access control entry for the Modify group.
    $descriptor = $descriptor + "(A;CI;RPWPCCDCLCLORCWOWDSDDTSW;;;$modifier)"

  If ($Action) {
    # Concatenate the access control entries with the existing security descriptor.
    $container | Set-ADObject -Replace @{defaultSecurityDescriptor = "$descriptor";} -Server $SchemaMaster

    write-verbose "The new security descriptor after the change is:" -verbose
    (Get-ADObject -Server $SchemaMaster "CN=Group-Policy-Container,$schemaNamingContext" -Properties defaultSecurityDescriptor).defaultSecurityDescriptor
  } Else {
    write-verbose "The new security descriptor to be applied is:" -verbose

} Else {
  write-warning "The current user is NOT a member of the Schema Admins group." -verbose
  write-warning "This is a requirement to run this script." -verbose


Jeremy Saunders

Jeremy Saunders

Independent Consultant | Contractor | Microsoft & Citrix Specialist | Desktop Virtualization Specialist at J House Consulting
Jeremy is a highly respected, IT Professional, with over 30 years’ experience in the industry. He is an independent IT consultant providing expertise to enterprise, corporate, higher education and government clients. His skill set, high ethical standards, integrity, morals and attention to detail, coupled with his friendly nature and exceptional design and problem solving skills, makes him one of the most highly respected and sought after Microsoft and Citrix technical resources in Australia. His alignment with industry and vendor best practices puts him amongst the leaders of his field.
Jeremy Saunders
Jeremy Saunders
Jeremy Saunders
  • Nice work Jeremy, suffered this issue on our fleet. Article is a great help!

  • Vincent Dailly

    Just because I don’t like to work with strings 🙂

    $GPODefault = “CN=Group-Policy-Container”
    $GPOIdentity = “$GPODefault,$((Get-ADRootDSE).SchemaNamingContext)”
    $GPOSchema = Get-ADObject -Identity $GPOIdentity -Properties “defaultSecurityDescriptor”

    $GPOSchemaSD = new-object System.Security.AccessControl.RawSecurityDescriptor($GPOSchema.defaultSecurityDescriptor)

    #Domain Computers
    #from well-known SID
    $DomainComputers = new-object System.Security.Principal.SecurityIdentifier `

    $GPOSchemaACE = New-Object System.Security.AccessControl.CommonAce(
    $DomainComputers, $false, $null)
    $GPOSchemaSDNew = $GPOSchemaSD.GetSddlForm([System.Security.AccessControl.AccessControlSections]::All)

    #Uncomment to write to your AD
    #Set-ADObject -Identity $GPOIdentity -Replace @{defaultSecurityDescriptor=$GPOSchemaSDNew}

    • Nice code Vincent. Like with anything, and as the saying goes, there’s a million ways to skin a cat! I like working with strings because it’s easier to create/log output, and also easier for others to follow. Every script I write pretty much outputs to log files that can then be attached to change records, etc. Horses for courses 🙂

Previous post:

Next post: