Imagine standing at the threshold of a fully automated Windows environment, where your domain controllers and RDS infrastructure are configured exactly the way you want, every time, without lifting a finger. That’s the power we’re exploring in this post. Building on our previous deep dives into DSC fundamentals, security, configuration data, and composite resources, we’ll now bring the theory to life, two domain controllers, a complete RDS session environment, all orchestrated seamlessly with DSC. Let’s turn configuration into automation you can trust. There is a lot to cover in this post so let’s get started.

Current Infrastructure & Requirements

For this demonstration, I set up 5 virtual machines. We will install primary domain controller, and then promote the second one. All other VM’s will wait for the domain to complete to continue with domain join and RDS config. The configuration data is excluded from the current configuration document and will be imported during the deployment process. A certificate has been installed on all nodes, which is required for MOF credential encryption. All MOF files will be automatically copied to their respective nodes, ensuring that each node receives only its own MOF file.

  • Required Modules:
    • ActiveDirectoryDsc (6.7.1)
    • DhcpServerDsc (4.0.0)
    • CompositeDSC (1.0)
    • RemoteDesktopServicesDsc (4.0.0) (You can download updated RemoteDesktopServicesDsc Module below)
    • ComputerManagementDsc (10.0.0)

DC01 (Windows Server 2025)

  • Primary Domain Controller
  • Roles: DNS, DHCP
  • Modules: CompositeDSC (This is the module that we built in Part 6), ActiveDirectoryDsc, DhcpServerDsc

DC02 (Windows Server 2025)

  • Secondary Domain Controller
  • Roles: DNS
  • Modules: ActiveDirectoryDsc

RDCB01 (Windows Server 2025)

  • Roles: RDS Connection Broker, Web Access
  • Modules: ActiveDirectoryDsc, RemoteDesktopServicesDsc, ComputerManagementDsc

RDSH01 (Windows Server 2025)

  • Roles: RDS Session Host
  • Modules: ActiveDirectoryDsc, RemoteDesktopServicesDsc, ComputerManagementDsc

RDWA01 (Windows Server 2025)

  • Roles: RDS Web Access
  • Modules: ActiveDirectoryDsc, RemoteDesktopServicesDsc, ComputerManagementDsc

CONFIGURATION DATA

Our first step is to build configuration data. For this demonstration, I will include RDS and DHCP information, but we could also add other configurations such as SQL or Web Server settings.

This time the config data will not be part of the actual configuration document. Microsoft explicitly recommends storing node-specific and environment-specific information in a separate files. Once created, save it as .psd1 file.

@{
    AllNodes = @(
 
        @{
            NodeName             = '*'
            PSDscAllowDomainUser = $true
            CertificateFile      = 'C:\Certs\DSCCredsEncryption.cer'
 
        },
 
        @{
 
            NodeName = 'DC01'
            Role     = 'Primary DC'
        },

        @{
 
            NodeName = 'DC02'
            Role     = 'Secondary DC'
        },

        @{
 
            NodeName = 'RDCB01'
            Role     = 'RD Connection Broker'
        },

        @{
 
            NodeName = 'RDSH01'
            Role     = 'RD Session Host'
        },

        @{
 
            NodeName = 'RDWA01'
            Role     = 'RD Web Access'
        }
    );
 
    ADData = @{
 
        DomainName        = 'mehic.se'
        DomainNetBiosName = 'MEHIC'
        
    }

    RDSData = @{
 
        ConnectionBroker             = 'rdcb01.mehic.se'
        SessionHost                  = 'rdsh01.mehic.se'
        WebAccessServer              = 'rdwa01.mehic.se'
        CollectionName               = 'Megablast RDS'
        AutomaticReconnectionEnabled = $true
        DisconnectedSessionLimitMin  = 360
        IdleSessionLimitMin          = 360
        BrokenConnectionAction       = 'Disconnect'
        LicenseServer                = 'rdcb01.mehic.se'
        LicenseMode                  = 'PerUser'
        
    }

    DHCPData = @{
 
        Name             = 'HBG Office'
        IPStartRange     = '192.168.0.120'
        IPEndRange       = '192.168.0.130'
        SubnetMask       = '255.255.255.0'
        ScopeId          = '192.168.0.0'
        AddressFamily    = 'IPv4'
        State            = 'Active'
        LeaseDuration    = '00:08:00'  
        IsSingleInstance = 'Yes'  
        DNSName          = 'DC01.mehic.se'
        IPaddress        = '192.168.0.10'


    }
        
}

