Public/New-DelegateAdOU.ps1
function New-DelegateAdOU { <# .Synopsis Creates new custom delegated Active Directory Organizational Unit. .DESCRIPTION Creates a new Organizational Unit (OU) in Active Directory with enhanced security and delegation settings. Key features: - Creates new OU with specified attributes - Removes built-in groups like Account Operators and Print Operators - Optionally removes Authenticated Users - Supports cleaning ACLs and inheritance settings - Implements security best practices - Supports location-based attributes .PARAMETER ouName [String] Name of the OU. Must be 2-50 characters. .PARAMETER ouPath [String] LDAP path where this OU will be created. Must be a valid Distinguished Name path. .PARAMETER ouDescription [String] Full description of the OU. Supports detailed descriptions of OU purpose. .PARAMETER ouCity [String] City location for the OU. .PARAMETER ouCountry [String] Country location for the OU. .PARAMETER ouStreetAddress [String] Street address for the OU location. .PARAMETER ouState [String] State/Province for the OU location. .PARAMETER ouZIPCode [String] Postal/ZIP code for the OU location. .PARAMETER strOuDisplayName [String] Display name for the OU. Defaults to ouName if not specified. .PARAMETER RemoveAuthenticatedUsers [Switch] Remove Authenticated Users group. CAUTION: This might affect GPO application to objects. .PARAMETER CleanACL [Switch] Remove specific non-inherited ACEs and enable inheritance. .EXAMPLE New-DelegateAdOU -ouName "T0-Admins" ` -ouPath "OU=Admin,DC=EguibarIT,DC=local" ` -ouDescription "Tier 0 Admin Objects" ` -CleanACL Creates a new Tier 0 admin OU with cleaned ACLs. .EXAMPLE $Splat = @{ ouPath = 'OU=GOOD,OU=Sites,DC=EguibarIT,DC=local' CleanACL = $True ouName = 'Computers' ouDescription = 'Container for the secure computers' } New-DelegateAdOU @Splat Creates a new OU for remote sites with location attributes. .OUTPUTS [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] Returns the created OU object. .NOTES Used Functions: Name ║ Module ══════════════════════════════════════╬════════════════════════ Get-AdOrganizationalUnit ║ ActiveDirectory New-ADOrganizationalUnit ║ ActiveDirectory Start-AdCleanOU ║ EguibarIT Revoke-Inheritance ║ EguibarIT Remove-AuthUser ║ EguibarIT.DelegationPS Write-Verbose ║ Microsoft.PowerShell.Utility Write-Warning ║ Microsoft.PowerShell.Utility Write-Error ║ Microsoft.PowerShell.Utility .NOTES Version: 1.3 DateModified: 31/Mar/2024 LasModifiedBy: Vicente Rodriguez Eguibar vicente@eguibar.com Eguibar IT http://www.eguibarit.com .LINK https://github.com/vreguibar/EguibarIT .LINK https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/implementing-least-privilege-administrative-models #> [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'Medium' )] # https://docs.microsoft.com/en-us/dotnet/api/microsoft.activedirectory.management?view=activedirectory-management-10.0 [OutputType([Microsoft.ActiveDirectory.Management.ADOrganizationalUnit])] Param ( # Param1 Site Name [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'Name of the OU (2-50 characters)', Position = 0)] [ValidateNotNullOrEmpty()] [ValidateLength(2, 50)] [ValidatePattern( '^[a-zA-Z0-9\s\-_]+$', ErrorMessage = 'OU name can only contain letters, numbers, spaces, hyphens and underscores' )] [string] $ouName, # Param2 OU DistinguishedName (Path) [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'LDAP path where this ou will be created', Position = 1)] [ValidateNotNullOrEmpty()] [ValidateScript( { Test-IsValidDN -ObjectDN $_ }, ErrorMessage = 'DistinguishedName provided is not valid! Please Check.' )] [Alias('DN', 'DistinguishedName', 'LDAPpath')] [string] $ouPath, # Param3 OU Description [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'Full description of the OU', Position = 2)] [string] $ouDescription, # Param4 OU City [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, Position = 3)] [string] $ouCity, # Param5 OU Country [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, Position = 4)] [string] $ouCountry, # Param6 OU Street Address [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, Position = 5)] [string] $ouStreetAddress, # Param7 OU State [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, Position = 6)] [string] $ouState, # Param8 OU Postal Code [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, Position = 7)] [string] $ouZIPCode, # Param9 OU Display Name [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, Position = 8)] [string] $strOuDisplayName, #PARAM10 Remove Authenticated Users [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'Remove Authenticated Users. CAUTION! This might affect applying GPO to objects.', Position = 9)] [switch] $RemoveAuthenticatedUsers, #PARAM11 Remove Specific Non-Inherited ACE and enable inheritance [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'Remove Specific Non-Inherited ACE and enable inheritance.', Position = 10)] [switch] $CleanACL ) 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 # Sites OU Distinguished Name $ouNameDN = 'OU={0},{1}' -f $PSBoundParameters['ouName'], $PSBoundParameters['ouPath'] $OUexists = [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit]::New() [hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase) } #end Begin Process { # if (-not $strOuDisplayName) { $strOuDisplayName = $PSBoundParameters['ouName'] } # End If try { # Check if OU exists Write-Debug -Message ('Checking for existing OU: {0}' -f $ouNameDN) $Splat = @{ Filter = { distinguishedName -eq $ouNameDN } SearchBase = $Variables.AdDn ErrorAction = 'SilentlyContinue' } $OUexists = Get-ADOrganizationalUnit @Splat # Check if OU exists If ($OUexists) { # OU it does exists Write-Warning -Message ('Organizational Unit {0} already exists. Exit the script.' -f $ouNameDN) return } #end If if ($PSCmdlet.ShouldProcess("Creating the Organizational Unit '$OuName'")) { Write-Verbose -Message ('Creating the {0} Organizational Unit' -f $PSBoundParameters['ouName']) # Create OU $Splat = @{ Name = $ouName Path = $PSBoundParameters['ouPath'] ProtectedFromAccidentalDeletion = $true } # Add optional parameters if specified $optionalParams = @{ City = 'ouCity' Country = 'ouCountry' Description = 'ouDescription' DisplayName = 'strOuDisplayName' PostalCode = 'ouZIPCode' StreetAddress = 'ouStreetAddress' State = 'ouState' } foreach ($param in $optionalParams.GetEnumerator()) { if ($PSBoundParameters.ContainsKey($param.Value)) { $Splat[$param.Key] = $PSBoundParameters[$param.Value] } #end If } #end Foreach # Create the OU $OUexists = New-ADOrganizationalUnit @Splat } } catch { Write-Error -Message ('Error creating OU: {0}' -f $_) throw } #end Try-Catch # Remove "Account Operators" and "Print Operators" built-in groups from OU. Any unknown/UnResolvable SID will be removed. Write-Debug -Message ('Cleaning OU permissions: {0}' -f $ouNameDN) Start-AdCleanOU -LDAPpath $ouNameDN -RemoveUnknownSIDs -Confirm:$false -Force # Handle ACL cleaning if requested if ($CleanACL) { Write-Verbose -Message ('Cleaning ACL inheritance: {0}' -f $ouNameDN) Revoke-Inheritance -LDAPpath $ouNameDN -RemoveInheritance -KeepPermissions } #end If # Remove Authenticated Users if requested if ($RemoveAuthenticatedUsers) { Write-Verbose -Message ('Removing Authenticated Users from: {0}' -f $ouNameDN) Remove-AuthUser -LDAPPath $ouNameDN } #end If } #end Process End { if ($null -ne $Variables -and $null -ne $Variables.Footer) { $txt = ($Variables.Footer -f $MyInvocation.InvocationName, 'creating new delegated OU.' ) Write-Verbose -Message $txt } #end If return $OUexists } #end End } #end Function New-DelegateAdOU |