DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.psm1

$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules'

$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common'
Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1')

$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADDomainController'

<#
    .SYNOPSIS
        Returns the current state of the domain controller.
 
    .PARAMETER DomainName
        Provide the FQDN of the domain the Domain Controller is being added to.
 
    .PARAMETER DomainAdministrationCredential
        Specifies the credential for the account used to install the domain controller.
        This account must have permission to access the other domain controllers
        in the domain to be able replicate domain information.
 
    .PARAMETER SafemodeAdministratorPassword
        Provide a password that will be used to set the DSRM password. This is a PSCredential.
 
    .PARAMETER DatabasePath
        Provide the path where the NTDS.dit will be created and stored.
 
    .PARAMETER LogPath
        Provide the path where the logs for the NTDS will be created and stored.
 
    .PARAMETER SysvolPath
        Provide the path where the Sysvol will be created and stored.
 
    .PARAMETER SiteName
        Provide the name of the site you want the Domain Controller to be added to.
#>

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $DomainName,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $DomainAdministratorCredential,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $SafemodeAdministratorPassword,

        [Parameter()]
        [System.String]
        $DatabasePath,

        [Parameter()]
        [System.String]
        $LogPath,

        [Parameter()]
        [System.String]
        $SysvolPath,

        [Parameter()]
        [System.String]
        $SiteName
    )

    Assert-Module -ModuleName 'ActiveDirectory'

    $getTargetResourceResult = @{
        DomainName           = $DomainName
        Ensure               = $false
        IsGlobalCatalog      = $false
    }

    Write-Verbose -Message (
        $script:localizedData.ResolveDomainName -f $DomainName
    )

    try
    {
        $domain = Get-ADDomain -Identity $DomainName -Credential $DomainAdministratorCredential
    }
    catch
    {
        $errorMessage = $script:localizedData.MissingDomain -f $DomainName
        New-ObjectNotFoundException -Message $errorMessage -ErrorRecord $_
    }

    Write-Verbose -Message (
        $script:localizedData.DomainPresent -f $DomainName
    )

    $domainControllerObject = Get-DomainControllerObject -DomainName $DomainName -ComputerName $env:COMPUTERNAME -Credential $DomainAdministratorCredential
    if ($domainControllerObject)
    {
        Write-Verbose -Message (
            $script:localizedData.FoundDomainController -f $domainControllerObject.Name, $domainControllerObject.Domain
        )

        Write-Verbose -Message (
            $script:localizedData.AlreadyDomainController -f $domainControllerObject.Name, $domainControllerObject.Domain
        )

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

        $getTargetResourceResult.Ensure = $true
        $getTargetResourceResult.DatabasePath = $serviceNTDS.'DSA Working Directory'
        $getTargetResourceResult.LogPath = $serviceNTDS.'Database log files path'
        $getTargetResourceResult.SysvolPath = $serviceNETLOGON.SysVol -replace '\\sysvol$', ''
        $getTargetResourceResult.SiteName = $domainControllerObject.Site
        $getTargetResourceResult.IsGlobalCatalog = $domainControllerObject.IsGlobalCatalog
        $getTargetResourceResult.DomainName = $domainControllerObject.Domain
    }
    else
    {
        Write-Verbose -Message (
            $script:localizedData.NotDomainController -f $env:COMPUTERNAME
        )
    }

    return $getTargetResourceResult
}

<#
    .SYNOPSIS
        Installs, or change properties on, a domain controller.
 
    .PARAMETER DomainName
        Provide the FQDN of the domain the Domain Controller is being added to.
 
    .PARAMETER DomainAdministrationCredential
        Specifies the credential for the account used to install the domain controller.
        This account must have permission to access the other domain controllers
        in the domain to be able replicate domain information.
 
    .PARAMETER SafemodeAdministratorPassword
        Provide a password that will be used to set the DSRM password. This is a PSCredential.
 
    .PARAMETER DatabasePath
        Provide the path where the NTDS.dit will be created and stored.
 
    .PARAMETER LogPath
        Provide the path where the logs for the NTDS will be created and stored.
 
    .PARAMETER SysvolPath
        Provide the path where the Sysvol will be created and stored.
 
    .PARAMETER SiteName
        Provide the name of the site you want the Domain Controller to be added to.
 
    .PARAMETER InstallationMediaPath
        Provide the path for the IFM folder that was created with ntdsutil.
        This should not be on a share but locally to the Domain Controller being promoted.
 
    .PARAMETER IsGlobalCatalog
        Specifies if the domain controller will be a Global Catalog (GC).
