Yeah, this error is a pain — you're trying to set permissions on something and Windows just refuses, spitting back 0x00000228. The ACL you passed didn't have the minimum required info. Let me show you the fix.
The Direct Fix
The fastest way out is to rebuild the ACL from scratch using PowerShell. Don't waste time trying to patch the broken one — Windows is telling you the whole structure is wrong.
# Get the current ACL and rewrite it properly
$path = "C:\Path\To\Your\File" # Replace with your target
$acl = Get-Acl -Path $path
# Create a fresh AccessRule with proper inheritance
$identity = "BUILTIN\Administrators"
$fileSystemRights = [System.Security.AccessControl.FileSystemRights]::FullControl
$inheritanceFlags = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
$propagationFlags = [System.Security.AccessControl.PropagationFlags]::None
$accessControlType = [System.Security.AccessControl.AccessControlType]::Allow
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$identity, $fileSystemRights, $inheritanceFlags, $propagationFlags, $accessControlType
)
# Clear existing rules and add the new one
$acl.SetAccessRuleProtection($false, $false)
$acl.ResetAccessRule($accessRule)
# Apply it
Set-Acl -Path $path -AclObject $acl
Run this as Administrator. If it throws Access Denied, you need SeSecurityPrivilege — more on that below.
Why This Works
What's actually happening here is that the original ACL was missing required inheritance flags or propagation flags. Windows security model demands that every ACE in an ACL has these fields populated. When you pass an ACL via API calls like SetSecurityInfo or NtSetSecurityObject and any ACE is incomplete, you get 0x00000228.
The reason the PowerShell script above works is that it explicitly sets ContainerInherit and ObjectInherit with PropagationFlags.None. That's the minimum valid combination for a folder that needs to propagate permissions to children. Without those flags, Windows sees the ACE as "not sufficiently defined" and refuses to apply it.
Also note: SetAccessRuleProtection($false, $false) tells Windows "this ACL isn't protected from parent inheritance." If you set it to $true, you'd be adding explicit deny rules as well, which isn't what we want here.
Less Common Variations
1. Registry ACLs
Same error can show up when editing registry keys. The fix is similar but uses RegistryAccessRule instead of FileSystemAccessRule:
$path = "HKLM:\SOFTWARE\YourKey"
$acl = Get-Acl -Path $path
$identity = "BUILTIN\Administrators"
$accessRule = New-Object System.Security.AccessControl.RegistryAccessRule(
$identity, "FullControl", "ContainerInherit", "None", "Allow"
)
$acl.ResetAccessRule($accessRule)
Set-Acl -Path $path -AclObject $acl
Watch out: registry ACLs don't allow ObjectInherit — only ContainerInherit makes sense here.
2. Service ACLs (SERVICE_SECURITY_INFO)
If you hit this when managing services via ChangeServiceConfig2, the fix is the same principle but using SDDL string format directly:
# Use SDDL string with all required fields
$sddl = "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)"
$sd = New-Object System.Security.AccessControl.CommonSecurityDescriptor $false, $false, $sddl
# Pass $sd to SetServiceObjectSecurity
That SDDL grants full control to SYSTEM and Administrators — the bare minimum Windows expects.
3. When SeSecurityPrivilege Is Missing
Sometimes you'll get 0x00000228 and an access denied, even as Administrator. That's because the security descriptor you're trying to set requires SeSecurityPrivilege (the right to manage SACLs). Here's how to enable it:
# Enable SeSecurityPrivilege for current process
$privilege = "SeSecurityPrivilege"
$tokenHandle = [System.Security.Principal.WindowsIdentity]::GetCurrent().Token
$luid = New-Object UInt64
[advapi32]::LookupPrivilegeValue($null, $privilege, [ref]$luid)
$tp = New-Object UInt64 2
[advapi32]::AdjustTokenPrivileges($tokenHandle, $false, [ref]$tp, 0, [IntPtr]::Zero, [IntPtr]::Zero)
You'll need P/Invoke for advapi32 functions — that code snippet is a sketch; in reality use System.Security.Principal.WindowsIdentity.RunAs with a new identity that has the privilege, or just run PowerShell as a user who already has it (usually LocalSystem).
Prevention
Stop building ACLs by hand unless you really have to. Use Get-Acl on a known-good object as a template, then modify only the identity and rights fields. That way you inherit all the correct flags automatically.
If you're writing C++ or C# code that calls SetSecurityInfo, always initialize your SECURITY_DESCRIPTOR with InitializeSecurityDescriptor and set the DACL via SetSecurityDescriptorDacl — don't skip the control flags. A null or partial descriptor will trigger this error every time.
And test on a VM first. This error can lock you out of files or registry keys if applied wrong. Trust me, been there.