public/Add-OUStructureFromTemplate.ps1
Function Add-OUStructureFromTemplate { [CmdletBinding(DefaultParameterSetName = "NonDefaultTemplate", SupportsShouldProcess = $true)] Param ( # Name of the OU structure. This is the CN of the parent OU and is used to derive the middle part of principal names (for global / orgs / components) [Parameter( ParameterSetName = "NonDefaultTemplate", Mandatory, ValueFromPipelineByPropertyName )] [Parameter( ParameterSetName = "AsOrg", Mandatory, ValueFromPipelineByPropertyName )] [Parameter( ParameterSetName = "AsComponent", Mandatory, ValueFromPipelineByPropertyName )] [String]$Name, # Description that will be added to the parent OU's LDAP description attribute [Parameter( ValueFromPipelineByPropertyName)] [String]$Description, # The parent path, for generic OU structures [Parameter( ParameterSetName = "NonDefaultTemplate", Mandatory, ValueFromPipelineByPropertyName )] [String]$Path, # The hashtable template. TODO: make this a custom class. [Parameter( ParameterSetName = "NonDefaultTemplate", Mandatory, ValueFromPipelineByPropertyName )] [System.Collections.Hashtable]$Template, # Use the 'Global' template and set the parent to none [Parameter( ParameterSetName = "AsGlobal", Mandatory, ValueFromPipelineByPropertyName )] [switch] $AsGlobal, # Use the 'org' template and set the parent to global [Parameter( ParameterSetName = "AsOrg", Mandatory, ValueFromPipelineByPropertyName )] [switch] $AsOrg, # Use the 'Component' template and set the parent to ParentOrg [Parameter( ParameterSetName = "AsComponent", Mandatory, ValueFromPipelineByPropertyName )] [switch] $AsComponent, # The Containing Org for component types. [Parameter( ParameterSetName = "AsComponent", Mandatory, ValueFromPipelineByPropertyName )] [ArgumentCompleter( { param ( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters ) (get-rbacOrg -org "$wordToComplete*").Org })] [String]$ParentOrg, # Clear and recreate any memberships for default roles. This will effectively re-align the roles with the spec and should be low-impact [Switch]$ResetRoleMembership, # Clear and recreate the memberlist of rights. Currently should only remove rights/roles groups (by name) [Switch]$ResetRightsMembership, [Microsoft.ActiveDirectory.Management.ADDirectoryServer]$Server = (get-addomainController -Writable -Discover) ) BEGIN { $shouldProcess = @{ Confirm = [bool]($ConfirmPreference -eq "low") Whatif = [bool]($WhatIfPreference.IsPresent) verbose = [bool]($VerbosePreference -ne "SilentlyContinue") } } PROCESS { write-loghandler -message "Starting to Add structure from template (DC: $($Server.Hostname))" -level "verbose" <# # Is this actually needed???? if ($PsItem.Name) { $Name = $_.Name } if ($PsItem.Description) { $Description = $_.Description } if ($PsItem.Path) { $Path = $_.Path } if ($PsItem.Template) { $Template = $_.Template } if ($PsItem.ParentOrg) { $ParentOrg = $_.ParentOrg } #> if ($PSItem) { if ($_.name -ne $name -or $_.description -ne $description -or $_.path -ne $path -or $_.parentOrg -ne $ParentOrg) { write-loghandler -level "warning" -message "This shouldnt happen, uncomment lines 65+ in add-oustructurefromtemplate...." throw "Whoops" } } if ($AsGlobal -or $AsOrg -or $AsComponent) { switch ($true) { $Asglobal { $template = $GlobalTemplate $MockObject = get-RBACGlobal -mock -Description $Description -Detailed $Name = $(split-LDAPPath -distinguishedName $Settings['OUPaths']['Global'] -leaf -nodeNameOnly) $GPOParam = @{ Global = $true } } $AsOrg { $Template = $OrgTemplate $MockObject = get-RBACOrg -org $Name -mock -Description $Description -Detailed ## TODO $GPOParam = @{ Org = $Name } } $AsComponent { $template = $ComponentTemplate $MockObject = get-RBACComponent -component $Name -mock -Description $Description -oRG $parentOrg -Detailed $GPOParam = @{ Org = $ParentOrg Component = $name } $mockObject.Parents.remove("Global") } } } else { $mockObject = @{ Name = $name Path = $Path DistinguishedName = "OU=$name,$path" Children = ($Template['LDAPContainers'] | foreach-object { [pscustomobject]$_ | resolve-rbacchildren -baseDN "OU=$name,$path" }) Parents = @() ObjectMidName = $name } $GPOParam = $false } $Path = $MockObject.Path $GroupMidName = $MockObject.ObjectMidName $RightsPath = $MockObject.Children.$($settings.Names.RightsOU).distinguishedName $RolesPath = $MockObject.Children.$($settings.Names.RolesOU).distinguishedName $parentOrgObj = $MockObject.Parents $resetRoleParam = @{ resetMembership = [bool]($ResetRoleMembership) } $resetRightsParam = @{ resetMembers = [bool]($ResetRightsMembership) } Write-loghandler -level "info" -message "Starting OU Structure processing" $MockObject.Children.getEnumerator().values | createOrSetOU -server $server @shouldProcess | format-Table Write-loghandler -level "info" -message "Finished OU Structure processing" if ($Template['DefaultRights']) { Write-loghandler -level "info" -message ("{0,-48} @ {1}" -f "About to create default rights", $RightsPath) } $rightsDef = [hashtable]::new() foreach ($Group in $Template['DefaultRights']) { $def = [pscustomobject]@{ Name = "{0}-{1}-{2}" -f $Settings['Names']['RightsName'],$GroupMidName,$Group.nameSuffix Description = $Group.Description path = $RightsPath GroupScope = $settings.AppSettings.RightScope Info = $Group.Description Members = @() memberOf = @() } if ($Group.AddParents -ne $false -and $ParentOrgObj.count -gt 0) { foreach ($parent in $parentOrgObj.getEnumerator()) { write-loghandler -level "warning" -message "Adding parents for: $($def.name) (Parent: $($parent.value.name))" -target $def.name try { $parentGroupFilter = "{0} -eq '{1}{2}-{3}-{4}' -or {0} -eq '{2}-{3}-{4}' -and GroupScope -eq '{5}'" -f "name", $Settings.Names.RightsPrefix, $Settings.Names.RightsName, $parent.value.ObjectMidName, $Group.nameSuffix, $Settings.AppSettings.RightScope $parentGroupName = (get-adgroup -filter $parentGroupFilter -searchBase $parent.Value.children.Rights.DistinguishedName -server $Server ).name if ($ParentGroupName.count -gt 1) { write-loghandler -level "warning" -message "Multiple groups found, assuming group with rightPrefix" $parentGroupName = @($parentGroupName.where({ $_ -like "$RightPrefix*" })) } if ($ParentGroupName.count -gt 1) { throw "Still too many groups! $parentGroupFilter" } write-loghandler -level "Verbose" -message "Adding the parent Org group $ParentGroupName as a member to $($def.name)" $def.members = $parentGroupName } catch { write-loghandler -level "warning" -message $_.exception.getType().fullname write-loghandler -level "warning" -message "Parent org group doesn't exist? skipping" } } } if (-not $group.DoNotPrefixGroupName) { $def.name = "{0}{1}" -f $Settings['Names']['RightsPrefix'],$def.name } #Write-loghandler -level "info" -message (" {0,-30}" -f $def.name, $def.path) -suppressCaller -suppressTimestamp $rightsDef.$($group.nameSuffix) = $def } $rightsDef.values | CreateOrSetGroup @ShouldProcess @resetRightsParam | format-table if ($Template['DefaultRoles']) { write-loghandler -level "Info" -message "Starting Roles processing at $rolesPath" } $rolesDef = [hashtable]::new() foreach ($Group in $Template['DefaultRoles']) { try { write-loghandler -level "Debug" -message $Group.nameSuffix } catch { write-warning "Wh" } $def = [psCustomObject]@{ Name = "{0}-{1}-{2}{3}" -f $Settings['Names']['RolesName'], $GroupMidName, $Group.nameSuffix, $Settings.Names.RolesSuffix Description = $Group.Description path = $RolesPath GroupScope = $settings.AppSettings.RoleScope Info = $Group.Description MemberOf = @() } write-loghandler -level "Debug" -message ("{0,-32} {1}" -f $def.name, $def.Description) if ($Group.AddParents -eq $true) { $parentGroupFilter = "{0} -eq '{2}-{3}-{4}{1}' -or {0} -eq '{2}-{3}-{4}' -and GroupScope -eq '$RoleScope'" -f "name", $Settings.Names.RolesSuffix, $Settings.Names.RightsName, $ParentPrincipalPrefix, $Group.nameSuffix, $RoleScope $parentGroupName = "{0}-{1}-{2}" -f $Settings['Names']['RolesName'], $ParentPrincipalPrefix, $Group.nameSuffix $parentGroupName = (get-adgroup -filter $parentGroupFilter -searchBase $parentOrgObj.distinguishedName -server $Server ).name if ($ParentGroupName.count -gt 1) { write-loghandler -level "warning" -message "Multiple groups found, assuming group with rightPrefix" $parentGroupName = @($parentGroupName.where({ $_ -like "$RightPrefix*" })) } if ($ParentGroupName.count -gt 1) { throw "Still too many groups! $parentGroupFilter" } write-loghandler -level "Verbose" -message "Add parent group as member: $parentGroupName" $def.members = $ParentGroupName } $protectedRole = $false $def.memberOf = $Group.rights | foreach-object { if (-not $rightsDef.ContainsKey($_)) { throw "Missing rights definition for role: $($def.name): $_" } $rightName = $rightsDef.$_.name if ($rightName -like "*$($Settings.Names.RightsProtected)*") { write-loghandler -level "" -message "âš¡âš¡âš¡$rightNameâš¡âš¡âš¡" $protectedRole = $true } $rightName } if ($group.auxiliaryGroups) { $def.memberof += $Group.auxiliaryGroups } if ($true -eq $Group.Protected) { $def.memberOf += "Protected Users" $protectedRole = $true } if ($protectedRole) { $def.name = "{0}{1}" -f $settings.Names.RoleProtected, $def.name } write-loghandler -level "Info" -message ("{0,-32} {1}" -f $def.name, $def.Description) foreach ($g in $def.memberOf) { if ($null -ne $g) { write-logHandler -level "Info" -message $g -indentlevel 1 -suppressTimestamp -suppressCaller } } $rolesDef.$($Group.nameSuffix) = $def } $rolesDef.values | CreateOrSetGroup @shouldProcess @resetRoleParam | out-null write-loghandler -level "Info" -message "Finished processing roles.`r`n`r`n" # Pre-create the Deny permissions as it's relatively slow to do. $ObjectGUIDs = get-ADObjectGUIDs $newOUPermsDef = ${function:new-OUPermission}.toString() $DefaultDenyRules = $Settings['AppSettings']['DefaultDenyObjectTypes'] | foreach-object { [PSCustomObject]@{ ADRight = "CreateChild" Action = "Deny" TargetObject = $PSItem InheritanceType = "None" } [PSCustomObject]@{ ADRight = "CreateChild" Action = "Deny" TargetObject = $PSItem InheritanceType = "All" } } | new-OUPermission -principal "NT Authority\Everyone" -ObjectGUIDs $ObjectGUIDs # Create the default ACL $DefaultOU_SDDL = (get-adobject -filter { (ldapDisplayName -eq "organizationalUnit") } -searchBase ($(get-adrootdse).SchemaNamingContext) -properties defaultSecurityDescriptor).defaultSecurityDescriptor $defaultOU_ACL = [System.DirectoryServices.ActiveDirectorySecurity]::new() $defaultOU_ACL.SetSecurityDescriptorSddlForm($defaultOU_SDDL) write-loghandler -level "Debug" -message "Starting OUDelegations..." $logHandlerDef = ${function:write-loghandler}.toString() $newOUPermsDef = ${function:new-OUPermission}.toString() $getouacls = ${function:get-ouacls}.toString() $addouperms = ${function:add-oupermissions}.toString() $getADObjectGUIDs = ${function:get-ADObjectGUIDs}.toString() # Build ParameterList $Output = $Template['OUDelegations'] | foreach-object -parallel { # Bring in needed functions and variables $delegation = $_ $thisElementPath = $using:mockObject.DistinguishedName $rightsDef = $using:rightsdef $sleepLength = $using:Settings.AppSettings.SleepLength $SleepTimeout = $using:Settings.AppSettings.SleepTimeout $objectGUIDs = $using:ObjectGUIDs ${function:write-loghandler} = $using:loghandlerDef ${function:new-OUPermission} = $using:newOUPermsDef ${function:add-oupermissions} = $using:addouperms ${function:get-ouacls} = $using:getouacls ${function:get-ADObjectGUIDs} = $using:getADObjectGUIDs $defaultOU_ACL = $using:defaultOU_ACL $ShouldProcess = $using:shouldProcess import-module -name ActiveDirectory $hostWidth = @{hostWidth = $using:host.ui.rawui.windowsize.width} $DebugPreference = $using:debugPreference $VerbosePreference = $using:verbosePreference $ADPath = $null $ADPath = if ($delegation.ADPath) { write-loghandler -level "Debug" -message "Using ADPath: $($delegation.ADPath)" $delegation.ADPath } elseif ($delegation.ADPathQuery) { write-loghandler -level "Debug" -message "Querying AD with: $($delegation.ADPathQuery)" $query = $delegation.ADPathQuery write-host "--> Children of $($query.searchbase)" (get-adobject @query -server $using:server).DistinguishedName } elseif ($delegation.ADPathLeafOU) { write-loghandler -level "Debug" -message "Deriving ADPath from $($delegation.ADPathLeafOU)" join-string -inputObject @($delegation.ADPathLeafOU, $thisElementPath) -Separator "," } else { write-loghandler -level "Debug" -message "Using childpath: $thisElementPath" $thisElementPath } write-loghandler -level "Info" -message "$ADPath" $sw = [System.Diagnostics.Stopwatch]::startNew() $ACEList = @( $delegation.ACLs | Foreach-Object { if ($PSItem.PrincipalSuffix -and -not $PSItem.Principal) { $principal = $rightsDef[$($PSItem.PrincipalSuffix)].name write-loghandler -level "debug" -message "Deriving Principal for: $($PSItem.PrincipalSuffix)" } elseif ($PSItem.Principal) { $Principal = $PSItem.principal write-loghandler -level "debug" -message "Principal: $Principal" } else { throw "Missing principal or principal suffix" } for ($i = 0; $i -lt $SleepTimeout / $sleepLength; $i++) { try { $principalSID = [System.Security.Principal.NTAccount]::new($principal).translate([System.security.Principal.SecurityIdentifier]) $identity = [System.Security.Principal.IdentityReference] $principalSID break } catch { write-loghandler -level "warning" -message $_.exception.getType().fullname write-loghandler -level "warning" -message "Could not find principal $principal; sleeping for $($sleepLength)" start-sleep -Seconds $sleepLength } } write-loghandler -level "debug" -message "Finished Deriving identity. Beginning to create ACEs" if (-not $identity) { write-error "Failed to resolve principal before timeout: $($PSItem.PrincipalSuffix)" write-loghandler -level "warning" -message "Skipping this set of ACLs." continue } try { $PSItem.ACEs | foreach-object { new-oupermission -Identity $identity @_ -objectGUIDs $ObjectGUIDs } } catch { write-warning "Whoops" throw $_ } }) $sw.stop() write-loghandler -level "debug" -message "Finished creating $($ACEList.count) DACLs in $($sw.ElapsedMilliseconds) ms" $sw.reset() if ($delegation.ApplyDefaultDeny -ne $false) { # Check the ACE list for 'createChild' rights, and add the associated objects to a list. This lets us affirmatively deny object creation for items not in the list $CreateChildACEs = $aceList.where({ $_.ActiveDirectoryRights -Like "*CreateChild*" }) $AllowCreationObjects = $CreateChildACEs | foreach-object { $GUIDList = @($_.objectType.guid, $_.InheritedObjectType.guid).where({ $_ -ne "00000000-0000-0000-0000-000000000000" }) $objectGUIDs.where({ $_.GUID -in $GUIDList }) } if ($AllowCreationObjects.count -gt 0) { write-loghandler -level "Verbose" -message "CreateChild found, only allowing the following child items: $($AllowCreationObjects.name -join "; ")" } elseif ($createChildAces.count -gt 0) { Write-error "Whoops, we have createChild ACEs but our filter failed???" } if (-not $delegation.DefaultDenyInheritance) { $denyInheritance = "None" } else { $denyInheritance = $delegation.DefaultDenyInheritance } # DefaultDenyRules should have an 'inheritance all' and an 'inheritance none' ace for each object, so we're just filtering down. $DenyRules = $DefaultDenyRules | where-object { $_.ObjectType -notin $AllowCreationObjects.GUID -and $_.inheritanceType -eq $denyInheritance } $ACEList += $DenyRules write-loghandler -level "debug" -message "Adding $($denyRules.count) Deny rules." } if ($Acelist.count -gt 0) { Add-OUPermissions -path $ADPath -aceList $AceList @shouldProcess -rebuild -defaultACL $defaultOU_ACL } } if ($GPOParam) { $GPOSpecList = foreach ($GPO in $Template['GPOs']) { [pscustomObject]@{ GPOTemplate = $GPO } } if ($GPOSpecList) { if($PSCmdlet.ShouldProcess("Creating GPOs")) { $GPOSpecList | CreateOrSetGPO @GPOParam -server $Server } } } else { write-loghandler -level "warning" -message "GPO behavior is undefined here. Please report this as a bug." } } <#if ($ACEList) { Write-Host "`r`nApplying AD PSItems" } $textIndent = "|-->" $ACLList | group-object path | foreach-object { Write-Host ("--->{0,-40}" -f $_.name) $_.group.acl | foreach-object { write-loghandler -level "Verbose" -message ("{0,6}{1,-48} on: {2,-36} IOT: {3,-36} {4}" -f $textIndent, $_.Principal, ($_.ExtendedRight + $_.TargetObject), $_.AppliesTo, $_.ADRight) #Add-OUPermission @_ } write-host "" }#> } |