Quick Links

Have you ever thought about trying out DSC but felt like the learning curve is too steep? Or perhaps you never saw yourself ever using it in production, so you didn't bother?

Using DSC doesn't have to be hard or complex, and you don't need a gigantic infrastructure to make it worthwhile. This article is going to talk about DSC and how to take the first steps, and it includes examples of how you can get started by applying it in small scale into production.

What Is DSC?

Desired State Configuration (DSC) is a configuration management system that is built into Windows and enables you to configure your system As Code. It uses a configuration written in PowerShell that is later on converted to a Managed Object Format (MOF).

The MOF file is later read and applied by the Local Configuration Manager Service on the target node (computer).

The configuration that's created and ran uses different DSC resources from DSC Modules. DSC Modules are like a regular PowerShell Module, and the resources are its functions. For example, you can install them using

        Install-Module
    

, and they are stored under in the PSModulePath.

What Can I Use DSC for?

The hardest part about DSC for many is to find use cases for it. People tend to talk and write about DSC only when it comes to orchestrating and spinning up large environments, or test and QA for different apps, which is a far away topic for many admins.

We can use DSC for our everyday routines and a lot more, without having to restructure our entire organization. I compiled a list of a few areas that you can either fully or partly automate with DSC:

Example: Reading and Writing a DSC configuration

A few lines of DSC can be equal to hundreds of lines with raw PowerShell, that's why it can be so incredibly powerful.

A DSC configuration consists mainly of 2 parts: The DSC configuration and the configuration data.

The configuration data (explained further down) can either be included externally, usually from a PSD file, or included in the configuration file. The recommended and most maintainable approach is to include this from a separate file.

Following is an example of how to create a DSC MOF that downloads and installs a generic MSI when applied:

        Configuration MyDSCConfiguration {
    # Import DSC modules with resources to use in the configuration
    Import-DscResource -ModuleName PSDesiredStateConfiguration
    Import-DscResource -ModuleName xPSDesiredStateConfiguration -ModuleVersion "8.10.0.0"

    ##############################################
    # Creates configuration for the server fileserver-1
    ##############################################
    Node "fileserver-1" {
       
        # Create C:ProgramDataSoftwarePackages to download files to on all nodes
        File SoftwarePackagesDirectory {
            # Identifier "SoftwarePackagesDirectory" using the DSC resource "File" from DSC module "PSDesiredStateConfiguration"

            DestinationPath = "C:ProgramDataSoftwarePackages"
            Type = "Directory" # Is a directory
            Ensure = "Present" # Ensure that it exists

        }

                # Download MSI
        xRemoteFile DownloadBackupAgent {
            # The xRemoteFile resource from xPSDesiredStateConfiguration DSC module used to download an MSI through HTTP
    
            DependsOn = "[File]SoftwarePackagesDirectory" # Tells DSC to not run this if the previous step hasn't completed successfully
            Uri = "http://web.contoso.com/packages/backupagent.msi" # Where to download the remote file from
            DestinationPath = "C:ProgramDataSoftwarePackagesBackupAgent.msi" # Where to save the downloaded file
    
        }
                
                # Install MSI
        xMsiPackage InstallBackupAgent {
            # The xMsiPackage resource from xPSDesiredStateConfiguration DSC module used to install the MSI we just downloaded
            
            DependsOn = "[xRemoteFile]DownloadBackupAgent" # Depends on previous step being completed before starting
            ProductId = "d29c3fa9-e03e-40e0-a6ed-556c6f05476a" # The MSI package ProductID
            Path = "C:ProgramDataSoftwarePackagesBackupAgent.msi" # Path to the MSI file we just downloaded
            Ensure = "Present" # Ensure that it's present (install the MSI), 'Ensure = "Absent"' would uninstall the MSI

        }

    }
}

After running this code, it creates a Configuration Function in the current PowerShell session that can be used. It creates a subfolder with the same name as the configuration where it saves the generated MOF files:

        PS51> MyDSCConfiguration

    Directory: C:TutorialsDSCMyDSCConfiguration


Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2019-10-28 16:38 3952 fileserver-1.mof

You can apply all DSC configurations in the MyDSCConfiguration folder to their host by running:

        PS51> Start-DscConfiguration -Path .MyDSCConfiguration -Wait
    

Using DSC with ConfigurationData

Configuration Data contains configuration data for the DSC configuration. The information includes a node name (computer name), and can contain other data like information about how to install an MSI package or registry key values, etc. This gives us a dynamic and more versatile approach to DSC, so that it can be reused and easily modified.

Let's say that you have these three servers that each need a unique combination of packages:

  • fileserver-1
  • Needs: BackupAgent
  • database-1
  • Needs: BackupAgent, BackupAgent-DatabaseAddon
  • appserver-1
  • Needs: BackupAgent, MonitoringAgent