BUILDING ENVIRONMENT

After defining the configuration data, we proceed to the construction of the Desired State Configuration environment. The following sections provide a detailed, structured breakdown of each stage in the process. The solution is designed to be fully automated, ensuring that no manual actions are required.

The first part defines the local and remote paths and configuration data location used by DSC. These paths determine where MOF files are generated on the authoring machine and where they will be copied on the destination nodes. Standardizing these paths ensures consistent deployment across all participating virtual machines. The second part prepares connectivity to all virtual machines that will take part in the configuration. An array of VM definitions is created using $VMs = @( … ). Each entry in this array represents a target node and contains the VM name. This structure allows the configuration to scale easily—additional nodes can be added by simply appending new entries to the array.

Using this list of VMs, PowerShell retrieves the corresponding VM’s and establishes new PSSessions to each one using provided administrative credentials. These sessions enable DSC to remotely copy MOF files and apply configurations across all nodes in an automated and consistent manner.

Next, we need to make sure that all the required modules are installed on the target nodes and the management device. New modules that we added to this config are: DhcpServerDsc, RemoteDesktopServicesDsc and ComputerManagementDsc.

First Node is the primary domain controller. First we need to configure LCM and then install all required roles. When that is done, we will install the domain and configure DHCP.

Node $PrimaryDCNode {
        $AD = $data.ADData
        $DHCP = $data.DHCPData

        LocalConfigurationManager {

            RebootNodeIfNeeded = $true
            ConfigurationMode = 'ApplyAndAutoCorrect'
            CertificateID      = '23D658C553CB1642BAACFADFF945B980F74B5185'
        }

        WindowsFeature ADDSInstall {

            Name                   = "AD-Domain-Services"
            Ensure                 = 'Present'
        }

        WindowsFeature RSATTools {

            Name                 = 'RSAT-AD-Tools'
            IncludeAllSubFeature = $true
            Ensure               = 'Present'
            DependsOn            = "[WindowsFeature]ADDSInstall"

        }

        WindowsFeature DHCP {

            Name   = 'DHCP'
            Ensure = 'Present'
        }

        WindowsFeature DHCPRSATTools {

            Name                 = 'RSAT-DHCP'
            Ensure               = 'Present'
            IncludeAllSubFeature = $true
            DependsOn            = "[WindowsFeature]DHCP"
        }

        ADDomain NewADForest {

            Credential                    = $DomainCreds
            DomainName                    = $AD.DomainName
            DomainNetBiosName             = $AD.DomainNetBiosName
            SafeModeAdministratorPassword = $SafeModeAdministratorPassword
            DependsOn                     = "[WindowsFeature]ADDSInstall"
        }

        CompanyADOUStructure ADStructure {

            CompanyName = 'Megablast AB'
            DomainDN    = 'DC=mehic,DC=se'
            DependsOn   = "[ADDomain]NewADForest"

        }

        xDhcpServerAuthorization DHCPAuthorization {

            IsSingleInstance     = $DHCP.IsSingleInstance
            Ensure               = 'Present'
            PsDscRunAsCredential = $DomainCreds
            DNSName              = $dhcp.DNSName
            DependsOn            = @("[WindowsFeature]DHCP", "[ADDomain]NewADForest")
        }

        xDhcpServerScope DHCPScope {

            Name          = $dhcp.Name
            IPStartRange  = $dhcp.IPStartRange
            IPEndRange    = $dhcp.IPEndRange
            SubnetMask    = $dhcp.SubnetMask
            ScopeId       = $dhcp.ScopeId
            AddressFamily = $dhcp.AddressFamily
            State         = $dhcp.State
            LeaseDuration = $dhcp.LeaseDuration
            DependsOn     = "[xDhcpServerAuthorization]DHCPAuthorization"
        }

    }

