DSCResources/AddsDomainPrincipals/AddsDomainPrincipals.schema.psm1

configuration AddsDomainPrincipals
{
    param
    (
        [Parameter(Mandatory)]
        [String]
        $DomainDN,

        [Hashtable[]]
        $Computers,

        [Hashtable[]]
        $Users,

        [Hashtable]
        $KDSKey,

        [Hashtable[]]
        $ManagedServiceAccounts
    )

    Import-DscResource -ModuleName PSDesiredStateConfiguration
    Import-DscResource -ModuleName ActiveDirectoryDsc
    
    function AddMemberOf {
        param (
            [String]   $ExecutionName,
            [String]   $ExecutionType,
            [String]   $AccountName,
            [String[]] $MemberOf
        )

        if( $null -ne $MemberOf -and $MemberOf.Count -gt 0 )
        {
            Script "$($ExecutionName)_MemberOf"
            {
                TestScript = 
                {
                    # get current member groups in MemberOf
                    $currentGroups = Get-ADPrincipalGroupMembership -Identity $using:AccountName | `
                                     Where-Object { $using:MemberOf -contains $_.SamAccountName } | `
                                     Select-Object -ExpandProperty SamAccountName

                    Write-Verbose "ADPrincipal '$using:AccountName' is member of required groups: $($currentGroups -join ', ')"

                    $missingGroups = $using:MemberOf | Where-Object { -not ($currentGroups -contains $_) }

                    if( $missingGroups.Count -eq 0 )
                    {  
                        return $true
                    }

                    Write-Verbose "ADPrincipal '$using:AccountName' is not member of required groups: $($missingGroups -join ', ')"
                    return $false
                }
                SetScript = 
                {
                    Add-ADPrincipalGroupMembership -Identity $using:AccountName -MemberOf $using:MemberOf
                }
                GetScript = { return 'NA' } 
                DependsOn = "[$ExecutionType]$ExecutionName"
            }            
        }
    }
    
    if( $null -ne $Computers )
    {
        foreach ($computer in $Computers)
        {
            # Remove Case Sensitivity of ordered Dictionary or Hashtables
            $computer = @{}+$computer

            # save group list
            $memberOf = $computer.MemberOf
            $computer.Remove( 'MemberOf' )

            $executionName = "adComputer_$($computer.ComputerName)"

            (Get-DscSplattedResource -ResourceName ADComputer -ExecutionName $executionName -Properties $computer -NoInvoke).Invoke($computer)

            AddMemberOf -ExecutionName $executionName -ExecutionType ADComputer -AccountName "$($computer.ComputerName)$" -MemberOf $memberOf
        }
    }

    if( $null -ne $Users )
    {
        # convert DN to Fqdn
        $pattern = '(?i)DC=(?<name>\w+){1,}?\b'
        $domainName = ([RegEx]::Matches($DomainDN, $pattern) | ForEach-Object { $_.groups['name'] }) -join '.'

        foreach ($user in $Users)
        {
            # Remove Case Sensitivity of ordered Dictionary or Hashtables
            $user = @{}+$user
            
            if( [string]::IsNullOrWhiteSpace($user.DomainName) )
            { 
                $user.DomainName = $domainName
            }

            # save group list
            $memberOf = $user.MemberOf
            $user.Remove( 'MemberOf' )

            $executionName = "adUser_$($user.UserName)"

            (Get-DscSplattedResource -ResourceName ADUser -ExecutionName $executionName -Properties $user -NoInvoke).Invoke($user)

            AddMemberOf -ExecutionName $executionName -ExecutionType ADUser -AccountName $user.UserName -MemberOf $memberOf
        }
    }

    $dependsOnKdsKey = $null

    if( $null -ne $KDSKey )
    {
        (Get-DscSplattedResource -ResourceName ADKDSKey -ExecutionName 'adKDSKey' -Properties $KDSKey -NoInvoke).Invoke($KDSKey)

        $dependsOnKdsKey = '[ADKDSKey]adKDSKey'
    }

    if( $null -ne $ManagedServiceAccounts )
    {
        foreach ($svcAccount in $ManagedServiceAccounts)
        {
            # Remove Case Sensitivity of ordered Dictionary or Hashtables
            $svcAccount = @{}+$svcAccount

            # save group list and computer
            $memberOf = $svcAccount.MemberOf
            $computer = $svcAccount.Computer
            $svcAccount.Remove( 'MemberOf' )
            $svcAccount.Remove( 'Computer' )

            if( $null -ne $dependsOnKdsKey )
            {
                $svcAccount.DependsOn = $dependsOnKdsKey
            }

            $executionName = "adMSA_$($svcAccount.ServiceAccountName)"

            (Get-DscSplattedResource -ResourceName ADManagedServiceAccount -ExecutionName $executionName -Properties $svcAccount -NoInvoke).Invoke($svcAccount)

            # assign a managed service account to AD computer
            if( -not [string]::IsNullOrWhitespace($computer) )
            {
                if( $svcAccount.AccountType -eq 'Standalone' )
                {
                    $svcAccountName = $svcAccount.ServiceAccountName

                    Script "$($executionName)_Computer"
                    {
                        TestScript = 
                        {
                            Write-Verbose "Get managed service accounts hosted by AD computer '$using:computer'..."
                            $svcAccounts = Get-ADComputerServiceAccount -Identity $using:computer -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $svcAccount.ServiceAccountName }

                            if( $null -ne $svcAccounts -and $svcAccounts.Count -ne 1 )
                            {
                                Write-Verbose "OK - managed service account '$using:svcAccountName' is assigned to AD computer '$using:computer'."
                                return $true;
                            }
        
                            Write-Verbose "Managed service account '$using:svcAccountName' is NOT assigned to AD computer '$using:computer'."
                            return $false
                        }
                        SetScript = 
                        {
                            Write-Verbose "Assign managed service account '$using:svcAccountName' to AD computer '$using:computer'"
                            Add-ADComputerServiceAccount -Computer $using:computer -ServiceAccount $using:svcAccountName
                        }
                        GetScript = { return 'NA' } 
                        DependsOn = "[ADManagedServiceAccount]$executionName"
                    }  
                }
                else
                {
                    throw "ERROR: Only a standalone managed service account can be assigned to an AD computer account."
                }          
            }

            # append $ to acoountname to identify it as MSA
            AddMemberOf -ExecutionName $executionName -ExecutionType ADManagedServiceAccount -AccountName "$($svcAccount.ServiceAccountName)$" -MemberOf $memberOf
        }
    }
}