private/Add-OUStructureFromTemplate.ps1

Function Add-OUStructureFromTemplate {
    [CmdletBinding(SupportsShouldProcess = $true)]
    Param
    (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
        [String]$Name,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 1)]
        [String]$Description,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 2)]
        [String]$Path,

        [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $true, Position = 3)]
        [System.Collections.Hashtable]$Template,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, Position = 4)]
        [String]$ParentOrg,

        [Switch]$ResetRoleMembership,

        [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")
        }
        #$ShouldProcess.verbose = $True
    }
    PROCESS {
        write-verbose "Starting to Add structure from template (DC: $($Server.Hostname))"
        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 }

        $DefaultRights = $Template.DefaultRights
        $DefaultRoles = $Template.DefaultRoles
        $resetRoleParam = @{
            resetMembership = [bool]($ResetRoleMembership)
        }
        $resetRightsParam = @{
            resetMembers = [bool]($ResetRightsMembership)
        }

        $structureString = "`r`n{0} OUs {0}{0}{0} Descriptions {0}{0}{0}" -f "#####"
        $structureString += "`r`n {0,-24} : {1}" -f $name, $description
        $structureString += $Template.OUs | ForEach-Object { "`r`n --> {0,-20} : {1}" -f $_.name, ($_.description[0..95] -join "") }
        $ChildPath = "OU=$name,$path"
        if (-not $shouldProcess.verbose) { Write-Host ("Creating OU Structure: '{0}'`r`n under: '{1}'`r`n{2}" -f $name, $path, $structureString) }
        if ($PSCmdlet.ShouldProcess(("`r`nCreating OU Structure: '{0}'`r`n under: '{1}'`r`n{2}" -f $name, $path, $structureString))) {
            $OUBase = CreateOrSetOU -Name $name -Description $Description -Path $path @shouldProcess
            $ChildPath = $OUBase.DistinguishedName
            $ChildOUs = $Template.OUs | foreach-object {
                $_.Path = $ChildPath
                write-verbose ("--- Template ChildOU: {0} at {1}" -f $_.name, $_.path)
                [pscustomObject]$_
            }

            $ChildOUs | CreateOrSetOU @ShouldProcess | out-null
        }
        Write-Host "Finished Structure Creation`r`n"
        if ($ParentOrg) {
            # Write-verbose "There is a component"
            $GroupMidName = "$parentOrg-$Name"
            $RightsPath = "OU={0},{1}" -f $($Settings.Names.RightsOU), $ChildPath
            $RolesPath = "OU={0},{1}" -f $Settings.Names.RightsOU, $ChildPath
            $ParentPrincipalPrefix = "$parentOrg"
            $parentOrgObj = get-rbacOrg -org $parentOrg
        }
        else {
            # write-verbose "No Component"
            $GroupMidName = $name
            $RightsPath = "OU={0},{1}" -f $($Settings.Names.RightsOU), $ChildPath
            $RolesPath = "OU={0},{1}" -f $Settings.Names.RightsOU, $ChildPath
            $ParentPrincipalPrefix = $GlobalOrgName
            $parentOrgObj = get-rbacOrg -org global -includeGlobal
        }
        # write-verbose "Rights: $rightsPath; Roles: $RolesPath"
        if ($defaultRights) { Write-host ("{0,-48} @ {1}" -f "About to create default rights", $RightsPath) }
        $rightsDef = @{}
        foreach ($Group in $DefaultRights) {

            $def = [pscustomobject]@{
                Name        = "$RightsName-$GroupMidName-$($Group.nameSuffix)"
                Description = $Group.Description
                path        = $RightsPath
                GroupScope  = $RightScope
                Info        = $Group.Description
                Members     = @()
                memberOf    = @()
            }
            if ($Group.AddParents -ne $false -and $ParentPrincipalPrefix -ne $name) {
                try {
                    $parentGroupFilter = "{0} -eq '{1}{2}-{3}-{4}' -or {0} -eq '{2}-{3}-{4}' -and GroupScope -eq '{5}'" -f "name", $RightPrefix, $RightsName, $ParentPrincipalPrefix, $Group.nameSuffix,$RightScope
                    $parentGroupName = (get-adgroup -filter $parentGroupFilter -searchBase $parentOrgObj.distinguishedName -server $Server ).name
                    if ($ParentGroupName.count -gt 1) {
                        write-warning "Multiple groups found, assuming group with rightPrefix"
                        $parentGroupName = @($parentGroupName.where({$_ -like "$RightPrefix*"}))
                    }
                    if ($ParentGroupName.count -gt 1) {
                        throw "Still too many groups! $parentGroupFilter"
                    }
                    write-Verbose "Adding the parent Org group $ParentGroupName as a member to $($def.name)"
                    $def.members = $parentGroupName
                }
                catch {
                    write-warning $_.exception.getType().fullname
                    Write-Warning "Parent org group doesn't exist? skipping"
                }
            }
            if (-not $group.DoNotPrefixGroupName) {
                $def.name = "$RightPrefix$($def.name)"
            }

            Write-Host ("--->{0,-30}" -f $def.name, $def.path)
            $rightsDef.$($group.nameSuffix) = $def
        }


        $rightsDef.values  | CreateOrSetGroup @ShouldProcess @resetRightsParam | out-null

        if ($defaultRoles) { Write-host ("`r`n{0,-48} @ {1}" -f "About to create Roles:", $RolesPath) }
        $rolesDef = @{}
        foreach ($Group in $DefaultRoles) {
            $def = [psCustomObject]@{
                Name        = "{0}-{1}-{2}{3}" -f $Settings.Names.RolesName, $GroupMidName, $Group.nameSuffix, $rolesuffix
                Description = $Group.Description
                path        = $RolesPath
                GroupScope  = $RoleScope
                Info        = $Group.Description
                MemberOf    = @()
            }
            if ($Group.AddParents -eq $true -and $name -ne "Global") {
                $parentGroupFilter = "{0} -eq '{2}-{3}-{4}{1}' -or {0} -eq '{2}-{3}-{4}' -and GroupScope -eq '$RoleScope'" -f "name", $RoleSuffix, $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-warning "Multiple groups found, assuming group with rightPrefix"
                    $parentGroupName = @($parentGroupName.where({$_ -like "$RightPrefix*"}))
                }
                if ($ParentGroupName.count -gt 1) {
                    throw "Still too many groups! $parentGroupFilter"
                }
                write-Verbose "Add parent group as member: $parentGroupName"
                $def.members = $ParentGroupName
            }
            Write-Host ("--->{0,-30}" -f $def.name, $def.path)
            $protectedRole = $false
            $def.memberOf = foreach ($right in $Group.rights) {
                $rightName = $rightsDef.$right.name
                if ($rightName -like "*$($protectedRight)*") {
                    write-warning "âš¡âš¡âš¡$rightNameâš¡âš¡âš¡"
                    $protectedRole = $true
                }
                $rightName
            }
            if ($group.auxiliaryGroups) {
                $def.memberof += $Group.auxiliaryGroups
            }
            if ($true -eq $Group.Protected) {
                $def.memberOf += "Protected Users"
                $protectedRole = $true
            }
            foreach ($g in $def.memberOf) {
                if ($null -ne $g) {
                    write-Host (" |-->{0}" -f $g)
                }
            }
            if ($protectedRole) {
                $def.name = "$protectedRole$($def.name)"
            }
            $rolesDef.$($Group.nameSuffix) = $def
        }
        $rolesDef.values | CreateOrSetGroup @shouldProcess @resetRoleParam | out-null

        # Pre-create the Deny permissions as it's relatively slow to do.
        $ObjectGUIDs = get-ADObjectGUIDs
        $DefaultDenyRules = $DefaultDenyObjectTypes | foreach-object -parallel {

            [PSCustomObject]@{
                ADRight         = "CreateChild"
                Action          = "Deny"
                TargetObject    = $PSItem
                InheritanceType = "None"
            }
            [PSCustomObject]@{
                ADRight         = "CreateChild"
                Action          = "Deny"
                TargetObject    = $PSItem
                InheritanceType = "All"
            }
        } | new-OUPermission -principal "NT Authority\Everyone"

        # Build ParameterList
        foreach ($delegation in $Template.OUDelegations) {
            $ADPath = $null
            $ADPath = if ($delegation.ADPath) {
                $delegation.ADPath
            } elseif ($delegation.ADPathQuery) {
                $query = $delegation.ADPathQuery
                Write-host "--> Children of $($query.searchbase)"
                (get-adobject @query -server $server).DistinguishedName
            } elseif ($delegation.ADPathLeafOU) {
                write-Verbose "Deriving ADPath: $($ADPath)"
                join-string -inputObject @($delegation.ADPathLeafOU, $ChildPath) -Separator ","
            } else {
                $ChildPath
            }
            Write-host "--> $ADPath"
            $outputText = ("--->{0,-40}" -f $ADPath)
            $ACEList = @($delegation.ACLs | Foreach-Object -parallel {
                    $objectGUIDsInner = $using:ObjectGUIDs
                    $rightsList = $using:rightsdef
                    #Action that will run in Parallel. Reference the current object via $PSItem and bring in outside variables with $USING:varname
                    if ($PSItem.PrincipalSuffix -and -not $PSItem.Principal) {
                        #$Principal = "{0}-{1}-{2}" -f $using:RightsName, $using:GroupMidName, $PSItem.PrincipalSuffix
                        $principal = $rightsList.$($PSItem.PrincipalSuffix).name
                        write-Verbose "Deriving Principal: $($Principal)"
                    }
                    elseif ($PSItem.Principal) {
                        $Principal = $PSItem.principal
                        write-verbose "Principal: $Principal"
                    }
                    else {
                        throw "Missing principal or principal suffix"
                    }
                    for ($i = 0; $i -lt $using:SleepTimeout/$using:sleepLength; $i++) {
                        try {
                            $principalSID = [System.Security.Principal.NTAccount]::new($principal).translate([System.security.Principal.SecurityIdentifier])
                            $identity = [System.Security.Principal.IdentityReference] $principalSID
                            continue
                        } catch {
                            write-warning $_.exception.getType().fullname
                            write-warning "Could not find principal $principal; sleeping for $($using:sleepLength)"
                            start-sleep -Seconds $using:sleepLength
                        }
                    }
                    if (-not $identity) {
                        write-error "Failed to resolve principal before timeout: $principal"
                        write-warning "Skipping this set of ACLs."
                        continue
                    }

                    $PSItem.ACEs | foreach-object -parallel {

                        # Details on permissions:
                        # # ObjectType: Can be either an object, or a "right" retrieved by the get-ADObjectGUIDs. If this is an extended right, it refers to the "right" type
                        # # InheritedObjectType: "Applies to" in the GUI. This is an object GUID.
                        # # ADRight: Generally CreateChild, DeleteChild, GenericAll, or something involving "ExtendedRight".
                        # #
                        #try {
                        If ($PSItem.appliesTo) {
                            $AppliesTo = $PSItem.appliesTo
                            $inheritedObjectType = $using:ObjectGUIdsInner | where-object { $_.name -eq $appliesTo -and $_.type -eq "Object" }
                        }
                        else {
                            write-verbose "IOT null"
                            $inheritedObjectType = @{
                                name = "---Null---"
                                GUID = [GUID]"00000000-0000-0000-0000-000000000000"
                            }
                        }
                        [System.security.AccessControl.AccessControlType] $action = if ($PSItem.Action) { $PSItem.action } else { "Allow" }
                        [System.DirectoryServices.ActiveDirectorySecurityInheritance] $InheritanceType = if ($PSItem.inheritanceType) { $PsItem.InheritanceType } else { "All" }

                        If ($PSItem.ExtendedRight -and -not $PSItem.ADRight) {
                            $ADRight = [System.directoryservices.ActiveDirectoryRights]"ExtendedRight"
                        }
                        else {
                            [System.directoryservices.ActiveDirectoryRights] $ADRight = $psitem.adright
                        }

                        if ($PSItem.extendedRight) {
                            $extendedRightName = $PSItem.extendedRight
                            write-verbose  "ObjectType as extended right ($extendedRightName)"
                            $ObjectType = [GUID]($using:ObjectGUIdsInner | where-object { $_.type -eq "Right" -and $_.name -eq $extendedRightName }).GUID
                        }
                        elseif (-not $PSItem.targetObject) {
                            write-verbose  "no target object, Setting null objectType"
                            $objectType = @{
                                name = "---Null---"
                                GUID = [GUID]"00000000-0000-0000-0000-000000000000"
                            }
                        }
                        else {
                            $targetObjectName = $PSItem.targetObject
                            $ObjectType = ($using:ObjectGUIdsInner | where-object { $_.name -eq  $targetObjectName})
                            write-Verbose "Not filtering object type ($targetObjectName --> $($objectType.guid))"
                        }
                        write-verbose ("{0,6}{1,-48} on: {2,-36} IOT: {3,-36} {4}" -f "ACE:", $using:Principal, $ObjectType.name, $inheritedObjectType.name, $ADRight)
                        if ($ADRight -eq "CreateChild" -and $objectType -eq [GUID]"00000000-0000-0000-0000-000000000000" -and $psitem.targetObject -eq "organizational-unit") {
                            write-warning "SQUAWK"
                            write-warning "TargetObjectName: $($targetObjectName); $($PSItem.targetObject); $objectType"

                        }
                        try {
                            New-object System.DirectoryServices.ActiveDirectoryAccessRule($using:Identity, $ADRight, $Action, $objectType.GUID, $InheritanceType, $inheritedObjectType.GUID)
                        } catch {
                            write-warning $_.exception.getType().fullname
                            write-warning "error creating ACE."
                            write-warning ("{0,6}{1,-48} on: {2,-36} IOT: {3,-36} {4}" -f "ACE:", $using:Principal, $ObjectType.name, $inheritedObjectType.name, $ADRight)
                        }

                        # }
                        # catch {
                        # $_ | format-list * -force
                        # Write-warning "WHOOPS"
                        # }
                    }

                })
            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-Verbose "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-verbose "Adding $($denyRules.count) Deny rules."
            }
            write-verbose "ACEList size: $($AceList.count)"

            write-host $(get-OUACLs -ACLList $ACEList -ShowDefaults | format-table | out-string)
            if ($PScmdlet.ShouldProcess("Applying Delegations now....")) {
                Add-OUPermissions -path $ADPath -aceList $AceList @shouldProcess
            }
        }
    }

    <#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-verbose ("{0,6}{1,-48} on: {2,-36} IOT: {3,-36} {4}" -f $textIndent, $_.Principal, ($_.ExtendedRight + $_.TargetObject), $_.AppliesTo, $_.ADRight)
                #Add-OUPermission @_
            }
            write-host ""
        }#>

}