The second domain controller waits for the domain to be fully configured before continuing with the promotion process. We are using WaitForADDomain resource for that operation.

Node $SecondaryDCNode {
        $AD = $data.ADData

        LocalConfigurationManager {

            RebootNodeIfNeeded = $true
            ConfigurationMode  = 'ApplyAndAutoCorrect'
            CertificateID      = '23D658C553CB1642BAACFADFF945B980F74B5185'
        }

        WindowsFeature ADDSInstall {

            Name   = "AD-Domain-Services"
            Ensure = 'Present'
        }

        WindowsFeature RSATTools {

            Name                 = 'RSAT-AD-Tools'
            IncludeAllSubFeature = $true
            Ensure               = 'Present'
            DependsOn            = "[WindowsFeature]ADDSInstall"
        }

        WaitForADDomain WaitforDomain {

            DomainName    = $AD.DomainName
            Credential    = $DomainCreds
            WaitTimeout   = 1800
        }
            
        ADDomainController SecondDC {

            DomainName                    = $AD.DomainName
            Credential                    = $DomainCreds
            SafemodeAdministratorPassword = $SafeModeAdministratorPassword
            InstallDns                    = $true
            IsGlobalCatalog               = $true
            DependsOn                     = "WaitForADDomain]WaitforDomain"

        }
}

The third section focuses on Remote Desktop Services (RDS). The first node configured is the RD Connection Broker. Before applying any RDS-specific configuration, the server must be joined to the domain. This is accomplished using the ComputerManagementDsc module, leveraging the Computer resource to perform the domain join and the PendingReboot resource to ensure the system is restarted if required before continuing with the RDS configuration. Once this is complete, the broker installs the required roles but delays configuration until all other nodes have completed the domain join process and their services are installed.

Node $ConnectionBrokerNode {
        $AD = $ConfigurationData.ADData    
        $RD = $ConfigurationData.RDSData

        LocalConfigurationManager {

            RebootNodeIfNeeded = $true
            ConfigurationMode  = 'ApplyAndAutoCorrect'
            CertificateID      = '23D658C553CB1642BAACFADFF945B980F74B5185'
        }

        WindowsFeature RDSConnectionBroker {
 
            Name   = 'RDS-Connection-Broker'
            Ensure = 'Present'
        }
        
        WindowsFeature RDLicensing {
            Ensure = "Present"
            Name   = "RDS-Licensing"
        }

        WindowsFeature RDSTools {

            Name   = 'RSAT-RDS-Tools'
            Ensure = "Present"
        }

        WaitForADDomain WaitforDomain {

            DomainName  = $AD.DomainName
            Credential  = $DomainCreds
            WaitTimeout = 1800
        }

        Computer JoinDomain {

            Name       = $ConnectionBrokerNode
            DomainName = $AD.DomainName
            Credential = $DomainCreds
            DependsOn  = "[WaitForADDomain]WaitforDomain"
        }

        PendingReboot RebootAfterDomainJoin {

            Name      = 'Domain Join'
            DependsOn = '[Computer]JoinDomain'

        }
        
        WaitForAll SessionHost {
 
            NodeName         = $RD.SessionHost
            ResourceName     = '[WindowsFeature]SessionHost'
            RetryIntervalSec = 60
            RetryCount       = 50
            DependsOn        = '[PendingReboot]RebootAfterDomainJoin'
        }

        WaitForAll WebAccess {

            NodeName         = $RD.WebAccessServer
            ResourceName     = '[WindowsFeature]WebAccess'
            RetryIntervalSec = 60
            RetryCount       = 50
            DependsOn        = '[WaitForAll]SessionHost'
        }

        Script WaitBeforeRDSDeployment {

            GetScript  = {
                @{ Result = "WaitingComplete" }
            }

            TestScript = {
                # Always return false so SetScript runs once
                $false
            }

            SetScript  = {
                Write-Verbose "Waiting 240 seconds before RDS deployment..."
                Start-Sleep -Seconds 240
            }

            DependsOn  = '[WaitForAll]WebAccess'
        }

        RDSessionDeployment NewDeployment {

            ConnectionBroker     = $RD.ConnectionBroker
            SessionHost          = $RD.SessionHost
            WebAccessServer      = $RD.WebAccessServer
            DependsOn            = '[Script]WaitBeforeRDSDeployment'
            PsDscRunAsCredential = $DomainCreds
        }

        RDSessionCollection RDCollection {

            CollectionName       = $RD.CollectionName
            SessionHost          = $RD.SessionHost
            ConnectionBroker     = $RD.ConnectionBroker
            DependsOn            = '[RDSessionDeployment]NewDeployment'
            PsDscRunAsCredential = $DomainCreds
        }

        RDSessionCollectionConfiguration CollectionConfig {

            CollectionName               = $RD.CollectionName
            ConnectionBroker             = $RD.ConnectionBroker
            AutomaticReconnectionEnabled = $true
            DisconnectedSessionLimitMin  = $RD.DisconnectedSessionLimitMin
            IdleSessionLimitMin          = $RD.IdleSessionLimitMin
            BrokenConnectionAction       = $RD.BrokenConnectionAction
            DependsOn                    = '[RDSessionCollection]RDCollection'
            PsDscRunAsCredential         = $DomainCreds
        }

        RDLicenseConfiguration Licenseconfig {

            ConnectionBroker     = $RD.ConnectionBroker
            LicenseServer        = $RD.LicenseServer
            LicenseMode          = $RD.LicenseMode
            DependsOn            = '[RDSessionCollectionConfiguration]collectionconfig'
            PsDscRunAsCredential = $DomainCreds
        }
    }

