What I am trying to do: I want to add the user "octo" as a member of "Key Admins". After that, I want to add "Key Admins" to AdminSDHolder and set both write and read permissions on the msDS-KeyCredentialLink attribute for "Key Admins". I have written a PowerShell script to accomplish this, but I encounter the current error when running it.
I am trying to set permissions on the AdminSDHolder object in Active Directory using PowerShell. However, I am encountering an error when creating an instance of the System.DirectoryServices.ActiveDirectoryAccessRule class. Here is the code I am using:
function Set-KeyCredentialLinkPermissions {
param (
[Parameter(Mandatory = $true)]
[string] $groupName,
[Parameter(Mandatory = $true)]
[string] $userToAdd
)
# Add the user to the group
try {
Add-ADGroupMember -Identity $groupName -Members $userToAdd
Write-Output "$userToAdd was successfully added to the $groupName group."
} catch {
Write-Error "An error occurred while adding the user to the group: $_"
return
}
# Get the DistinguishedName of the AdminSDHolder object
$adminSDHolderDN = (Get-ADObject -Filter { Name -eq "AdminSDHolder" } -Properties DistinguishedName).DistinguishedName
try {
# Connect to the AdminSDHolder object
$adsi = [ADSI]"LDAP://$adminSDHolderDN"
# Get the ObjectSID of the group
$groupSID = (Get-ADGroup -Identity $groupName -Properties ObjectSID).ObjectSID
# Convert the SID to string format
$identityReference = $groupSID.Value
# GUID for msDS-KeyCredentialLink attribute
$guidKeyCredentialLink = "2f68b1d6-f5ae-4cb6-b34f-8108b3409d6b" # msDS-KeyCredentialLink GUID
# Get the current ACL
$acl = $adsi.ObjectSecurity
# Optionally clear existing rules (to leave only msDS-KeyCredentialLink permissions)
$acl.Access | Where-Object { $_.IdentityReference -eq $identityReference } | ForEach-Object {
$acl.RemoveAccessRule($_)
}
# Create new access rules
$writeRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$identityReference,
[System.DirectoryServices.ActiveDirectoryRights]::WriteProperty,
[System.Security.AccessControl.AccessControlType]::Allow,
[System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendants,
$guidKeyCredentialLink)
$readRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$identityReference,
[System.DirectoryServices.ActiveDirectoryRights]::ReadProperty,
[System.Security.AccessControl.AccessControlType]::Allow,
[System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendants,
$guidKeyCredentialLink)
# Set general read permission (for descendant user objects)
$readAllRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$identityReference,
[System.DirectoryServices.ActiveDirectoryRights]::GenericRead,
[System.Security.AccessControl.AccessControlType]::Allow,
[System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendants,
[guid]::Empty.ToString())
# Add access rules and commit changes
$acl.AddAccessRule($writeRule)
$acl.AddAccessRule($readRule)
$acl.AddAccessRule($readAllRule)
$adsi.ObjectSecurity = $acl
$adsi.CommitChanges()
Write-Output "Permissions for msDS-KeyCredentialLink were successfully set on the AdminSDHolder object."
} catch {
Write-Error "An error occurred while setting msDS-KeyCredentialLink permissions on the AdminSDHolder object: $_"
}
}
# Run the function
Set-KeyCredentialLinkPermissions -groupName "Key Admins" -userToAdd "octo"
However, I encounter the following error when running the code:
Set-KeyCredentialLinkPermissions: C:\Users\Administrator\Desktop\msDS-KeyCredentialLink.ps1:80
Line |
80 | Set-KeyCredentialLinkPermissions -groupName "Key Admins" -userToAdd " …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| AdminSDHolder nesnesinde msDS-KeyCredentialLink izinleri ayarlanirken bir hata olustu: Cannot find an overload
| for "ActiveDirectoryAccessRule" and the argument count: "5".
I created a PowerShell script to achieve the following goals:
Add the user "octo" to the "Key Admins" group.
Add the "Key Admins" group to the AdminSDHolder object.
Set both write and read permissions on the msDS-KeyCredentialLink attribute for the "Key Admins" group.
Note: In the case at hand you're targeting a .NET type's constructor, but since constructors as just special methods, the following also applies to methods.
You're trying to call this 5-parameter constructor overload of the [System.DirectoryServices.ActiveDirectoryAccessRule]
constructor.
Unfortunately, the error message that PowerShell emits - Cannot find an overload for "ActiveDirectoryAccessRule" and the argument count: "5"
- is misleading in the case at hand, because it isn't the count of the arguments that is the problem, but their types.
In cases where the argument types are the problem, the implication is that they don't match the parameter types exactly or cannot be converted to them.
While PowerShell is quite flexible with respect to the automatic type conversion it attempts to find a suitable overload, it is best to make the argument types match the parameter types exactly, both for conceptual clarity and long-term stability.
Your specific problem was primarily a typo:
[System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendants
must be [System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendents
(e
rather than a
as the last vowel) - this despite the fact that the a
spelling, because it refers to a noun, is linguistically the correct spelling.
If the error message included the argument types that were passed (as it may in the future), the problem would be obvious: Since the [System.DirectoryServices.ActiveDirectorySecurityInheritance]
has no static property named Descendants
, the whole expression evaluates to $null
, which prevented PowerShell from finding the right overload.
If you had used Set-StrictMode
-Version 2
or above, PowerShell would have reported an error for the attempt to access a non-existent property; however, Set-StrictMode
has notable pitfalls:
Set-StrictMode
is dynamically rather than lexically scoped, which means that all code invoked from a scope in which it is in effect is also affected - and such code may not have been designed for that and therefore break; providing a lexically scoped strict mode to remedy that is the subject of this RFC.
With Set-StrictMode -Version 2
or higher, the implicitly added .Count
property that unifies PowerShell's handling of scalars and collections is then not available - this should be considered a bug, and has been reported in GitHub issue #2798.
Note that PowerShell freely converts string representations of enumeration values to the latter, so if you had tried to pass 'Descendants'
in a context where a [System.DirectoryServices.ActiveDirectorySecurityInheritance]
is expected, an error would have occurred even without a strict mode in effect.
Thus, to avoid referencing a non-existent enumeration value independently of any strict mode, you could either use tab completion at design time, or use a cast, in which case the problem would become obvious:
# Fails with 'Descendants', succeeds with 'Descendents'
[System.DirectoryServices.ActiveDirectorySecurityInheritance] 'Descendants'
Additionally, in the interest of exact type matching, [guid]::Empty.ToString()
should be [guid]::Empty
, and $guidKeyCredentialLink
should be [guid] $guidKeyCredentialLink
Finally, you should avoid using pseudo method syntax in your New-Object
call:
Instead of New-Object SomeType(arg1, ...)
, use New-Object SomeType [-ArgumentList] arg1, ...
- PowerShell cmdlets, scripts and functions are invoked like shell commands, not like methods. That is, no parentheses around the argument list, and whitespace-separated arguments (,
constructs an array as a single argument, as needed for -ArgumentList
). See this answer for details.
However, method syntax is required if you use the PSv5+ [SomeType]::new()
constructor-call method (i.e. if you use the intrinsic static ::new()
method), as shown below, which is generally preferable to using New-Object
.
To put it all together, using the last constructor call as an example:
$readAllRule =
[System.DirectoryServices.ActiveDirectoryAccessRule]::new(
$identityReference,
[System.DirectoryServices.ActiveDirectoryRights]::GenericRead,
[System.Security.AccessControl.AccessControlType]::Allow,
[System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendents,
[guid]::Empty
)
[1] This is an unavoidable consequence of PowerShell being a late-bound language, and it is why PowerShell-native solutions are generally preferable to .NET method calls, if available.
The alternative is to match the constructor / method signature precisely, using casts, if necessary, which is the only way to guarantee long-term stability.
For a real-life example affecting the System.String.Split
method, to which new overloads were added in .NET (Core) and therefore PowerShell (Core) 7, see the bottom section of this answer.