Public/New-AdDelegatedGroup.ps1
function New-AdDelegatedGroup { <# .SYNOPSIS Creates or modifies AD groups with enhanced security settings and error handling. .DESCRIPTION Creates a new Active Directory group or modifies an existing one with additional security settings beyond the standard New-ADGroup cmdlet capabilities. The function: - Handles group existence checks gracefully - Implements security best practices - Removes built-in groups from ACLs - Supports tiered administration model - Is fully idempotent - running multiple times produces same result The function follows the Active Directory tiering model and adheres to security best practices. .EXAMPLE New-AdDelegatedGroup -Name "Poor Admins" -GroupCategory Security -GroupScope DomainLocal -DisplayName "Poor Admins" -Path 'OU=Groups,OU=Admin,DC=EguibarIT,DC=local' -Description 'New Admin Group' -ProtectFromAccidentalDeletion -RemoveAuthUsers .EXAMPLE $splat = @{ Name = 'Poor Admins' GroupCategory = 'Security' GroupScope = 'DomainLocal' DisplayName = 'Poor Admins' Path = 'OU=Groups,OU=Admin,DC=EguibarIT,DC=local' Description = 'New Admin Group' ProtectFromAccidentalDeletion = $true } New-AdDelegatedGroup @Splat .PARAMETER Name Name of the group to be created (SamAccountName). Must be 1-256 characters, using only alphanumeric characters, spaces, hyphens, underscores, and periods. .PARAMETER GroupCategory Group category, either Security or Distribution. Security groups can be assigned permissions, Distribution groups cannot. .PARAMETER GroupScope Group Scope, either DomainLocal, Global or Universal. Determines the scope in which the group can be granted permissions and have members. .PARAMETER DisplayName Display Name of the group to be created. This is what appears in the Global Address List. .PARAMETER Path DistinguishedName of the container where the group will be created. Must be a valid organizational unit or container DN. .PARAMETER Description Description of the group. Helps document the purpose and usage of the group. .PARAMETER ProtectFromAccidentalDeletion When specified, sets the ProtectedFromAccidentalDeletion attribute to True. This prevents the group from being deleted without first removing this protection. .PARAMETER RemoveAccountOperators When specified, removes the Account Operators built-in group from the ACL. Improves security by limiting administrative access. .PARAMETER RemoveEveryone When specified, removes the Everyone built-in group from the ACL. Improves security by limiting anonymous access. .PARAMETER RemoveAuthUsers When specified, removes the Authenticated Users built-in group from the ACL. Improves security by limiting broad access to the group. .PARAMETER RemovePreWin2000 When specified, removes the Pre-Windows 2000 Compatible Access built-in group from the ACL. Improves security by removing legacy access. .INPUTS System.String You can pipe group name strings to this function. .OUTPUTS Microsoft.ActiveDirectory.Management.AdGroup Returns the newly created or modified AD group object. .NOTES Used Functions: Name ║ Module/Namespace ═══════════════════════════════════════╬══════════════════════════════ Get-FunctionDisplay ║ EguibarIT Remove-AccountOperator ║ EguibarIT.DelegationPS Remove-Everyone ║ EguibarIT.DelegationPS Remove-AuthUser ║ EguibarIT.DelegationPS Remove-PreWin2000 ║ EguibarIT.DelegationPS Get-AdGroup ║ ActiveDirectory Move-ADObject ║ ActiveDirectory New-ADGroup ║ ActiveDirectory Set-AdGroup ║ ActiveDirectory Set-AdObject ║ ActiveDirectory Write-Verbose ║ Microsoft.PowerShell.Utility Write-Debug ║ Microsoft.PowerShell.Utility .NOTES Version: 1.3 DateModified: 22/May/2025 LastModifiedBy: Vicente Rodriguez Eguibar vicente@eguibar.com Eguibar IT http://www.eguibarit.com .LINK https://github.com/vreguibar/EguibarIT/blob/main/Public/New-AdDelegatedGroup.ps1 .LINK https://www.eguibarit.com .COMPONENT Active Directory .ROLE Identity Management .FUNCTIONALITY Group Administration #> [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'Medium' )] [OutputType([Microsoft.ActiveDirectory.Management.AdGroup])] Param ( # Param1 Group which membership is to be changed [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $False, HelpMessage = 'Name of the group to be created. SamAccountName (1-256 chars, alphanumeric and -_.)', Position = 0)] [ValidateNotNullOrEmpty()] [ValidateLength(1, 256)] [ValidatePattern('^[A-Za-z0-9\s\-_.]+$')] [Alias('GroupName', 'GroupID', 'Identity', 'SamAccountName')] $Name, # Param2 Group category, either Security or Distribution [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $False, HelpMessage = 'Group category, either Security or Distribution', Position = 1)] [ValidateSet('Security', 'Distribution')] [ValidateNotNullOrEmpty()] $GroupCategory, # Param3 Group Scope, either DomainLocal, Global or Universal [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $False, HelpMessage = 'Group Scope, either DomainLocal, Global or Universal', Position = 2)] [ValidateSet('DomainLocal', 'Global', 'Universal')] [ValidateNotNullOrEmpty()] $GroupScope, # Param4 Display Name of the group to be created [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $False, HelpMessage = 'Display Name of the group to be created', Position = 3)] [ValidateNotNullOrEmpty()] [System.String] $DisplayName, # Param5 DistinguishedName of the container where the group will be created. [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $False, HelpMessage = 'DistinguishedName of the container where the group will be created.', Position = 4)] [ValidateNotNullOrEmpty()] [ValidateScript( { Test-IsValidDN -ObjectDN $_ }, ErrorMessage = 'DistinguishedName provided is not valid! Please Check.' )] [Alias('DN', 'DistinguishedName', 'LDAPpath')] [System.String] $path, # Param6 Description of the group. [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $False, HelpMessage = 'Description of the group.', Position = 5)] [ValidateNotNullOrEmpty()] [System.String] $Description, # Param7 Protect from accidental deletion. [Parameter(Mandatory = $False, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $False, HelpMessage = 'Protect from accidental deletion.', Position = 6)] [Switch] $ProtectFromAccidentalDeletion, # Param8 Remove Account Operators Built-In group. [Parameter(Mandatory = $False, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $False, HelpMessage = 'Remove Account Operators Built-In group', Position = 7)] [Switch] $RemoveAccountOperators, # Param9 Remove Everyone Built-In group. [Parameter(Mandatory = $False, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $False, HelpMessage = 'Remove Everyone Built-In group', Position = 8)] [Switch] $RemoveEveryone, # Param10 Remove Authenticated Users Built-In group. [Parameter(Mandatory = $False, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $False, HelpMessage = 'Remove Authenticated Users Built-In group', Position = 9)] [Switch] $RemoveAuthUsers, # Param11 Remove Pre-Windows 2000 Built-In group. [Parameter(Mandatory = $False, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $False, HelpMessage = 'Remove Pre-Windows 2000 Built-In group', Position = 10)] [Switch] $RemovePreWin2000, [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, HelpMessage = 'If present, the function will not ask for confirmation when performing actions.', Position = 11)] [Switch] $Force ) Begin { Set-StrictMode -Version Latest if ($null -ne $Variables -and $null -ne $Variables.Header) { $txt = ($Variables.Header -f (Get-Date).ToString('dd/MMM/yyyy'), $MyInvocation.Mycommand, (Get-FunctionDisplay -HashTable $PsBoundParameters -Verbose:$False) ) Write-Verbose -Message $txt } #end If ############################## # Module imports Import-MyModule -Name 'ActiveDirectory' -Verbose:$false Import-MyModule -Name 'EguibarIT.DelegationPS' -Verbose:$false ############################## # Variables Definition [hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase) $newGroup = [Microsoft.ActiveDirectory.Management.AdGroup]::New() } # End Begin Section Process { #Check if group exist $groupExists = Get-ADGroup -Filter { SamAccountName -eq $Name } -ErrorAction SilentlyContinue if (-not $groupExists) { Write-Debug -Message ('Group {0} does not exists. Creating it!' -f $Name) if ($PSCmdlet.ShouldProcess("$Name", 'Group does not exist. Should it be created?')) { Try { $Splat = @{ Name = $Name SamAccountName = $Name GroupCategory = $PSBoundParameters['GroupCategory'] GroupScope = $PSBoundParameters['GroupScope'] DisplayName = $PSBoundParameters['DisplayName'] path = $PSBoundParameters['path'] Description = $PSBoundParameters['Description'] ErrorAction = 'Stop' } $newGroup = New-ADGroup @Splat Write-Debug -Message ('Group {0} created successfully.' -f $Name) } catch { Write-Error -Message ('An error occurred while creating the group: {0})' -f $_.Exception.Message) throw } #end Try-Catch } #end If } else { Write-Warning -Message ('Groups {0} already exists. Modifying the group!' -f $PSBoundParameters['Name']) # Remove ProtectedFromAccidentalDeletion flag Set-ADObject -Identity $groupExists -ProtectedFromAccidentalDeletion $False # Modify existing group Try { $Splat = @{ Identity = $groupExists Description = $PSBoundParameters['Description'] DisplayName = $PSBoundParameters['DisplayName'] GroupCategory = $PSBoundParameters['GroupCategory'] GroupScope = $PSBoundParameters['GroupScope'] Passthru = $true ErrorAction = 'Stop' } if ($Force -or $PSCmdlet.ShouldProcess('Existing group. Should it be Modified?')) { $newGroup = Set-ADGroup @Splat if (-not $newGroup) { Start-Sleep 2 $newGroup = Get-ADGroup $groupExists } #end If Write-Debug -Message ('Existing group {0} modified.' -f $newGroup) } #end If If (-not($newGroup.DistinguishedName -contains $PSBoundParameters['path'])) { # Move object to the corresponding OU Move-ADObject -Identity $newGroup.DistinguishedName -TargetPath $PSBoundParameters['path'] -ErrorAction Stop } #end If } catch { Write-Error -Message ('An error occurred while creating the group: {0})' -f $_.Exception.Message) throw } #end Try-Catch } #end If-Else # Get the group again and store it on variable. try { $newGroup = Get-ADGroup -Filter { SamAccountName -eq $Name } -ErrorAction Stop Write-Debug -Message ('Refreshing group {0}' -f $name) } catch { Write-Error -Message ('Error while trying to refresh group {0}' -f $name) } #end Try-Catch # Protect From Accidental Deletion If ($PSBoundParameters['ProtectFromAccidentalDeletion']) { Set-ADObject -Identity $newGroup.DistinguishedName -ProtectedFromAccidentalDeletion $true Write-Debug -Message ('Group {0} Protect From Accidental Deletion' -f $name) } #end If # Remove Account Operators Built-In group If ($PSBoundParameters['RemoveAccountOperators']) { Remove-AccountOperator -LDAPPath $newGroup.DistinguishedName Write-Debug -Message ('Group {0} Remove Account Operators' -f $name) } #end If # Remove Everyone Built-In group If ($PSBoundParameters['RemoveEveryone']) { Remove-Everyone -LDAPPath $newGroup.DistinguishedName Write-Debug -Message ('Group {0} Remove Everyone' -f $name) } #end If # Remove Authenticated Users Built-In group If ($PSBoundParameters['RemoveAuthUsers']) { Remove-AuthUser -LDAPPath $newGroup.DistinguishedName Write-Debug -Message ('Group {0} Remove Authenticated Users' -f $name) } #end If # Remove Pre-Windows 2000 Built-In group If ($PSBoundParameters['RemovePreWin2000']) { Remove-PreWin2000 -LDAPPath $newGroup.DistinguishedName Write-Debug -Message ('Group {0} Remove Pre-Windows 2000' -f $name) } #end If } # End Process section End { if ($null -ne $Variables -and $null -ne $Variables.Footer) { $txt = ($Variables.Footer -f $MyInvocation.InvocationName, 'creating Delegated Group.' ) Write-Verbose -Message $txt } #end If #Return the group object. return $newGroup } #end End } #end Function New-AdDelegatedGroup |