#>

function Set-TargetResource
{
    <#
        Suppressing this rule because $global:DSCMachineStatus is used to
        trigger a reboot for the one that was suppressed when calling
        Install-ADDSDomainController.
    #>

    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')]
    <#
        Suppressing this rule because $global:DSCMachineStatus is only set,
        never used (by design of Desired State Configuration).
    #>

    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Scope='Function', Target='DSCMachineStatus')]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $DomainName,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $DomainAdministratorCredential,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $SafemodeAdministratorPassword,

        [Parameter()]
        [System.String]
        $DatabasePath,

        [Parameter()]
        [System.String]
        $LogPath,

        [Parameter()]
        [System.String]
        $SysvolPath,

        [Parameter()]
        [System.String]
        $SiteName,

        [Parameter()]
        [System.String]
        $InstallationMediaPath,

        [Parameter()]
        [System.Boolean]
        $IsGlobalCatalog
    )

    $getTargetResourceParameters = @{} + $PSBoundParameters
    $getTargetResourceParameters.Remove('InstallationMediaPath')
    $getTargetResourceParameters.Remove('IsGlobalCatalog')
    $targetResource = Get-TargetResource @getTargetResourceParameters

    if ($targetResource.Ensure -eq $false)
    {
        Write-Verbose -Message (
            $script:localizedData.Promoting -f $env:COMPUTERNAME, $DomainName
        )

        # Node is not a domain controller so we promote it.
        $installADDSDomainControllerParameters = @{
            DomainName                    = $DomainName
            SafeModeAdministratorPassword = $SafemodeAdministratorPassword.Password
            Credential                    = $DomainAdministratorCredential
            NoRebootOnCompletion          = $true
            Force                         = $true
        }

        if ($PSBoundParameters.ContainsKey('DatabasePath'))
        {
            $installADDSDomainControllerParameters.Add('DatabasePath', $DatabasePath)
        }

        if ($PSBoundParameters.ContainsKey('LogPath'))
        {
            $installADDSDomainControllerParameters.Add('LogPath', $LogPath)
        }

        if ($PSBoundParameters.ContainsKey('SysvolPath'))
        {
            $installADDSDomainControllerParameters.Add('SysvolPath', $SysvolPath)
        }

        if ($PSBoundParameters.ContainsKey('SiteName') -and $SiteName)
        {
            $installADDSDomainControllerParameters.Add('SiteName', $SiteName)
        }

        if ($PSBoundParameters.ContainsKey('IsGlobalCatalog') -and $IsGlobalCatalog -eq $false)
        {
            $installADDSDomainControllerParameters.Add('NoGlobalCatalog', $true)
        }

        if (-not [System.String]::IsNullOrWhiteSpace($InstallationMediaPath))
        {
            $installADDSDomainControllerParameters.Add('InstallationMediaPath', $InstallationMediaPath)
        }

        Install-ADDSDomainController @installADDSDomainControllerParameters

        Write-Verbose -Message (
            $script:localizedData.Promoted -f $env:COMPUTERNAME, $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

        Write-Verbose -Message (
            $script:localizedData.IsDomainController -f $env:COMPUTERNAME, $DomainName
        )

        # Check if Node Global Catalog state is correct
        if ($PSBoundParameters.ContainsKey('IsGlobalCatalog') -and $targetResource.IsGlobalCatalog -ne $IsGlobalCatalog)
        {
            # DC is not in the expected Global Catalog state
            if ($IsGlobalCatalog)
            {
                $globalCatalogOptionValue = 1

                Write-Verbose -Message $script:localizedData.AddGlobalCatalog
            }
            else
            {
                $globalCatalogOptionValue = 0

                Write-Verbose -Message $script:localizedData.RemoveGlobalCatalog
            }

            $domainControllerObject = Get-DomainControllerObject -DomainName $DomainName -ComputerName $env:COMPUTERNAME -Credential $DomainAdministratorCredential
            if ($domainControllerObject)
            {
                Set-ADObject -Identity $domainControllerObject.NTDSSettingsObjectDN -Replace @{
                    options = $globalCatalogOptionValue
                }
            }
            else
            {
                $errorMessage = $script:localizedData.ExpectedDomainController
                New-ObjectNotFoundException -Message $errorMessage
            }
        }

        if ($PSBoundParameters.ContainsKey('SiteName') -and $targetResource.SiteName -ne $SiteName)
        {
            Write-Verbose -Message (
                $script:localizedData.IsDomainController -f $targetResource.SiteName, $SiteName
            )

            # DC is not in correct site. Move it.
            Write-Verbose -Message ($script:localizedData.MovingDomainController -f $targetResource.SiteName, $SiteName)
            Move-ADDirectoryServer -Identity $env:COMPUTERNAME -Site $SiteName -Credential $DomainAdministratorCredential
        }
    }
}