The final two node sections configure the Local Configuration Manager (LCM) and install the required roles on the Session Host and Web Access servers.

Node $RDSessionHostNode {
        $AD = $ConfigurationData.ADData

        LocalConfigurationManager {

            RebootNodeIfNeeded = $true
            ConfigurationMode  = 'ApplyAndAutoCorrect'
            CertificateID      = '23D658C553CB1642BAACFADFF945B980F74B5185'
        }

        WaitForADDomain WaitforDomain {

            DomainName  = $AD.DomainName
            Credential  = $DomainCreds
            WaitTimeout = 1800
        }

        Computer JoinDomain {

            Name       = $RDSessionHostNode
            DomainName = $AD.DomainName
            Credential = $DomainCreds
            DependsOn  = '[WaitForADDomain]WaitforDomain'
        }

        PendingReboot RebootAfterDomainJoinSessionHost {

            Name = 'Domain Join'
            DependsOn = '[Computer]JoinDomain'
                }

        WindowsFeature SessionHost {
            Name      = 'RDS-RD-Server'
            Ensure    = 'Present'
            DependsOn = '[PendingReboot]RebootAfterDomainJoinSessionHost' 
        }       
    }

    #EndRegion

    #Region Join RD Web Access Server to a domain and install RD Web Access role

    Node $RDWebAccessNode {
        $AD = $ConfigurationData.ADData    

        LocalConfigurationManager {

            RebootNodeIfNeeded = $true
            ConfigurationMode  = 'ApplyAndAutoCorrect'
            CertificateID      = '23D658C553CB1642BAACFADFF945B980F74B5185'
        }


        WaitForADDomain WaitforDomain {

            DomainName  = $AD.DomainName
            Credential  = $DomainCreds
            WaitTimeout = 1800
        }

        Computer JoinDomain {

            Name       = $RDWebAccessNode
            DomainName = $AD.DomainName
            Credential = $DomainCreds
            DependsOn  = '[WaitForADDomain]WaitforDomain'
        }

        PendingReboot RebootAfterDomainJoinWebAccess {

            Name = 'Domain Join'
            DependsOn = '[Computer]JoinDomain'
        }

        WindowsFeature WebAccess {
 
            Name   = 'RDS-Web-Access'
            Ensure = 'Present'
            DependsOn = '[PendingReboot]RebootAfterDomainJoinWebAccess'
        }

    }

