One Config | Multiple Nodes

This topic contains 4 replies, has 5 voices, and was last updated by Profile photo of Nana Lakshmanan Nana Lakshmanan 8 months, 2 weeks ago.

  • Author
    Posts
  • #35980
    Profile photo of Peter Cashen
    Peter Cashen
    Participant

    Hi All,

    I'm wondering about the best way to apply one config to multiple machines without creating multiple mof files etc.

    So I'm thinking about 'roles' so a config for say a ConfigMgr Distribution Point...

    At the moment, I call servers from an OU in AD, and configure the LCM with the same GUID for all of them... that way they all pull the same config. I've simply created this GUID from an online guid generator ha... it works, but I think there must be a tidier way?!

    Peter

  • #35984
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    That's the basic gist of it. If your goal is to avoid having multiple (potentially duplicate) MOFs, then you need to create a single MOF file with a particular GUID (or friendly name, in PSv5), and configure all of the LCMs to pull that config.

  • #35985
    Profile photo of Ed O'Connor
    Ed O’Connor
    Participant

    Hi Pete:
    I don't know if this is the best way but I can share how I handled this. BTW, I am using WMF5 and configuring the LCM via Configuration Names.

    Basically, what I do is get the node name and node objectGUID from AD. Then I I create my mof generation script using a descriptive name for the 'node' (i.e. what ever the config is for, in my case 'DSCServers'). I then take the mof; generate the checksum, copy to the config folder, also I query AD again and get the node Object GUID and add it to the 'RegistrationKeys.txt' file. Then I set the LCM to use the ObjectGUIDS for the RegistrationKey and the single mof name for the ConfigurationNames.

    Here is my scripts:
    Gets Computers and GUID from AD:

    #Pull computer objects for AD
        function GetComputers {
            import-module ActiveDirectory
            Get-ADComputer -SearchBase "OU=DSC Managed Nodes,OU=SERVERS,OU=NYC,OU=Americas,DC=testlab,DC=testing,DC=com" -Filter *
        }
        $computers = GetComputers
    
    #Pull list of computers and GUIDs into hash table
        $ConfigData = @{
            AllNodes = @(
                foreach ($node in $computers) {
                    @{NodeName = $node.Name; NodeGUID = $node.objectGUID;}
                }
            )
        } 
    

    MOF Generation Script

    Configuration TestConfig
    
    {
    
    Import-DscResource -ModuleName 'PSDesiredStateConfiguration'
    
     Node DSCServers 
     {
    
      #Leaves a timestamp indicating the last time this script was run on the node (this resource is intentionally not idempotent)
        Script LeaveTimestamp
        {
            SetScript = {
            $currentTime = $null
            $currentTimeString = $null
    
            $currentTime = Get-Date
            $currentTimeString = $currentTime.ToString()
            [Environment]::SetEnvironmentVariable("DSCClientRun","Last DSC-Client run: $currentTimeString","Machine")
            eventcreate /t INFORMATION /ID 1 /L APPLICATION /SO "DSC-Client" /D "(1)Last DSC Servers Configuration run: $currentTimeString"
        }
        TestScript = {
          #$False
            $LastLogEntry = $null
            $diffMinutes = $null
            $EvtLog30Min = $False
            $LastLogEntry = (Get-EventLog "Application" | Where-Object {$_.EventID -eq 1})
            If ($LastLogEntry -like "*") {
    	    $diffMinutes = (New-TimeSpan $LastLogEntry.TimeGenerated[0] (Get-Date)).TotalMinutes
                If ($diffMinutes -lt 29) {$EvtLog30Min = $True}
          
                    }
            
    		$EvtLog30Min
            
        }
        GetScript = {
        return @{Result="Writes a Application Log Event every time DSC Config is run"}
        }
        }
    	#Install Role 'DHCP Server'
    		WindowsFeature DHCP
    		{
    		Ensure = "Present"
    		Name = "DHCP"
    		}
    	#Install Role 'DNS Server'
    		WindowsFeature DNS
    		{
    		Ensure = "Present"
    		Name = "DNS"
    		}
    	#Copies the Firewall Rule File
    		File "FWRuleFilecopy"
            {
                Ensure = "Present" 
                Type = "Directory"
                MatchSource = $True
    			Checksum = "SHA-256"
                Recurse = $true
                SourcePath = "\\lab-dsc-01\shared\FWRules"
                DestinationPath = "C:\DSCFiles\FWRules"    
            }
            Log AfterFWRuleFileCopy
            {
                # The message below gets written to the Microsoft-Windows-Desired State Configuration/Analytic log
                Message = "Finished running the file resource with ID FW Rule File copy"
                DependsOn = "[File]FWRuleFilecopy" # This means run "DirectoryCopy" first.
            }
    	#Copies the Print Drivers File
    		File "PrintDriverFilecopy"
            {
                Ensure = "Present" 
                Type = "Directory"
                MatchSource = $True
    			Checksum = "SHA-256"
                Recurse = $true
                SourcePath = "\\lab-dsc-01\shared\PrintDrivers"
                DestinationPath = "C:\DSCFiles\PrintDrivers"    
            }
            Log AfterPrintDriverFileCopy
            {
                # The message below gets written to the Microsoft-Windows-Desired State Configuration/Analytic log
                Message = "Finished running the file resource with ID Print Drivers File copy"
                DependsOn = "[File]PrintDriverFilecopy" # This means run "DirectoryCopy" first.
            }	
        #Ensure WindowsFirewall Running
            Service WindowsFirewall
            {
                Name = "MPSSvc"
                StartupType = "Automatic"
                State = "Running"
            }
    	#Sets Page File Size
            Registry "Sets Page File Size"
            {
                Ensure = "Present"
                Key = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management"
                ValueName = "PagingFiles"
                ValueType = "Multistring"
    			ValueData = "c:\pagefile.sys 6142 6142"
            }
        #Disable TCPIPv6
            Registry DisableTCPIPv6 
            {
                Ensure = "Present"
                Key = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP6\Parameters"
                ValueName = "DisabledComponents"
                ValueType = "DWord"
                ValueData = "255"
            }
        #Disable EDNSProbes
            Registry DisableEDNSProbes 
            {
                Ensure = "Present"
                Key = "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\DNS\Parameters"
                ValueName = "EnableEDNSProbes"
                ValueType = "DWord"
                ValueData = "0"
            } 
        }
    }
    
    TestConfig -ConfigurationData $ConfigData -OutputPath "$Env:Temp\TestConfig"
    

    Generates the Checksum, copies to configuration folder, and adds GUID to RegistrationKeys.txt

    #Creates CheckSums for the .mof files
    
        write-host "Creating checksums..."
        New-DSCCheckSum -ConfigurationPath "$Env:Temp\TestConfig" -OutPath "$Env:Temp\TestConfig" -Verbose -Force
    
    #Copies the Node ObjectGUID to the RegistrationKeys file
    
        write-host "Getting the Node ObjectGUID and copying to the RegistrationKeys file..."
    
        $CompGuid = (Get-ADComputer -SearchBase "OU=DSC Managed Nodes,OU=SERVERS,OU=NYC,OU=Americas,DC=lab2,DC=mckinsey,DC=com" -filter *).objectguid
    
    
        foreach ($Guid in $CompGuid) {
        if($guid.guid -notin (gc "C:\Program Files\WindowsPowerShell\DscService\RegistrationKeys.txt")){
    
        $GUID.guid | out-file "C:\Program Files\WindowsPowerShell\DscService\RegistrationKeys.txt" -append
        }
        }
    
    #Copies the configuration/.mof files and checksum files to the configuration store
    
        write-host "Copying configurations to pull service configuration store..."
        $SourceFiles = "$Env:Temp\TestConfig\*.mof*"
        $TargetFiles = "$env:SystemDrive\Program Files\WindowsPowershell\DscService\Configuration"
        Move-Item $SourceFiles $TargetFiles -Force
        Remove-Item "$Env:Temp\TestConfig\"
    

    Config the LCM

    [DSCLocalConfigurationManager()]
    
    Configuration ConfigurationForPull
    { 
        Node $allnodes.NodeName
        {
            Settings 
            { 
                RefreshMode = "PULL";
                AllowModuleOverwrite = $True;
                RebootNodeIfNeeded = $True;
                RefreshFrequencyMins = 30;
                ConfigurationModeFrequencyMins = 15; 
                ConfigurationMode = "ApplyAndAutoCorrect";
            }
            ConfigurationRepositoryWeb lab-dsc-01
            {
                AllowUnsecureConnection = $True
                ServerUrl = "http://lab-dsc-01:8080/PSDSCPullServer.svc"
                RegistrationKey = "$($Node.NodeGUID)"
                ConfigurationNames = @("DSCServers")
            }
        }
        } 
    
    ConfigurationForPull -ConfigurationData $ConfigData -OutputPath "$Env:Temp\PullDSCCfg"
    Set-DscLocalConfigurationManager -Path "$Env:Temp\PullDSCCfg" -verbose -force
    

    I hope this helps a bit.

  • #36013
    Profile photo of Arie H
    Arie H
    Participant

    Hi,
    @Ed : That's a nice trick with the GUID in the registrationkey.txt but I consider it an overkill as one key would be enough. I'd hate to maintain that long list of GUIDs when you decommission a server. Also the key itself is deleted from the LCM after the initial registration to the pull server.

    @Peter : The example that Ed posted is the way to do it imho. What the example needs are changes to the $Configdata to set Roles, which you might get from a custom property in your ad schema for example, and in the mof generation script at the node section to check the role and apply the settings, and the have a node section per role with appropriate settings in it. This will undoubtedly make this script huge and abit hard to manage.

    That said one mof to control them all isn't the best way. Not saying one mof per node is better..its not, but say 5-10 mofs will be better solution. In Eds example, you can separate the long configuration file into blocks, together with Roles in your configurationData, generate the mofs with specific names and add them all to the LCM ConfigurationNames value.
    This will give several values:

    1. More flexibility in allowing more differentiation in the servers

    2. Easier unit testing. Cant emphasize enough how much pester is important.

    3. You never change configuration scripts, you just push a LCM that has different values in the ConfigurationNames.

    4. You're not limited to host provisioning but you can also apply this to software you want to run on that node, although Don keeps reminding us, rightfully, that we shouldn't bend DSC to do things it might not have been ideally created for 😉
    I still use dsc for applications, via Package resource or PackageManagt to pull from our internal nuget repo, mainly as I going to swap my RM agent based deployments to dsc based to allow me to get tfs 2015 and RM vNext on-premise coming later this year.

    To be fair there is a cavity in the form of harder control when you want to have cross-node dependency, but that can be addressed as well.

  • #36742
    Profile photo of Nana Lakshmanan
    Nana Lakshmanan
    Participant

    The correct way of specifying the same mof to different nodes is using the ConfigurationNames property as described here https://msdn.microsoft.com/en-us/powershell/dsc/pullclientconfignames

You must be logged in to reply to this topic.