<#
    .SYNOPSIS
        Determines if the domain controller is in desired state.
 
    .PARAMETER DomainName
        Provide the FQDN of the domain the Domain Controller is being added to.
 
    .PARAMETER DomainAdministrationCredential
        Specifies the credential for the account used to install the domain controller.
        This account must have permission to access the other domain controllers
        in the domain to be able replicate domain information.
 
    .PARAMETER SafemodeAdministratorPassword
        Provide a password that will be used to set the DSRM password. This is a PSCredential.
 
    .PARAMETER DatabasePath
        Provide the path where the NTDS.dit will be created and stored.
 
    .PARAMETER LogPath
        Provide the path where the logs for the NTDS will be created and stored.
 
    .PARAMETER SysvolPath
        Provide the path where the Sysvol will be created and stored.
 
    .PARAMETER SiteName
        Provide the name of the site you want the Domain Controller to be added to.
 
    .PARAMETER InstallationMediaPath
        Provide the path for the IFM folder that was created with ntdsutil.
        This should not be on a share but locally to the Domain Controller being promoted.
 
    .PARAMETER IsGlobalCatalog
        Specifies if the domain controller will be a Global Catalog (GC).
#>

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

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $DomainAdministratorCredential,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $SafemodeAdministratorPassword,

        [Parameter()]
        [System.String]
        $DatabasePath,

        [Parameter()]
        [System.String]
        $LogPath,

        [Parameter()]
        [System.String]
        $SysvolPath,

        [Parameter()]
        [System.String]
        $SiteName,

        [Parameter()]
        [System.String]
        $InstallationMediaPath,

        [Parameter()]
        [System.Boolean]
        $IsGlobalCatalog
    )

    Write-Verbose -Message (
        $script:localizedData.TestingConfiguration -f $env:COMPUTERNAME, $DomainName
    )

    if ($PSBoundParameters.SiteName)
    {
        if (-not (Test-ADReplicationSite -SiteName $SiteName -DomainName $DomainName -Credential $DomainAdministratorCredential))
        {
            $errorMessage = $script:localizedData.FailedToFindSite -f $SiteName, $DomainName
            New-ObjectNotFoundException -Message $errorMessage
        }
    }

    $getTargetResourceParameters = @{} + $PSBoundParameters
    $getTargetResourceParameters.Remove('InstallationMediaPath')
    $getTargetResourceParameters.Remove('IsGlobalCatalog')
    $existingResource = Get-TargetResource @getTargetResourceParameters

    $testTargetResourceReturnValue = $existingResource.Ensure

    if ($PSBoundParameters.ContainsKey('SiteName') -and $existingResource.SiteName -ne $SiteName)
    {
        Write-Verbose -Message (
            $script:localizedData.WrongSite -f $existingResource.SiteName, $SiteName
        )

        $testTargetResourceReturnValue = $false
    }

    # Check Global Catalog Config
    if ($PSBoundParameters.ContainsKey('IsGlobalCatalog') -and $existingResource.IsGlobalCatalog -ne $IsGlobalCatalog)
    {
        if ($IsGlobalCatalog)
        {
            Write-Verbose -Message (
                $script:localizedData.ExpectedGlobalCatalogEnabled -f $existingResource.SiteName, $SiteName
            )
        }
        else
        {
            Write-Verbose -Message (
                $script:localizedData.ExpectedGlobalCatalogDisabled -f $existingResource.SiteName, $SiteName
            )
        }

        $testTargetResourceReturnValue = $false
    }

    return $testTargetResourceReturnValue
}

Export-ModuleMember -Function *-TargetResource