Once those steps are complete, the configuration moves to its final phase, copying the generated MOF files to the destination servers. Each server receives only its own MOF file, ensuring node-specific configuration. After the files are in place, the configuration is pushed to the target servers to apply the desired state. This will ensure that the deployment is fully automated.

Expand to see the whole code
#Region Specify the local and destination paths. This is required for full automation. MOF files will be copied to all VMs.
$LocalDSCPath = "C:\DSC"
$RemoteDSCPath = "C:\DSC" # Path inside the VMs.
$ConfigData = "C:\DSC\environmentconfigdata.psd1"

#EndRegion


#Region Initialize new PSSessions and establish connections with all VMs.

$VMs = @(
    @{ Name = 'DC01' }
    @{ Name = 'DC02' }
    @{ Name = 'RDCB01' }
    @{ Name = 'RDSH01' }
    @{ Name = 'RDWA01' }
)

$VM = foreach ($VM in $VMs) {

    Get-VM -Name $VM.Name
}

$cred = Get-Credential -UserName administrator -Message "VM admin credentials"

$sessions = foreach ($vm in $VMs) {
    $s = New-PSSession -VMName $vm.Name -Credential $cred
    $s | Add-Member -NotePropertyName VMName -NotePropertyValue $vm.Name -Force
    $s
}

#EndRegion

# Configuration Document InstallEnvironment

