DSCResources/xDSCVault_ADDomainController/xDSCVault_ADDomainController.psm1

#
# xADDomainController: DSC resource to install a domain controller in Active
# Directory.
#
# User name and password needed for this resource and Write-Verbose Used in helper functions
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSDSCUseVerboseMessageInDSCResource', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
param ()

$errorActionPreference = 'Stop'

Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) `
        -ChildPath 'CommonResourceHelper.psm1')
        
function Get-TargetResource
{
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $VaultAddress,

        [System.String]
        $ApiPrefix = 'v1',

        [Parameter(Mandatory)]
        [String]$DomainName,

        [parameter(Mandatory = $true)]
        [System.String]
        $DomainAdministratorUsername,
    
        [parameter(Mandatory = $true)]
        [System.String]
        $DomainAdministratorVaultPath,

        [parameter(Mandatory = $true)]
        [System.String]
        $SafemodeAdministratorPasswordVaultPath,

        [String]$DatabasePath,

        [String]$LogPath,

        [String]$SysvolPath,

        [String]$SiteName,

        [System.String]
        $AuthBackend = 'approle'
    )

    $returnValue = @{
        DomainName = $DomainName
        Ensure     = $false
    }

    $clientToken = Start-VaultAuth -VaultAddress $VaultAddress -ApiPrefix $ApiPrefix -AuthBackend $AuthBackend
    $currentVaultValue = Read-VaultData -VaultAddress $VaultAddress -ClientToken $clientToken.auth.client_token -VaultPath $DomainAdministratorVaultPath -ApiPrefix $ApiPrefix
    $VaultValue = ConvertTo-SecureString -String $currentVaultValue.data.value -AsPlainText -Force
    $DomainAdministratorCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList (("$DomainName" + '\' + "$DomainAdministratorUsername"), $VaultValue)

    try
    {
        Write-Verbose -Message "Resolving '$($DomainName)' ..."
        $domain = Get-ADDomain -Identity $DomainName -Credential $DomainAdministratorCredential
        if ($domain -ne $null)
        {
            Write-Verbose -Message "Domain '$($DomainName)' is present. Looking for DCs ..."
            try
            {
                $dc = Get-ADDomainController -Identity $env:COMPUTERNAME -Credential $DomainAdministratorCredential
                Write-Verbose -Message "Found domain controller '$($dc.Name)' in domain '$($dc.Domain)'."
                if ($dc.Domain -eq $DomainName)
                {
                    Write-Verbose -Message "Current node '$($dc.Name)' is already a domain controller for domain '$($dc.Domain)'."

                    $serviceNTDS = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters'
                    $serviceNETLOGON = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters'

                    $returnValue.Ensure = $true
                    $returnValue.DatabasePath = $serviceNTDS.'DSA Working Directory'
                    $returnValue.LogPath = $serviceNTDS.'Database log files path'
                    $returnValue.SysvolPath = $serviceNETLOGON.SysVol -replace '\\sysvol$', ''
                    $returnValue.SiteName = $dc.Site
                }
            }
            catch
            {
                if ($error[0]) 
                {
                    Write-Verbose -Message $error[0].Exception
                }
                Write-Verbose -Message 'Current node does not host a domain controller.'
            }
        }
    }
    catch [System.Management.Automation.CommandNotFoundException]
    {
        if ($error[0]) 
        {
            Write-Verbose -Message $error[0].Exception
        }
        Write-Verbose -Message 'Current node is not running AD WS, and hence is not a domain controller.'
    }
    $returnValue
}

function Set-TargetResource
{
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $VaultAddress,

        [System.String]
        $ApiPrefix = 'v1',

        [Parameter(Mandatory)]
        [String]$DomainName,

        [parameter(Mandatory = $true)]
        [System.String]
        $DomainAdministratorUsername,
    
        [parameter(Mandatory = $true)]
        [System.String]
        $DomainAdministratorVaultPath,

        [parameter(Mandatory = $true)]
        [System.String]
        $SafemodeAdministratorPasswordVaultPath,

        [String]$DatabasePath,

        [String]$LogPath,

        [String]$SysvolPath,

        [String]$SiteName,

        [System.String]
        $AuthBackend = 'approle'
    )

    # Debug can pause Install-ADDSDomainController, so we remove it.
    $parameters = $PSBoundParameters.Remove('Debug')
    $targetResource = Get-TargetResource @PSBoundParameters

    $clientToken = Start-VaultAuth -VaultAddress $VaultAddress -ApiPrefix $ApiPrefix -AuthBackend $AuthBackend
    $currentVaultValue = Read-VaultData -VaultAddress $VaultAddress -ClientToken $clientToken.auth.client_token -VaultPath $DomainAdministratorVaultPath -ApiPrefix $ApiPrefix
    $VaultValue = ConvertTo-SecureString -String $currentVaultValue.data.value -AsPlainText -Force
    $DomainAdministratorCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList (("$DomainName" + '\' + "$DomainAdministratorUsername"), $VaultValue)

    $clientToken = Start-VaultAuth -VaultAddress $VaultAddress -ApiPrefix $ApiPrefix -AuthBackend $AuthBackend
    $currentVaultValue = Read-VaultData -VaultAddress $VaultAddress -ClientToken $clientToken.auth.client_token -VaultPath $SafemodeAdministratorPasswordVaultPath -ApiPrefix $ApiPrefix
    $VaultValue = ConvertTo-SecureString -String $currentVaultValue.data.value -AsPlainText -Force
    $SafemodeAdministratorPassword = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ('SafeModePlaceHolderUser', $VaultValue)

    if ($targetResource.Ensure -eq $false)
    {
        ## Node is not a domain controllr so we promote it
        Write-Verbose -Message "Checking if domain '$($DomainName)' is present ..."
        $domain = $null
        try
        {
            $domain = Get-ADDomain -Identity $DomainName -Credential $DomainAdministratorCredential
        }
        catch
        {
            if ($error[0]) 
            {
                Write-Verbose $error[0].Exception
            }
            throw (New-Object -TypeName System.InvalidOperationException -ArgumentList "Domain '$($DomainName)' could not be found.")
        }

        Write-Verbose -Message "Verified that domain '$($DomainName)' is present, continuing ..."
        $params = @{
            DomainName                    = $DomainName
            SafeModeAdministratorPassword = $SafemodeAdministratorPassword.Password
            Credential                    = $DomainAdministratorCredential
            NoRebootOnCompletion          = $true
            Force                         = $true
        }
        if ($DatabasePath -ne $null)
        {
            $params.Add('DatabasePath', $DatabasePath)
        }
        if ($LogPath -ne $null)
        {
            $params.Add('LogPath', $LogPath)
        }
        if ($SysvolPath -ne $null)
        {
            $params.Add('SysvolPath', $SysvolPath)
        }
        if ($SiteName -ne $null -and $SiteName -ne '')
        {
            $params.Add('SiteName', $SiteName)
        }

        Install-ADDSDomainController @params
        Write-Verbose -Message "Node is now a domain controller for '$($DomainName)'."

        # Signal to the LCM to reboot the node to compensate for the one we
        # suppressed from Install-ADDSDomainController
        $global:DSCMachineStatus = 1
    }
    elseif ($targetResource.Ensure)
    {
        ## Node is a domain controller. We check if other properties are in desired state
        if ($PSBoundParameters['SiteName'] -and $targetResource.SiteName -ne $SiteName)
        {
            ## DC is not in correct site. Move it.
            Write-Verbose "Moving Domain Controller from '$($targetResource.SiteName)' to '$SiteName'"
            Move-ADDirectoryServer -Identity $env:COMPUTERNAME -Site $SiteName -Credential $DomainAdministratorCredential
        }
    }
}

function Test-TargetResource
{
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $VaultAddress,

        [System.String]
        $ApiPrefix = 'v1',

        [Parameter(Mandatory)]
        [String]$DomainName,

        [parameter(Mandatory = $true)]
        [System.String]
        $DomainAdministratorUsername,
    
        [parameter(Mandatory = $true)]
        [System.String]
        $DomainAdministratorVaultPath,

        [parameter(Mandatory = $true)]
        [System.String]
        $SafemodeAdministratorPasswordVaultPath,

        [String]$DatabasePath,

        [String]$LogPath,

        [String]$SysvolPath,

        [String]$SiteName,

        [System.String]
        $AuthBackend = 'approle'
    )

    $clientToken = Start-VaultAuth -VaultAddress $VaultAddress -ApiPrefix $ApiPrefix -AuthBackend $AuthBackend
    $currentVaultValue = Read-VaultData -VaultAddress $VaultAddress -ClientToken $clientToken.auth.client_token -VaultPath $DomainAdministratorVaultPath -ApiPrefix $ApiPrefix
    $VaultValue = ConvertTo-SecureString -String $currentVaultValue.data.value -AsPlainText -Force
    $DomainAdministratorCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList (("$DomainName" + '\' + "$DomainAdministratorUsername"), $VaultValue)

    if ($PSBoundParameters.SiteName)
    {
        if (-not (Test-ADReplicationSite -SiteName $SiteName -DomainName $DomainName -Credential $DomainAdministratorCredential))
        {
            throw (New-Object -TypeName System.InvalidOperationException -ArgumentList "Site '$($SiteName)' could not be found.")
        }
    }

    $isCompliant = $true

    try
    {
        $parameters = $PSBoundParameters.Remove('Debug')

        $existingResource = Get-TargetResource @PSBoundParameters
        $isCompliant = $existingResource.Ensure

        if ([System.String]::IsNullOrEmpty($SiteName))
        {
            #If SiteName is not specified confgiuration is compliant
        }
        elseif ($existingResource.SiteName -ne $SiteName)
        {
            Write-Verbose "Domain Controller Site is not in a desired state. Expected '$SiteName', actual '$($existingResource.SiteName)'"
            $isCompliant = $false
        }
    }
    catch
    {
        if ($error[0]) 
        {
            Write-Verbose $error[0].Exception
        }
        Write-Verbose -Message "Domain '$($DomainName)' is NOT present on the current node."
        $isCompliant = $false
    }

    $isCompliant
}

## Import the common AD functions
$adCommonFunctions = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.ps1'
. $adCommonFunctions

Export-ModuleMember -Function *-TargetResource