The best approach is to create a file with the configuration data that you need instead, not only specifying the nodes (servers), but also adding a list of packages into it:

        # Save as ConfigurationData.psd1

@{
        #############################
        # Specify server nodes
    #############################
    AllNodes = @(
@{
            NodeName = "*"
        }

        # File server node
@{
            NodeName = "fileserver-1"
            Role = "Fileserver"

            # Selects what packages to install
            Packages = @(
                'BackupAgent'
            )
        },

        # Database server node
@{
            NodeName = "database-1"
            Role = "Database"

            # Selects what packages to install
            Packages = @(
                'BackupAgent',
                'BackupAgent-DataBaseAddon'
            )
        },

        # Appserver node
@{
            NodeName = "appserver-1"
            Role = "Appserver"

            # Selects what packages to install
            Packages = @(
                'BackupAgent',
                'MonitoringAgent'
            )
        }
    )

        #############################
    # Define each package
        #############################
    Packages = @(
@{
            Name = "BackupAgent"
            ProductId = "3c4e21b5-8de2-408e-84e0-a42b0507b8b1"
            FileName = "BackupAgent.msi"
            Ensure = "Present"
        },
@{
            Name = "BackupAgent-DataBaseAddon"
            ProductId = "97f860f6-0a58-4bf3-aef8-abc0f796e9df"
            FileName = "BackupAgent-DataBaseAddon.msi"
            Ensure = "Present"
        },
@{
            Name = "MonitoringAgent"
            ProductId = "77b29b12-c222-4d85-b812-bbd4136ad0ff"
            FileName = "MonitoringAgent.msi"
            Ensure = "Present"
        }
    )
}

You can now use this Configuration Data file to dynamically generate DSC configurations for your servers:

        Configuration MyDSCConfiguration {
    # Import DSC modules with resources to use in the configuration
    Import-DscResource -ModuleName PSDesiredStateConfiguration
    Import-DscResource -ModuleName xPSDesiredStateConfiguration -ModuleVersion "8.10.0.0"

    # Get Packages from the ConfigurationData
    $Packages = $ConfigurationData.Packages

    ##############################################
    # Applies to all nodes
    ##############################################
    Node $AllNodes.NodeName {
        
        # Create C:ProgramDataSoftwarePackages to download files to on all nodes
        File SoftwarePackagesDirectory {
            DestinationPath = "C:ProgramDataSoftwarePackages"
            Type = "Directory" # Is a directory
            Ensure = "Present" # Ensure that it exists
        }

        # Start dynamically generating DSC configuration by running through all the package names in Node.Packages
        Foreach($PackageName in $Node.Packages){

            $Package = $Packages.Where{$_.Name -eq $PackageName}

            xRemoteFile "PackageDownload-$($Package.Name)" {
                DependsOn = "[File]SoftwarePackagesDirectory"
                DestinationPath = "C:SoftwarePackages$($Package.FileName)"
                Uri = "http://web.contso.com/packages/$($Package.FileName)"
            }

            xMsiPackage "Install$($Package.Name)" {
                DependsOn = "[xRemoteFile]PackageDownload-$($Package.Name)"
                ProductId = $Package.ProductId
                Path = "C:ProgramDataSoftwarePackages$($Package.FileName)"
                Ensure = $Package.Ensure
            }
        }
    }
}

If you run this, the DSC function generates a MOF file for each of your servers that can be used to install the packages you defined in the ConfigurationData.psd1 file:

        PS51> MyDSCConfiguration -ConfigurationData .ConfigurationData.psd1


    Directory: C:TutorialsDSCMyDSCConfiguration


Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2019-10-28 17:25 3962 fileserver-1.mof
-a---- 2019-10-28 17:25 6012 database-1.mof
-a---- 2019-10-28 17:25 5894 appserver-1.mof

# Applies the configuration to the servers
PS51> Start-DscConfiguration -Path .MyDSCConfiguration -Wait

If you want to add new packages, modify your ConfigurationData.psd1 file and run the previous code one more time.

Conclusion

It can be a pretty short and steep learning curve to get started with DSC. But the hardest part for many is to find use cases where they can use it.

Using the previous method that utilizes HTTP (or HTTPS) to fetch packages gives you a versatile way to distribute software to your servers. And because it's HTTPS, it's easier to expose as well, and you can use it to manage Software in DMZ and other less-trusted networks.

In addition, using configuration data instead of hard coding everything into your DSC configurations gives you a much more easily maintained approach. You can even generate this configuration data from external sources such as CSV, SQL, Sharepoint and everything else that can be read with PowerShell.

I hope that you got some ideas for your environment to start making your daily tasks easier with DSC.