Configuration InstallEnvironment {

    param (
 
        [Parameter(Mandatory = $true)]
        [pscredential]$DomainCreds,

        [Parameter(Mandatory = $true)]
        [pscredential]$SafeModeAdministratorPassword
 
    )
    #Region DSC Resource Modules --> OBS!!! These modules MUST exist on the destination host servers
 
    Import-DscResource -ModuleName 'PSDesiredStateConfiguration' ,
    @{ModuleName = 'ActiveDirectoryDsc'; ModuleVersion = "6.7.1" },
    @{ModuleName = 'CompositeDSC'; ModuleVersion = '1.0' },
    @{ModuleName = 'RemoteDesktopServicesDsc'; ModuleVersion = '4.0.0' },
    @{ModuleName = 'DhcpServerDsc'; ModuleVersion = '4.0.0' },
    @{ModuleName = 'ComputerManagementDsc'; ModuleVersion = '10.0.0' }

    #EndRegion


    #All Node Variables 
    $PrimaryDCNode = $AllNodes.Where{ $_.Role -eq 'Primary DC' }.NodeName
    $SecondaryDCNode = $AllNodes.Where{ $_.Role -eq 'Secondary DC' }.NodeName
    $ConnectionBrokerNode = $AllNodes.Where{ $_.Role -eq 'RD Connection Broker' }.NodeName
    $RDSessionHostNode = $AllNodes.Where{ $_.Role -eq 'RD Session Host' }.NodeName
    $RDWebAccessNode = $AllNodes.Where{ $_.Role -eq 'RD Web Access' }.NodeName


    #Region Install and configure Primary Domain Controller

    Node $PrimaryDCNode {
        $AD = $ConfigurationData.ADData
        $DHCP = $ConfigurationData.DHCPData

        LocalConfigurationManager {

            RebootNodeIfNeeded = $true
            ConfigurationMode  = 'ApplyAndAutoCorrect'
            CertificateID      = '23D658C553CB1642BAACFADFF945B980F74B5185'
        }

        WindowsFeature ADDSInstall {

            Name   = "AD-Domain-Services"
            Ensure = 'Present'
        }

        WindowsFeature RSATTools {

            Name                 = 'RSAT-AD-Tools'
            IncludeAllSubFeature = $true
            Ensure               = 'Present'
            DependsOn            = "[WindowsFeature]ADDSInstall"

        }

        WindowsFeature DHCP {

            Name   = 'DHCP'
            Ensure = 'Present'
        }

        WindowsFeature DHCPRSATTools {

            Name                 = 'RSAT-DHCP'
            Ensure               = 'Present'
            IncludeAllSubFeature = $true
            DependsOn            = "[WindowsFeature]DHCP"
        }

        ADDomain NewADForest {

            Credential                    = $DomainCreds
            DomainName                    = $AD.DomainName
            DomainNetBiosName             = $AD.DomainNetBiosName
            SafeModeAdministratorPassword = $SafeModeAdministratorPassword
            DependsOn                     = "[WindowsFeature]ADDSInstall"
        }

        CompanyADOUStructure ADStructure {

            CompanyName = 'Megablast AB'
            DomainDN    = 'DC=mehic,DC=se'
            DependsOn   = "[ADDomain]NewADForest"

        }

        xDhcpServerAuthorization DHCPAuthorization {

            IsSingleInstance     = $DHCP.IsSingleInstance
            Ensure               = 'Present'
            PsDscRunAsCredential = $DomainCreds
            DNSName              = $dhcp.DNSName
            DependsOn            = @("[WindowsFeature]DHCP", "[ADDomain]NewADForest")
        }

        xDhcpServerScope DHCPScope {

            Name          = $dhcp.Name
            IPStartRange  = $dhcp.IPStartRange
            IPEndRange    = $dhcp.IPEndRange
            SubnetMask    = $dhcp.SubnetMask
            ScopeId       = $dhcp.ScopeId
            AddressFamily = $dhcp.AddressFamily
            State         = $dhcp.State
            LeaseDuration = $dhcp.LeaseDuration
            DependsOn     = "[xDhcpServerAuthorization]DHCPAuthorization"
        }

    }
   
    #EndRegion

    #Region Install and configure secondary Domain Controller

    Node $SecondaryDCNode {
        $AD = $ConfigurationData.ADData

        LocalConfigurationManager {

            RebootNodeIfNeeded = $true
            ConfigurationMode  = 'ApplyAndAutoCorrect'
            CertificateID      = '23D658C553CB1642BAACFADFF945B980F74B5185'
        }

        WindowsFeature ADDSInstall {

            Name   = "AD-Domain-Services"
            Ensure = 'Present'
        }

        WindowsFeature RSATTools {

            Name                 = 'RSAT-AD-Tools'
            IncludeAllSubFeature = $true
            Ensure               = 'Present'
            DependsOn            = "[WindowsFeature]ADDSInstall"
        }

        WaitForADDomain WaitforDomain {

            DomainName  = $AD.DomainName
            Credential  = $DomainCreds
            WaitTimeout = 1800
        }
            
        ADDomainController SecondDC {

            DomainName                    = $AD.DomainName
            Credential                    = $DomainCreds
            SafemodeAdministratorPassword = $SafeModeAdministratorPassword
            InstallDns                    = $true
            IsGlobalCatalog               = $true
            DependsOn                     = "[WaitForADDomain]WaitforDomain"

        }
    }

    #EndRegion


    #Region Join Server to a domain and Install and configure RD Connetion Broker and RD Collection

    Node $ConnectionBrokerNode {
        $AD = $ConfigurationData.ADData    
        $RD = $ConfigurationData.RDSData

        LocalConfigurationManager {

            RebootNodeIfNeeded = $true
            ConfigurationMode  = 'ApplyAndAutoCorrect'
            CertificateID      = '23D658C553CB1642BAACFADFF945B980F74B5185'
        }

        WindowsFeature RDSConnectionBroker {
 
            Name   = 'RDS-Connection-Broker'
            Ensure = 'Present'
        }
        
        WindowsFeature RDLicensing {
            Ensure = "Present"
            Name   = "RDS-Licensing"
        }

        WindowsFeature RDSTools {

            Name   = 'RSAT-RDS-Tools'
            Ensure = "Present"
        }

        WaitForADDomain WaitforDomain {

            DomainName  = $AD.DomainName
            Credential  = $DomainCreds
            WaitTimeout = 1800
        }

        Computer JoinDomain {

            Name       = $ConnectionBrokerNode
            DomainName = $AD.DomainName
            Credential = $DomainCreds
            DependsOn  = "[WaitForADDomain]WaitforDomain"
        }

        PendingReboot RebootAfterDomainJoin {

            Name      = 'Domain Join'
            DependsOn = '[Computer]JoinDomain'

        }
        
        WaitForAll SessionHost {
 
            NodeName         = $RD.SessionHost
            ResourceName     = '[WindowsFeature]SessionHost'
            RetryIntervalSec = 60
            RetryCount       = 50
            DependsOn        = '[PendingReboot]RebootAfterDomainJoin'
        }

        WaitForAll WebAccess {

            NodeName         = $RD.WebAccessServer
            ResourceName     = '[WindowsFeature]WebAccess'
            RetryIntervalSec = 60
            RetryCount       = 50
            DependsOn        = '[WaitForAll]SessionHost'
        }

        Script WaitBeforeRDSDeployment {

            GetScript  = {
                @{ Result = "WaitingComplete" }
            }

            TestScript = {
                # Always return false so SetScript runs once
                $false
            }

            SetScript  = {
                Write-Verbose "Waiting 240 seconds before RDS deployment resource..."
                Start-Sleep -Seconds 240
            }

            DependsOn  = '[WaitForAll]WebAccess'
        }

        RDSessionDeployment NewDeployment {

            ConnectionBroker     = $RD.ConnectionBroker
            SessionHost          = $RD.SessionHost
            WebAccessServer      = $RD.WebAccessServer
            DependsOn            = '[Script]WaitBeforeRDSDeployment'
            PsDscRunAsCredential = $DomainCreds
        }

        RDSessionCollection RDCollection {

            CollectionName       = $RD.CollectionName
            SessionHost          = $RD.SessionHost
            ConnectionBroker     = $RD.ConnectionBroker
            DependsOn            = '[RDSessionDeployment]NewDeployment'
            PsDscRunAsCredential = $DomainCreds
        }

        RDSessionCollectionConfiguration CollectionConfig {

            CollectionName               = $RD.CollectionName
            ConnectionBroker             = $RD.ConnectionBroker
            AutomaticReconnectionEnabled = $true
            DisconnectedSessionLimitMin  = $RD.DisconnectedSessionLimitMin
            IdleSessionLimitMin          = $RD.IdleSessionLimitMin
            BrokenConnectionAction       = $RD.BrokenConnectionAction
            DependsOn                    = '[RDSessionCollection]RDCollection'
            PsDscRunAsCredential         = $DomainCreds
        }

        RDLicenseConfiguration Licenseconfig {

            ConnectionBroker     = $RD.ConnectionBroker
            LicenseServer        = $RD.LicenseServer
            LicenseMode          = $RD.LicenseMode
            DependsOn            = '[RDSessionCollectionConfiguration]collectionconfig'
            PsDscRunAsCredential = $DomainCreds
        }
    }

    #EndRegion

    #Region Join RD Session Host server to a domain and install RD Session Host role

    Node $RDSessionHostNode {
        $AD = $ConfigurationData.ADData

        LocalConfigurationManager {

            RebootNodeIfNeeded = $true
            ConfigurationMode  = 'ApplyAndAutoCorrect'
            CertificateID      = '23D658C553CB1642BAACFADFF945B980F74B5185'
        }

        WaitForADDomain WaitforDomain {

            DomainName  = $AD.DomainName
            Credential  = $DomainCreds
            WaitTimeout = 1800
        }

        Computer JoinDomain {

            Name       = $RDSessionHostNode
            DomainName = $AD.DomainName
            Credential = $DomainCreds
            DependsOn  = '[WaitForADDomain]WaitforDomain'
        }

        PendingReboot RebootAfterDomainJoinSessionHost {

            Name = 'Domain Join'
            DependsOn = '[Computer]JoinDomain'
                }

        WindowsFeature SessionHost {
            Name      = 'RDS-RD-Server'
            Ensure    = 'Present'
            DependsOn = '[PendingReboot]RebootAfterDomainJoinSessionHost' 
        }       
    }

    #EndRegion

    #Region Join RD Web Access Server to a domain and install RD Web Access role

    Node $RDWebAccessNode {
        $AD = $ConfigurationData.ADData    

        LocalConfigurationManager {

            RebootNodeIfNeeded = $true
            ConfigurationMode  = 'ApplyAndAutoCorrect'
            CertificateID      = '23D658C553CB1642BAACFADFF945B980F74B5185'
        }


        WaitForADDomain WaitforDomain {

            DomainName  = $AD.DomainName
            Credential  = $DomainCreds
            WaitTimeout = 1800
        }

        Computer JoinDomain {

            Name       = $RDWebAccessNode
            DomainName = $AD.DomainName
            Credential = $DomainCreds
            DependsOn  = '[WaitForADDomain]WaitforDomain'
        }

        PendingReboot RebootAfterDomainJoinWebAccess {

            Name = 'Domain Join'
            DependsOn = '[Computer]JoinDomain'
        }

        WindowsFeature WebAccess {
 
            Name   = 'RDS-Web-Access'
            Ensure = 'Present'
            DependsOn = '[PendingReboot]RebootAfterDomainJoinWebAccess'
        }

    }

    #EndRegion

}

    

