Skip to content

Instantly share code, notes, and snippets.

@sean-m
Last active July 11, 2024 17:27
Show Gist options
  • Save sean-m/9b561244fe6669e88919 to your computer and use it in GitHub Desktop.
Save sean-m/9b561244fe6669e88919 to your computer and use it in GitHub Desktop.
Function takes the DN of a group and DN of user as input, sets the group manager as the prescribed user and checks that pesky "can modify group membership" box.
<#
.Synopsis
Takes Group DNs and User DNs and sets the user(s) as managers of the group(s).
.DESCRIPTION
Only changes needed for the group's configuration to match the request are
made, that is if the group already has the same managers in the specified
positions (managedBy vs msExchCoManagedByLink) then nothing is modified.
If no property changes are needed but the rights aren't set correctly,
the necessary ACL rules are applied and extraneous rules are removed.
Note: If no managers are specified, current managers are purged! This is
facilitated by the OGSet-GroupManger function in common.ps1.
.PARAMETER groupsDN
List of group distinguished names that will have managers set.
.PARAMETER managersDN
List of user distinguished names to set as group managers. Note, the first
entry in this list will be the primary manager set to the "managedBy" property.
#>
function Set-GroupManager {
[CmdletBinding()]
[OutputType([bool])]
Param
(
# request object
[Parameter(Mandatory=$true, ValueFromPipeline=$false, Position=0)]
[object[]]$groupsDN,
[Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true, Position=1)]
[object[]]$managersDN,
[switch]$purge
)
Begin
{
<# This should not be used directly! It has issues and is only here to purge
existing group managers if that function is required. #>
function OGSet-GroupManager {
[cmdletbinding()]
param (
[Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelinebyPropertyName=$True, Position=0)]
[string]$ManagerDN,
[Parameter(Mandatory=$true, ValueFromPipeline=$false, ValueFromPipelinebyPropertyName=$True, Position=1)]
[string]$GroupDN,
[switch]$Primary,
[switch]$Purge
)
try {
Import-Module ActiveDirectory -NoClobber
$grp = [ADSI]"LDAP://$GroupDN";
if ($grp -like $null) {
throw "Cannot find group: $GroupDN"
}
# Taken from here: http://blogs.msdn.com/b/dsadsi/archive/2013/07/09/setting-active-directory-object-permissions-using-powershell-and-system-directoryservices.aspx
[System.DirectoryServices.DirectoryEntryConfiguration]$SecOptions = $grp.get_Options();
$SecOptions.SecurityMasks = [System.DirectoryServices.SecurityMasks]'Dacl'
# Remove rules that grant mod rights to redundant folks, ie: previous group managers.
if ($Purge) {
$mgrRules = $grp.get_ObjectSecurity().Access | ? {`
-not $_.IsInherited `
-and $_.ActiveDirectoryRights -eq "WriteProperty" `
-and $_.AccessControlType -eq "Allow" `
-and $_.ObjectType -eq "bf9679c0-0de6-11d0-a285-00aa003049e2" }
# Remove manager DACL rules
$results = @{}
$mgrRules | % { $key = $_.IdentityReference.ToString();
[bool]$result = $grp.get_ObjectSecurity().RemoveAccessRule($_);
$grp.CommitChanges();
$results.Add($key,$result); }
Set-ADGroup -Identity $GroupDN -Clear msExchCoManagedByLink
Set-ADGroup -Identity $GroupDN -Clear managedBy
$grp.Dispose()
# Wait for things to settle
Start-Sleep -Seconds 5
}
if (-not [String]::IsNullOrEmpty($ManagerDN)) {
$grp = [ADSI]"LDAP://$GroupDN";
# Taken from here: http://blogs.msdn.com/b/dsadsi/archive/2013/07/09/setting-active-directory-object-permissions-using-powershell-and-system-directoryservices.aspx
[System.DirectoryServices.DirectoryEntryConfiguration]$SecOptions = $grp.get_Options();
$SecOptions.SecurityMasks = [System.DirectoryServices.SecurityMasks]'Dacl'
$mgr = [ADSI]"LDAP://$ManagerDN";
if ($mgr -eq $null) {
throw "Cannot find manager user: $ManagerDN"
}
$grp.managedBy | Out-Null
$identityRef = (Get-ADUser -Filter {DistinguishedName -like $ManagerDN}).SID.Value
$sid = New-Object System.Security.Principal.SecurityIdentifier ($identityRef);
$adRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($sid, `
[System.DirectoryServices.ActiveDirectoryRights]::WriteProperty, `
[System.Security.AccessControl.AccessControlType]::Allow, `
[Guid]"bf9679c0-0de6-11d0-a285-00aa003049e2");
## Check if this is setting the first or extra managers
[string]$pre_mgBy = $grp.managedBy
$setPrimary = $false;
if ($Primary `
-or ([String]::IsNullOrEmpty($pre_mgBy)) `
-or ([String]::IsNullOrEmpty($grp.managedBy))) {
$setPrimary = $true
$grp.InvokeSet("managedBy", @("$ManagerDN"));
$grp.CommitChanges();
}
$grp.get_ObjectSecurity().AddAccessRule($adRule);
$grp.CommitChanges();
if ((-not [String]::IsNullOrEmpty($pre_mgBy)) -and (-not $setPrimary)) {
Set-ADGroup -Identity $GroupDN -Add @{msExchCoManagedByLink=$ManagerDN}
}
}
}
catch {
Write-Debug "Group manager DACL entries:"
Write-Error "Failed setting $ManagerDN as manager of $GroupDN"
$mgrRules
throw $Error[1]
}
}
Import-Module ActiveDirectory -NoClobber
## Needed for meaningful feedback when throwing exception
$failedMgr = @()
## Resolve a site-local domain controller
$domain_controller = ""
$ad_site = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::GetComputerSite().Name
$tries = 0
$check_again = $true
while ($check_again) {
$dc = $null
if ($ad_site) {
$dc = Get-ADDomainController -Discover -ForceDiscover -SiteName $ad_site
$domain_controller = $dc.HostName[0].ToString()
}
else {
$dc = Get-ADDomainController -Discover -ForceDiscover
$domain_controller = $dc.HostName[0].ToString()
}
$check = [ADSI]"LDAP://$domain_controller/$($dc.DefaultPartition)"
$check_again = -not [bool]$($check)
$tries++
if ($tries -eq 5) { throw "Can't find responding domain controller after 5 tries!" }
}
Write-Debug "Domain controller: $domain_controller"
}
Process {
Write-Debug "In: Process-SetGroupManagerTask"
# Get manager user account
if ([bool]@($managersDN)) {
Write-Debug "Domain controller: $domain_controller"
$mgr = @($managersDN | % {Get-AdUser -Identity $_ -Server $domain_controller})
}
# Fail if manager user not found
if ($mgr -eq $null -and [bool]@($managersDN)) {
throw "Cannot find specified manager(s) in AD"
}
# Collect security group objects
$grps = @($groupsDN | % { Get-ADGroup -Properties managedBy,msExchCoManagedByLink -Identity $_ -Server $domain_controller})
if ($grps -eq $null) {
## Can't find groups, fail
throw [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] `
"Could not resolve security groups"
}
# Set manager(s) on groups
foreach ($group in $grps) {
Write-Debug "Setting managers for: $($group.Name)"
$grpDn = $group.DistinguishedName
[string]$primary_manager = [String]::Empty
[string[]]$co_managers = @()
[string]$cur_primary_manager = [String]::Empty
[string[]]$cur_co_managers = @()
## Determine operations to perform per manager
$current_mgrs = @()
$current_mgrSid = @()
$current_mgrs += $group.ManagedBy | ? { $_ -ne $null } | % { Get-ADObject -Server $domain_controller -Identity $_ -Properties SamAccountName,objectSid,userAccountControl}
$current_mgrs += $group.msExchCoManagedByLink | ? { $_ -ne $null } | % { Get-ADObject -Server $domain_controller -Identity $_ -Properties SamAccountName,objectSid,userAccountControl}
if ($current_mgrs) { $current_mgrSid += $($current_mgrs.objectSid) | Select -ExpandProperty Value}
$cur_primary_manager = $group.ManagedBy
$cur_co_managers = @($group.msExchCoManagedByLink)
$req_mgrs = @()
$req_mgrs += $mgr.SID.Value
## Don't want any managers so purge what's there
if (-not $req_mgrs -and $purge) {
Write-Warning "No managers requested, purging managers!"
OGSet-GroupManager -GroupDN $group.DistinguishedName -Purge
continue
}
elseif ($req_mgrs -and $purge) {
Write-Warning "Managers requested and purge set, options are mutually exclusive. Skipping.."
continue
}
## Determine what changes are needed to get group
## to desired state based on diff with current state
$cmp = Compare-Object @($current_mgrSid) @($req_mgrs)
$operations = @{}
$total_mgrs = New-Object "System.Collections.Generic.List[string]"
$req_mgrs.ForEach({$total_mgrs.Add($PSItem)})
$current_mgrSid.ForEach({$total_mgrs.Add($PSItem)})
foreach ($c in $cmp) {
switch ($c.SideIndicator) {
"=>" {
$operations.Add($c.InputObject,"Add")
break
}
"<=" {
$operations.Add($c.InputObject,"Remove")
$total_mgrs.Remove($c.InputObject)
break
}
}
}
Write-Debug "Pending group manager changes:`n$($operations | FT -a | Out-String)"
$mgr_dns = @($total_mgrs | select -Unique | % { Get-AdUser -Filter {SID -eq $_} -Server $domain_controller } | select -ExpandProperty DistinguishedName)
[string]$primary_manager = ""
[string[]]$co_managers = @()
if ($mgr_dns) {
$primary_manager = $mgr_dns[0]
if ($mgr_dns.Count -gt 1) {
$co_managers += $mgr_dns[1..$($mgr_dns.Count - 1)]
}
}
## Determine which modifications to apply
[bool]$set_primary = (-not [String]::IsNullOrEmpty($primary_manager) `
-and $primary_manager -notlike $cur_primary_manager)
[bool]$set_co_managers = ($co_managers -or (Compare-Object $co_managers $cur_co_managers))
[string[]]$co_manager_add = @((Compare-Object $co_managers $cur_co_managers | ? { $_.SideIndicator -like "<=" }).InputObject)
[string[]]$co_manager_rm = @((Compare-Object $co_managers $cur_co_managers | ? { $_.SideIndicator -like "=>" }).InputObject)
Write-Debug "Primary manager: $primary_manager"
Write-Debug "Co-Managers:`n$($co_managers | FT -a | Out-String)"
## Perform group property modifications
$gdn = $group.DistinguishedName
# Check group owner and set accordingly
$grp = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$domain_controller/$gdn"
if (-not ($grp.ObjectSecurity.Owner -like "DHS\svc_pssta")) {
Write-Debug "Group owned by: $($grp.ObjectSecurity.Owner), resetting to DHS\svc_pssta"
$owner = New-Object System.Security.Principal.NTAccount("DHS","svc_pssta")
$grp.ObjectSecurity.SetOwner($owner)
$grp.CommitChanges()
$grp.RefreshCache()
}
# Set manager properties
if ($set_primary -and $set_co_managers) {
if ($co_manager_add -and $co_manager_rm) {
Write-Debug "Set primary, co-manager add and remove"
Set-ADGroup -Identity $gdn -Add @{msExchCoManagedByLink=$co_manager_add} -Remove @{msExchCoManagedByLink=$co_manager_rm} -ManagedBy $primary_manager -Server $domain_controller
}
elseif ($co_manager_add) {
Write-Debug "Set primary, co-manager add"
Set-ADGroup -Identity $gdn -Add @{msExchCoManagedByLink=$co_manager_add} -ManagedBy $primary_manager -Server $domain_controller
}
elseif ($co_manager_rm) {
Write-Debug "Set primary, co-manager remove"
Set-ADGroup -Identity $gdn -Remove @{msExchCoManagedByLink=$co_manager_rm} -ManagedBy $primary_manager -Server $domain_controller
}
Start-Sleep -Seconds 2
}
if ($set_co_managers -and -not $set_primary) {
Write-Debug "Setting co-manager(s)"
if ($co_manager_add -and $co_manager_rm) {
Write-Debug "Co-manager add and remove"
Set-ADGroup -Identity $gdn -Add @{msExchCoManagedByLink=$co_manager_add} -Remove @{msExchCoManagedByLink=$co_manager_rm} -Server $domain_controller
}
elseif ($co_manager_add) {
Write-Debug "Co-manager add"
Set-ADGroup -Identity $gdn -Add @{msExchCoManagedByLink=$co_manager_add} -Server $domain_controller
}
elseif ($co_manager_rm) {
Write-Debug "Co-manager remove"
Set-ADGroup -Identity $gdn -Remove @{msExchCoManagedByLink=$co_manager_rm} -Server $domain_controller
}
Start-Sleep -Seconds 2
}
if ($set_primary -and -not $set_co_managers) {
Write-Debug "Only setting a primary manager"
Set-ADGroup -Identity $gdn -ManagedBy $primary_manager -Server $domain_controller
Start-Sleep -Seconds 2
}
if ((-not $set_primary) -and (-not $set_co_managers)) {
"Making no changes to group's managers"
}
## Setting access control
[bool]$changedACL = $false
$mgrs_with_rights = @()
$mgrs_with_rights += $primary_manager | % { if (-not [String]::IsNullOrEmpty($_)) { Get-ADObject -Server $domain_controller -Identity $_ -Properties SamAccountName,objectSid,userAccountControl } }
$mgrs_with_rights += $co_managers | ? { $_ -ne $null } | % { Get-ADObject -Server $domain_controller -Identity $_ -Properties SamAccountName,objectSid,userAccountControl}
$mgrs_with_rights_ids = @($mgrs_with_rights | ? { $_ -ne $null } | % { "$env:USERDOMAIN\$($_.SamAccountName)" })
# Get group
$grp = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$domain_controller/$gdn"
if (-not $grp) { throw "Cannot locate group to set ACL: LDAP://$domain_controller/$gdn" }
## Get list of rules which grant modify group member rights
$mgr_rules = $grp.ObjectSecurity.Access | ? {`
-not $_.IsInherited `
-and $_.ActiveDirectoryRights -eq "WriteProperty" `
-and $_.AccessControlType -eq "Allow" `
-and $_.ObjectType -eq "bf9679c0-0de6-11d0-a285-00aa003049e2" }
## Remove rules for anyone not in the intended managers list
$mgr_rules | % { if (-not $mgrs_with_rights_ids.Contains($_.IdentityReference.ToString())) {
$changedACL = $true
Write-Debug "Removing rule for: $($_.IdentityReference.ToString())"
$grp.ObjectSecurity.RemoveAccessRule($_)
} }
## Get identity reference strings for any manager rules left
$mgr_rule_idrefs = @($grp.ObjectSecurity.Access | ? {`
-not $_.IsInherited `
-and $_.ActiveDirectoryRights -eq "WriteProperty" `
-and $_.AccessControlType -eq "Allow" `
-and $_.ObjectType -eq "bf9679c0-0de6-11d0-a285-00aa003049e2" } | % { $_.IdentityReference.ToString() })
## Add rules for intended managers
$mgrs_with_rights | % {
if (-not $mgr_rule_idrefs.Contains("$env:USERDOMAIN\$($_.SamAccountName)")) {
$changedACL = $true
$identityRef = $_.objectSid.Value
$sid = New-Object System.Security.Principal.SecurityIdentifier ($identityRef);
$adRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($sid, `
[System.DirectoryServices.ActiveDirectoryRights]::WriteProperty, `
[System.Security.AccessControl.AccessControlType]::Allow, `
[Guid]"bf9679c0-0de6-11d0-a285-00aa003049e2");
Write-Debug "Adding access rule for: $($_.Name)"
$grp.ObjectSecurity.AddAccessRule($adRule)
}
}
## Set ACL rules
if ($changedACL) {
Write-Debug "Setting group ACL"
$grp.CommitChanges();
}
else {
Write-Debug "No ACL changes made"
}
Write-Debug ""
} ## foreach ($group in $grps)
}
}
@tehmilcho
Copy link

Hey,
There is noway to get this run "manager can update membership list" different credentials, right?

I have to set this in a Azure Runbook but the runbooks/Powershell code will be executed as "System" from the worker I can't find any cmdlet/code that can check the checkbox and can give a different cred to do it =(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment