Last active
July 11, 2024 17:27
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.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) | |
} | |
} |
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
Perfect! Just tested the other script and it works like a freight train 👍 Thx Sean!