InstallEnvironment -ConfigurationData $ConfigData -SafeModeAdministratorPassword (Get-Credential -UserName '(Only Password)' -Message "Safe Mode Pass Here") `
    -DomainCreds (Get-Credential -UserName administrator@mehic.se -Message 'Domain Admin Creds') -OutputPath C:\DSC

  


foreach ($session in $sessions) {

    $vmName = $session.VMName

    # Get both node MOF and meta MOF
    $mofFiles = Get-ChildItem -Path $LocalDSCPath -Filter "$vmName*.mof" -ErrorAction SilentlyContinue

    if (-not $mofFiles) {
        Write-Warning "No MOF files found for $vmName"
        continue
    }

    # Ensure remote folder exists
    Invoke-Command -Session $session -ScriptBlock {
        param($remoteDSCPath)
        if (-not (Test-Path $remoteDSCPath)) {
            New-Item -ItemType Directory -Path $remoteDSCPath -Force | Out-Null
        }
    } -ArgumentList $RemoteDSCPath

    # Copy only this node's MOF files
    foreach ($mof in $mofFiles) {
        Copy-Item -Path $mof.FullName -Destination $RemoteDSCPath -ToSession $session -Force
    }
}

Invoke-Command -Session $sessions -ScriptBlock { 

    Write-Host "Applying LCM..."
    Set-DscLocalConfigurationManager -Path C:\DSC -Verbose

    Write-Host "Applying Domain Controller configuration..."
    Start-DscConfiguration -Path C:\DSC -Wait -Force -Verbose
}



We can see the whole process in the verbose mode. Before continuing with configuration steps that depend on Active Directory, the servers wait until the domain is fully available.

After ca 10 min we have whole environment up and running.

One of the key strengths of DSC in a multi-server deployment is orchestration through dependencies. Each server installs its required roles, waits for prerequisites such as Active Directory availability, performs domain join operations, handles required reboots, and then continues configuration automatically. This ensures that configuration steps occur in the correct order and only when the environment is ready.

However, distributed environments introduce timing considerations. Unlike a single-server deployment, where operations occur sequentially, multiple nodes operate independently. Proper dependency handling and synchronization are therefore critical to avoid race conditions and timeouts. By implementing explicit wait conditions and clear dependency chains, we ensure stability and reliability throughout the deployment process.

By defining the desired state clearly and managing dependencies properly, we create a robust, automated deployment process that reduces manual effort, minimizes configuration errors, and provides long-term operational stability.

Thanks for reading.

Cheers

Leave a comment

Trending