DryActiveDirectory.psm1

Using Module ActiveDirectory
Using Namespace System.Management.Automation.Runspaces
# DryActiveDirectory is an AD config module for use with DryDeploy, or by itself.
#
# Copyright (C) 2021 Bjørn Henrik Formo (bjornhenrikformo@gmail.com)
# LICENSE: https://raw.githubusercontent.com/bjoernf73/DryActiveDirectory/main/LICENSE
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Class OU {
    [String]       $OUDN
    [String]       $ObjectType
    [String]       $DomainFQDN
    [String]       $DomainDN
    [PSSession]    $PSSession
    [String]       $DomainController
    [PSCredential] $Credential
    [String]       $ExecutionType

    # Overload for CN or OU creation in a PSSession
    OU(
        [String]    $OUDN,
        [String]    $DomainFQDN,
        [PSSession] $PSSession
    )
    {
        $This.OUDN = $OUDN
        If ($This.OUDN -match "^CN=*") {
            $This.ObjectType   = 'container' 
        }
        ElseIf ($This.OUDN -match "^OU=*") {
            $This.ObjectType   = 'organizationalUnit' 
        }
        ElseIf ($This.OUDN.Trim() -eq '') {
            $This.ObjectType   = 'DomainRoot' 
        }
        Else { 
            ol 1 "Unknown Object Type (not CN, OU or Domain Root): $($This.OUDN)"
            Throw "Unknown Object Type (not CN, OU) or Domain Root: $($This.OUDN)"
        }  
        $This.DomainFQDN       = $DomainFQDN 
        $This.DomainDN         = "DC=" + $($This.DomainFQDN.replace(".",",DC="))
        $This.PSSession        = $PSSession
        $This.ExecutionType    = 'Remote'
        $This.DomainController = 'localhost'
    } 

    # Overload for CN or OU creation locally with PSCredential
    OU(
        [String]       $OUDN,
        [String]       $DomainFQDN,
        [String]       $DomainController,
        [PSCredential] $Credential
    )
    {
        $This.OUDN = $OUDN
        If ($This.OUDN -match "^CN=*") {
            $This.ObjectType   = 'container' 
        }
        ElseIf ($This.OUDN -match "^OU=*") {
            $This.ObjectType   = 'organizationalUnit' 
        }
        ElseIf ($This.OUDN.Trim() -eq '') {
            $This.ObjectType   = 'DomainRoot' 
        }
        Else { 
            ol 1 "Unknown Object Type (not CN or OU): $($This.OUDN)"
            Throw "Unknown Object Type (not CN or OU): $($This.OUDN)"
        } 
        $This.DomainFQDN       = $DomainFQDN 
        $This.DomainDN         = "DC=" + $($This.DomainFQDN.replace(".",",DC="))
        $This.Credential       = $Credential
        $This.ExecutionType    = 'Local'
        $This.DomainController = $DomainController
    } 

    # Overload for CN or OU creation locally using privileges of the executing user
    OU(
        [String]       $OUDN,
        [String]       $DomainFQDN,
        [String]       $DomainController
    )
    {
        $This.OUDN = $OUDN
        If ($This.OUDN -match "^CN=*") {
            $This.ObjectType   = 'container' 
        }
        ElseIf ($This.OUDN -match "^OU=*") {
            $This.ObjectType   = 'organizationalUnit' 
        }
        ElseIf ($This.OUDN.Trim() -eq '') {
            $This.ObjectType   = 'domainRoot' 
        }
        Else { 
            ol 1 "Unknown Object Type (not CN or OU): $($This.OUDN)"
            Throw "Unknown Object Type (not CN or OU): $($This.OUDN)"
        } 
        $This.DomainFQDN       = $DomainFQDN 
        $This.DomainDN         = "DC=" + $($This.DomainFQDN.replace(".",",DC="))
        $This.Credential       = $Null
        $This.ExecutionType    = 'Local'
        $This.DomainController = $DomainController
    } 

    [void]CreateOU () {
        If ($This.ObjectType -eq 'domainRoot') {
            ol 3 "Trying to create root of domain - just return"
        } 
        Else {
            # Create an array of elements. Start with making sure
            # root level exist, looping out to the leaf
            $DNParts = $This.OUDN.Split(',')
            For ($c = ($DNParts.Count -1); $c -ge 0; $c--) {    
                
                $CurrentDN             = [String]::Join(',', ($DNParts[$c..($DNParts.Count -1)]))
                $CurrentDomainDN       = ($CurrentDN + ',' + $This.DomainDN).TrimStart(',')
                $CurrentName           = (($CurrentDN -split (",",2))[0]).SubString(3)
                $CurrentParent         = ($currentDN -split (",",2))[1]
                $CurrentParentDomainDN = ($CurrentParent + ',' + $This.DomainDN).TrimStart(',')
                
                If ($CurrentParent -eq '') {
                    ol 3 "'$CurrentName'. The parent domainDN is $CurrentParentDomainDN"
                }
                
                Else {
                    ol 3 'LeafOU (CurrentName)',"'$CurrentName'" 
                    ol 3 'Parent (CurrentParent)',"'$CurrentParent'"
                    ol 3 'Parent domainDN (CurrentParentDomainDN)',"'$CurrentParentDomainDN'"
                    ol 3 'CurrentDomainDN',"'$CurrentDomainDN'"
                }
                
                # Test if object exists
                Try {
                    [ScriptBlock] $GetResultScriptBlock = { 
                        Param (
                            $ObjectDN,
                            $Server,
                            $Credential
                        )
                        
                        Try {
                            $GetADObjectParams = @{
                                Identity    = $ObjectDN
                                Server      = $Server
                                ErrorAction = 'Stop'
                            }
                            If ($Credential) {
                                $GetADObjectParams += @{
                                    Credential = $Credential
                                }   
                            }
                            Get-ADOBject @GetADObjectParams | Out-Null
                            # The Object exists already
                            $True
                        }
                        Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
                            # The Object does not exist
                            $False
                        }
                        Catch {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                    } 

                    $GetArgumentList = @($CurrentDomainDN,$This.DomainController,$This.Credential)
                    $GetParams       = @{
                        ScriptBlock  = $GetResultScriptBlock
                        ArgumentList = $GetArgumentList
                    }
                    If ($This.ExecutionType -eq 'Remote') {
                        $GetParams  += @{
                            Session  = $This.PSSession
                        }
                    }
                    $GetResult       = Invoke-Command @GetParams

                    Switch ($GetResult) {
                        $True {
                            ol s "The OU exists already"
                            ol 3 "The OU '$CurrentName' in parent '$CurrentParent' exists already."
                        }
                        $False {
                            ol 3 "The OU '$CurrentName' in parent '$CurrentParent' does not exist, must be created"
                        }
                        Default {
                            ol 2 "Error trying to get OU '$CurrentName' in parent '$CurrentParent'"
                            Throw $GetResult
                        }
                    } 
                }
                Catch {
                    ol 2 "Failed to test '$CurrentDomainDN'" 
                    throw $_
                }  

                If ($GetResult -eq $False) {
                    [ScriptBlock] $SetResultScriptBlock = { 
                        Param (
                            $Name,
                            $Type,
                            $Path,
                            $Server,
                            $Credential
                        )
                        
                        Try {
                            $NewADObjectParams = @{
                                Name        = $Name
                                Type        = $Type
                                Path        = $Path
                                Server      = $Server
                                ErrorAction = 'Stop'
                            }
                            If ($Credential) {
                                $NewADObjectParams += @{
                                    Credential = $Credential
                                }   
                            }
                            New-ADOBject @NewADObjectParams | Out-Null
                            # The Object was created
                            $True
                        }
                        Catch {
                            $_
                        }
                    } 

                    $SetArgumentList = @($CurrentName,$This.ObjectType,$CurrentParentDomainDN,$This.DomainController,$This.Credential)
                    $SetParams       = @{
                        ScriptBlock  = $SetResultScriptBlock
                        ArgumentList = $SetArgumentList
                    }
                    If ($This.ExecutionType -eq 'Remote') {
                        $SetParams  += @{
                            Session  = $This.PSSession
                        }
                    }
                    $SetResult       = Invoke-Command @SetParams

                    Switch ($SetResult) {
                        $True {
                            ol s "The OU was created"
                            ol 3 "OU '$CurrentName' in parent '$CurrentParent' was created"
                            $OUsWasCreated = $True
                        }
                        
                        Default {
                            ol f "The OU was not created"
                            ol 2 "Failed to create OU '$CurrentName' in parent '$CurrentParent'"
                            Throw $SetResult.ToString()
                        }
                    }
                }
            }     
        }
    }
}

# Dot source all ScriptBlock-scripts, Function-scripts and ExportedFunction-scripts
$ScriptBlocksPath      = "$PSScriptRoot\ScriptBlocks\*.ps1"
$FunctionsPath         = "$PSScriptRoot\Functions\*.ps1"
$ExportedFunctionsPath = "$PSScriptRoot\ExportedFunctions\*.ps1"

$ScriptBlocks          = Resolve-Path -Path $ScriptBlocksPath -ErrorAction Stop
$Functions             = Resolve-Path -Path $FunctionsPath -ErrorAction Stop
$ExportedFunctions     = Resolve-Path -Path $ExportedFunctionsPath -ErrorAction Stop

ForEach ($ScriptBlock in $ScriptBlocks) {
    . $ScriptBlock.Path
}
ForEach ($Function in $Functions) {
    . $Function.Path
}
ForEach ($ExportedFunction in $ExportedFunctions) {
    . $ExportedFunction.Path
}