How are people managing their configuration?

So as I add more and more services to be controlled by DSC, what I’m finding is my configuration is growing extremely quickly. In some cases a single service could take over 2,000 lines, which means as I add dozens of services in … breaking 10,000 plus lines is quickly becoming reality. To me, this is starting to become rather unwieldy for a number of reasons, so I started to explore if there were “other ways” to manage this. So far I’ve come down to 4 basic possibilities, but I’ve slo got reservations on each:

Use one large configuration file
As mentioned, once you start breaking 10,000+ lines of configuration, finding settings, changing settings, etc. becomes much more of a chore than I’d like. It also means if I like to use github to store revisions it becomes much MUCH harder to track what is changing as the entire table gets updated when any change is made (rather than having a separate project per major service). Basically I find myself “desiring” a more “NAGIOS-like” configuration where I can have multiple configurations in a directory and call which ever i want.

Use multiple small configurations
This one gets immediate appeal from me, as I can have a “standard configuration” for each major service (LAMP, SQL, SCOM, Exchange, whatever), and track what the “standard” will be in separate code repositories. Cool. It also has a downside: if for some reason I need to deploy a server that has two services on it (Its a SQL with IIS), running the configurationdata through two configurations will get me two MOF files… well that’s a problem. Boo.

Master Configuration that references other configurations
I started looking into ways I could either write a powershell function or see if some kind of reference information existed to “merge” configurations before I ran configuraitondata against them … something silly like you might do in an array ($array = $data-a, $data-b). No real luck in this department so far … but I may dig deeper, does this even exist?

Make a mof reference other MOFs
For some reason I have it stuck in my head that while the mof/node relationship is 1:1, I thought I read/heard in a lecture somewhere that a mof can reference another mof. If so this might be another options to explore … but I can’t find anything confirming if this is true or something completely from my head.

So as you can see … what I’m trying to do is find an elegant way to be able to break my ever growing configuration into multiple smaller files for manageability without breaking the 1:! mof to node relationship. How are other DSC gurus out there handling this right now? My short answer is I’m using multiple smaller configuration files and enforcing that servers have a strict “one role” deployment… but I’d like to retain a bit more flexibility if I could.

In PowerShell 5.0, one of the features being added to DSC is “Partial Configuration”; this allows a machine to process multiple MOF documents, which might be right up your alley.

For now, though, the best thing to do is probably to leverage composite configurations. These are written using basically the same syntax as any other configuration, but you can import and use them like DSC resources in your “master” configuration. Check out http://blogs.msdn.com/b/powershell/archive/2014/02/25/reusing-existing-configuration-scripts-in-powershell-desired-state-configuration.aspx to learn more about how to write and use composite configs.

What you’re seeing is a lack of management tooling from Microsoft for DSC. Hard to tell when they’ll start providing any, or if they’ll wait for a third party to do so.

Dave: I think this helps a ton! If this can be linked as easily as I think it does (at least in my head), I can work with this.

Don: I assumed as much but was trying to get a gauge on how others are doing it. Honestly I’m in the middle of writing my own collection of functions for my “tool users” to help with this very task, though it’s still in it’s infancy. I was debating if I could write a function that read and merged multiple configurations before realizing it would be a ton of work and wondering if there was a better way :slight_smile:

Actually my last major hurdle is password management … going to look for a powershell friendly encrypted password repository out there so I don’t need to use Get-Credentials and “stop the flow” of scripts, just have it “go get what it needs”.

You should check out the DSC tooling modules at GitHub - PowerShellOrg/DSC: DSC Tools and Documentation . :slight_smile: The DscConfiguration module already has password vault encryption functionality built-in (using a digital certificate to manage the encryption keys.) You add credentials to the vault with the Add-DscEncrypedPassword command, and then when you load up the configuration data with Get-DscConfigurationData, the PSCredential objects will be available in $ConfigurationData.Credentials (a hashtable using the username as its key.)

I think I equally love and hate you for making my job easier and robbing me of a little pet-project :slight_smile:

Justin, I have also been struggling to find good examples online of how others are approaching this. At the moment, I am using a combination of your second and third option.

I started with one script and i didn’t like the way it looked as i added more roles and settings. I want to show it off so others can see how easy it is.

I take any script resource that looks ugly and turn it into its own resource. Then I tend to create a composite resource for each major role. I tie it all together with a master config that uses a switch statement on the role to call the corresponding composite resources. Each node, role, and environment are separate files that get combined for processing.

I pulled a lot from Dave’s tools to create mine.

I have been using GitHub - PowerShellOrg/DSC: DSC Tools and Documentation on the current Development Branch (due to the cool features)

I currently have 3 configs. Test, Server, Workstation.

To the most part they are the same as far as the config file goes but use diferent DSC_Configuration folders

Here is an example of the configuration file.

Configuration ServerConfig
{
    $script:DependsOn = $null

    Import-DscResource -ModuleName  StackExchangeResources,
                                    xComputerManagement,
                                    xHyper-V,
                                    cSMBShare,
                                    PowerShellAccessControl,
                                    PDSResorces

    node $AllNodes.NodeName
    {
        Write-Warning  "Current Node:  $($node.name) GUID: $($node.NodeName)"
        
        #region ### User Conifguration ###
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName LocalUserSettings)
        {
            $Users = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName LocalUserSettings\User -MultipleResultBehavior AllValues
            )
            foreach ($User in $Users)
            {
                
                User $User['UserName']
                {
                    UserName                 = $User['UserName']
                    FullName                 = $User['FullName']
                    Description              = $User['Description']
                    Disabled                 = $User['Disabled']
                    Password                 = if ($User['CredName']) {$ConfigurationData.Credentials.($User['CredName'])} else {$null}
                    PasswordChangeNotAllowed = $User['PasswordChangeNotAllowed']
                    PasswordChangeRequired   = $User['PasswordChangeRequired']
                    PasswordNeverExpires     = $User['PasswordNeverExpires']
                    DependsOn                = $User['DependsOn']
                    Ensure                   = $User['Ensure'] 
                }
            } 
        }#endregion

        #region ### Group Conifguration ###
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName LocalGroupSettings)
        {
            $Groups = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName LocalGroupSettings\Group -MultipleResultBehavior AllValues
            )
            foreach ($Group in $Groups)
            {
                group $Group['GroupName']
                {
                    GroupName        = $Group['GroupName']
                    Description      = $Group['Description'] 
                    Members          = $Group['Members']
                    MembersToInclude = $Group['MembersToInclude']
                    MembersToExclude = $Group['MembersToExclude']
                    DependsOn        = $Group['DependsOn']
                    Credential       = if ($Group['CredName']) {$ConfigurationData.Credentials.($Group['CredName'])} else {$null}
                    Ensure           = $Group['Ensure']
                }
            }
        } #endregion
        
        #region ### Windows Features ###
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName WindowsFeatureSettings)
        {
            $Features = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName WindowsFeatureSettings\WindowsFeature -MultipleResultBehavior AllValues
            )
            foreach ($Feature in $Features)
            {
                    WindowsFeature $Feature['Name'] 
                    {
                        Name                 = $Feature['Name']
                        Credential           = if ($Feature['CredName']) {$ConfigurationData.Credentials.($Feature['CredName'])} else {$null}
                        DependsOn            = $Feature['DependsOn']
                        Ensure               = $Feature['Ensure']
                        IncludeAllSubFeature = $Feature['IncludeAllSubFeature']
                        LogPath              = $Feature['LogPath']
                        Source               = if($Feature['SourceRef']){
                                                    (Resolve-DscConfigurationProperty -Node $node -PropertyName $Feature['SourceRef'])
                                               } 
                                               elseif ($Feature['Source']){
                                                    ($Feature['Source'])} 
                                               else{$null}
                    }
            }
        } #endregion

        #region ### File ###
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName FileSettings)
        {
            $Files = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName FileSettings\File -MultipleResultBehavior AllValues
            )
            foreach ($File in $Files)
            {
                if ($File['Attributes'] -and $File['Checksum']) {
                    File  $File['Name'] 
                    {
                        DestinationPath = $File['DestinationPath'] 
                        Attributes      = $File['Attributes'] 
                        Checksum        = $File['Checksum'] 
                        Contents        = if($File['Contents']){(Resolve-DscConfigurationProperty -Node $node -PropertyName  $File['Contents'] )} else {$null }
                        Credential      = if ($File['CredName']) {$ConfigurationData.Credentials.($File['CredName'])} else {$null}
                        DependsOn       = $File['DependsOn'] 
                        Ensure          = $File['Ensure'] 
                        Force           = $File['Force'] 
                        MatchSource     = $File['MatchSource'] 
                        Recurse         = $File['Recurse'] 
                        SourcePath      = $File['SourcePath'] 
                        Type            = if ($File['Type']) {$File['Type'] } else {'file' }
                    }
                }
                elseif ($File['Checksum']) {
                    File  $File['Name'] 
                    {
                        DestinationPath = $File['DestinationPath'] 
                        #Attributes      = $File['Attributes'] 
                        Checksum        = $File['Checksum'] 
                        Contents        = if($File['Contents']){(Resolve-DscConfigurationProperty -Node $node -PropertyName  $File['Contents'] )} else {$null }
                        Credential      = if ($File['CredName']) {$ConfigurationData.Credentials.($File['CredName'])} else {$null}
                        DependsOn       = $File['DependsOn'] 
                        Ensure          = $File['Ensure'] 
                        Force           = $File['Force'] 
                        MatchSource     = $File['MatchSource'] 
                        Recurse         = $File['Recurse'] 
                        SourcePath      = $File['SourcePath'] 
                        Type            = if ($File['Type']) {$File['Type'] } else {'file' }
                    }
                }
                elseif ($File['Attributes']) {
                    File  $File['Name'] 
                    {
                        DestinationPath = $File['DestinationPath'] 
                        Attributes      = $File['Attributes'] 
                        #Checksum        = $File['Checksum'] 
                        Contents        = if($File['Contents']){(Resolve-DscConfigurationProperty -Node $node -PropertyName  $File['Contents'] )} else {$null }
                        Credential      = if ($File['CredName']) {$ConfigurationData.Credentials.($File['CredName'])} else {$null}
                        DependsOn       = $File['DependsOn'] 
                        Ensure          = $File['Ensure'] 
                        Force           = $File['Force'] 
                        MatchSource     = $File['MatchSource'] 
                        Recurse         = $File['Recurse'] 
                        SourcePath      = $File['SourcePath'] 
                        Type            = if ($File['Type']) {$File['Type'] } else {'file' }
                    }
                }
                else {
                    File  $File['Name'] 
                    {
                        DestinationPath = $File['DestinationPath'] 
                        #Attributes      = $File['Attributes'] 
                        #Checksum        = $File['Checksum'] 
                        Contents        = if($File['Contents']){(Resolve-DscConfigurationProperty -Node $node -PropertyName  $File['Contents'] )} else {$null }
                        Credential      = if ($File['CredName']) {$ConfigurationData.Credentials.($File['CredName'])} else {$null}
                        DependsOn       = $File['DependsOn'] 
                        Ensure          = $File['Ensure'] 
                        Force           = $File['Force'] 
                        MatchSource     = $File['MatchSource'] 
                        Recurse         = $File['Recurse'] 
                        SourcePath      = $File['SourcePath'] 
                        Type            = if ($File['Type']) {$File['Type'] } else {'file' }
                    }
                
                }

            }
        } #endregion
       
        #region ### Hyper-V Settings ###
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName HypervSettings\vmSwitch)
        {
            #region ### Hyper-v Virtual Switch ###
            $Switchs = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName HypervSettings\vmSwitch -MultipleResultBehavior AllValues
            )
            foreach ($Switch in $Switchs)
            {
                xVMSwitch $Switch['Name']
                {
                    Name              = $Switch['Name']
                    Type              = $Switch['Type']
                    AllowManagementOS = $Switch['AllowManagementOS']
                    DependsOn         = $Switch['DependsOn']
                    Ensure            = $Switch['Ensure']
                    NetAdapterName    = $Switch['NetAdapterName']
                }
            } #endregion
        }
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName HypervSettings\VHDX)
        {

            #region ### VHDX Creation ###
            $VHDs = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName HypervSettings\VHDX -MultipleResultBehavior AllValues
            )
            foreach ($VHD in $VHDs)
            {
                xVHD $VHD['Name']
                {
                    Name             = $VHD['Name']
                    Path             = $VHD['Path']
                    DependsOn        = $VHD['DependsOn']
                    Ensure           = $VHD['Ensure']
                    Generation       = $VHD['Generation']
                    MaximumSizeBytes = $VHD['MaximumSizeBytes']
                    ParentPath       = $VHD['ParentPath']
                }

            } #endregion
        }
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName HypervSettings\VirtualMachine)
        {
            #region ### VM Creation ###
            $VirtualMachine = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName HypervSettings\VirtualMachine -MultipleResultBehavior AllValues
            )
            foreach ($VM in $VirtualMachine)
            {
                xVMHyperV $VM['Name']
                {
                    Name            = $VM['Name']
                    VhdPath         = $VM['VhdPath']
                    DependsOn       = $VM['DependsOn']
                    Ensure          = $VM['Ensure']
                    Generation      = $VM['Generation']
                    MACAddress      = $VM['MACAddress']
                    MaximumMemory   = $VM['MaximumMemory']
                    MinimumMemory   = $VM['MinimumMemory']
                    Path            = $VM['Path']
                    ProcessorCount  = $VM['ProcessorCount']
                    RestartIfNeeded = $VM['RestartIfNeeded']
                    StartupMemory   = $VM['StartupMemory']
                    State           = $VM['State']
                    SwitchName      = $VM['SwitchName']
                    WaitForIP       = $VM['WaitForIP']
                }

            } #endregion
        }
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName HypervSettings\VMDvdDrive)
        {
            #region ### Attach ISO ###
            $VMDvdDrive = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName HypervSettings\VMDvdDrive -MultipleResultBehavior AllValues
            )
            foreach ($DVD in $VMDvdDrive)
            {
                cVMDvdDrive $DVD['Name']
                {
                    ISOPath   = $DVD['ISOPath']
                    VMName    = $DVD['VMName']
                    DependsOn = $DVD['DependsOn']
                    Ensure    = $DVD['Ensure']
                }

            } #endregion
        }
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName HypervSettings\VMHardDiskDrive)
        {
             #region ### Attach VHD ###
            $VMHardDiskDrive = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName HypervSettings\VMHardDiskDrive -MultipleResultBehavior AllValues
            )
            foreach ($VHD in $VMHardDiskDrive)
            {
                cVMHardDiskDrive $VHD['Name']
                {
                    VHDPath   = $VHD['VHDPath']
                    VMName    = $VHD['VMName']
                    DependsOn = $VHD['DependsOn']
                    Ensure    = $VHD['Ensure']
                }
 
            } #endregion
         } #endregion

        #region ### Install Packages ###
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName InstallPackages)
        {
            $Packages = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName InstallPackages\Package -MultipleResultBehavior AllValues
            )
            foreach ($Package in $Packages)
            {
                Package $Package['Name']
                {
                    Name       = $Package['Name']
                    Path       = $Package['Path']
                    ProductId  = $Package['ProductId']
                    Arguments  = $Package['Arguments']
                    Credential = if ($Package['CredName']) {$ConfigurationData.Credentials.($Package['CredName'])} else {$null}
                    DependsOn  = $Package['DependsOn']
                    Ensure     = $Package['Ensure']
                    LogPath    = $Package['LogPath']
                    ReturnCode = $Package['ReturnCode']
                }

            }
        } #endregion

        #region ### Access Control Entry (Permission) ###
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName AccessControlEntry)
        {
            $Aces = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName AccessControlEntry\Ace -MultipleResultBehavior AllValues
            )
            foreach ($Ace in $Aces)
            {
                if ($Ace['AceType'] -eq 'SystemAudit') {
                    cAccessControlEntry $ACE['Name']
                    {
                        AceType                  = $Ace['AceType']
                        ObjectType               = $Ace['ObjectType']
                        Path                     = $Ace['Path']
                        Principal                = $Ace['Principal']
                        AccessMask               = $Ace['AccessMask']
                        AppliesTo                = $Ace['AppliesTo']
                        AuditFailure             = $Ace['AuditFailure'] 
                        AuditSuccess             = $Ace['AuditSuccess']
                        DependsOn                = $Ace['DependsOn']
                        Ensure                   = $Ace['Ensure']
                        OnlyApplyToThisContainer = $Ace['OnlyApplyToThisContainer']
                    }
                }
                elseif ($Ace['Specific'])
                {
                    cAccessControlEntry $ACE['Name']
                    {
                        AceType                  = $Ace['AceType']
                        ObjectType               = $Ace['ObjectType']
                        Path                     = $Ace['Path']
                        Principal                = $Ace['Principal']
                        AccessMask               = $Ace['AccessMask']
                        AppliesTo                = $Ace['AppliesTo']
                        DependsOn                = $Ace['DependsOn']
                        Ensure                   = $Ace['Ensure']
                        OnlyApplyToThisContainer = $Ace['OnlyApplyToThisContainer']
                        Specific                 = $Ace['Specific']
                    }
                }
                else
                {
                     cAccessControlEntry $ACE['Name']
                    {
                        AceType                  = $Ace['AceType']
                        ObjectType               = $Ace['ObjectType']
                        Path                     = $Ace['Path']
                        Principal                = $Ace['Principal']
                        AccessMask               = $Ace['AccessMask']
                        AppliesTo                = $Ace['AppliesTo']
                        DependsOn                = $Ace['DependsOn']
                        Ensure                   = $Ace['Ensure']
                        OnlyApplyToThisContainer = $Ace['OnlyApplyToThisContainer']
                    }
                }
            }
        } #endregion

        #region ### SMB Share ###
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName SmbShareSettings)
        {
            $SmbShares = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName SmbShareSettings\SmbShare -MultipleResultBehavior AllValues
            )
            foreach ($SmbShare in $SmbShares)
            {
                if ($SmbShare['FolderEnumerationMode']) 
                {
                    cSmbShare $SmbShare['Name']
                    {
                        Name                  = $SmbShare['Name']
                        Path                  = $SmbShare['Path']
                        ChangeAccess          = $SmbShare['ChangeAccess']
                        ConcurrentUserLimit   = $SmbShare['ConcurrentUserLimit']
                        DependsOn             = $SmbShare['DependsOn']
                        Description           = $SmbShare['Description']
                        EncryptData           = $SmbShare['EncryptData']
                        Ensure                = $SmbShare['Ensure']
                        FolderEnumerationMode = $SmbShare['FolderEnumerationMode']
                        FullAccess            = $SmbShare['FullAccess']
                        NoAccess              = $SmbShare['NoAccess']
                        ReadAccess            = $SmbShare['ReadAccess']
                    }
                }
                else 
                {
                    cSmbShare $SmbShare['Name']
                    {
                        Name                  = $SmbShare['Name']
                        Path                  = $SmbShare['Path']
                        ChangeAccess          = $SmbShare['ChangeAccess']
                        ConcurrentUserLimit   = $SmbShare['ConcurrentUserLimit']
                        DependsOn             = $SmbShare['DependsOn']
                        Description           = $SmbShare['Description']
                        EncryptData           = $SmbShare['EncryptData']
                        Ensure                = $SmbShare['Ensure']
                        FullAccess            = $SmbShare['FullAccess']
                        NoAccess              = $SmbShare['NoAccess']
                        ReadAccess            = $SmbShare['ReadAccess']
                    }
                }
            }
        } #endregion
     
        #region ### Service Settings ###
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName ServiceSettings)
        {
            $Services = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName ServiceSettings\Service -MultipleResultBehavior AllValues
            )
            
            foreach ($Service in $Services)
            {
                if ($Service['BuiltInAccount']) {
                    Service $Service['Name']
                    {
                        Name           = $Service['Name']
                        BuiltInAccount = $Service['BuiltInAccount']
                        DependsOn      = $Service['DependsOn']
                        StartupType    = $Service['StartupType']
                        State          = $Service['State']
                    }
                }
                else {
                    Service $Service['Name']
                    {
                        Name           = $Service['Name']
                        Credential     = if ($Service['CredName']) {$ConfigurationData.Credentials.($Service['CredName'])} else {$null}
                        DependsOn      = $Service['DependsOn']
                        StartupType    = $Service['StartupType']
                        State          = $Service['State']
                    }
                }
            }
        } #endregion
 
        #region ### Registry Settings ###
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName RegistrySettings)
        {
            $RegistrySettings = @(
                Resolve-DscConfigurationProperty -Node $Node -PropertyName RegistrySettings\Registry -MultipleResultBehavior AllValues
            )
            
            foreach ($Registry in $RegistrySettings)
            {
                if ($Registry.ValueType)
                { 
                    Registry $Registry['Name']
                    {
                        Key       = $Registry['Key']
                        ValueName = $Registry['ValueName']
                        DependsOn = $Registry['DependsOn']
                        Ensure    = $Registry['Ensure']
                        Force     = $Registry['Force']
                        Hex       = $Registry['Hex']
                        ValueData = $Registry['ValueData']
                        ValueType = $Registry['ValueType']
                    }
                }
                else
                {
                    Registry $Registry['Name']
                    {
                        Key       = $Registry['Key']
                        ValueName = $Registry['ValueName']
                        DependsOn = $Registry['DependsOn']
                        Ensure    = $Registry['Ensure']
                        Force     = $Registry['Force']
                        Hex       = $Registry['Hex']
                        ValueData = $Registry['ValueData']
                    }
                }
                
            }
        } #endregion
  
        #region ### Time Zone ###
        if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName TimeZone)
        {
            Timezone TZone
            {
                Name = (Resolve-DscConfigurationProperty -Node $Node -PropertyName TimeZone)
            }
        }#endregion


        #region ### Phisical Servers ###
        if ($node.ServerType -eq 'Phsycal')
        {
            Package 'OMSA'
            {
                    Name       = 'OMSA'
                    Path       = '\\itfiles\DSC$\MSI\OMSA_74x64\SysMgmtx64.msi'
                    ProductId  = 'D21351E1-B98E-4F49-94C1-9E0BE31BA4A4'
                    DependsOn  = '[xComputer]NewName'
                    Ensure     = 'Present'
                    LogPath    = 'C:\Programdata\Logs\OMSA.log'
            }
        }#endregion

        #region ### All OnDomain Servers ###
        if (-not ($node.OffDomain))
        {
            xComputer NewName
            {
                Name = $Node.Name
                DomainName = 'domain.com'
                Credential = (New-Object System.Management.Automation.PSCredential -ArgumentList 'domain\DomainJoiner',$ConfigurationData.Credentials.DomainJoiner.Password)
            }
        } #endregion

        #region BranchHost File Copy Script
        If (Test-DscConfigurationPropertyExists -Node $Node -PropertyName BranchHostFileCopyScript)
        {
            script BranchServerBootDrive
            {
                SetScript = {
                    if (-not (Test-Path -Path 'E:\Hyper-V\Virtual Hard Disks\BranchServer_Boot.vhdx' -ErrorAction SilentlyContinue))
                    {
                        copy-item -Path 'E:\Hyper-V\Virtual Hard Disks\2012R2u1_Std.vhdx' -Destination 'E:\Hyper-V\Virtual Hard Disks\BranchServer_Boot.vhdx'
                    }
                }
                TestScript = {
                    Test-Path -Path 'E:\Hyper-V\Virtual Hard Disks\BranchServer_Boot.vhdx' -ErrorAction SilentlyContinue
                    }
                GetScript = { 
                    $Ensure = (Test-Path -Path 'E:\Hyper-V\Virtual Hard Disks\BranchServer_Boot.vhdx' -ErrorAction SilentlyContinue) 
                    $output = @{Ensure = $Ensure
                                Path = 'E:\Hyper-V\Virtual Hard Disks\BranchServer_Boot.vhdx'}
                    $output
                }
                DependsOn = '[file]VMImage'
            }
            cVhdFileInjection CopyBranchServerGUID 
            { 
                VhdPath =  'E:\Hyper-V\Virtual Hard Disks\BranchServer_Boot.vhdx'
                FileDirectory =  PDS_cFileDirectory { 
                                    SourcePath = 'E:\Hyper-V\BranchServerGUID'
                                    DestinationPath = 'DSCtemp\GUID' 
                                    } 
                DependsOn = '[File]BranchServerGUIDFile','[script]BranchServerBootDrive'
                # VolumeNumber = 1
            } 

        } #endregion

   
        LocalConfigurationManager
        {
            CertificateId = (Resolve-DscConfigurationProperty -Node $node -PropertyName 'CertificateID')
            AllowModuleOverwrite = (Resolve-DscConfigurationProperty -Node $node -PropertyName 'AllowModuleOverwrite')
            ConfigurationID = $Node.NodeName
            ConfigurationModeFrequencyMins = (Resolve-DscConfigurationProperty -Node $node -PropertyName 'ConfigurationModeFrequencyMins') 
            ConfigurationMode = (Resolve-DscConfigurationProperty -Node $node -PropertyName 'ConfigurationMode') 
            RebootNodeIfNeeded = (Resolve-DscConfigurationProperty -Node $node -PropertyName 'RebootNodeIfNeeded') 
            RefreshMode = (Resolve-DscConfigurationProperty -Node $node -PropertyName 'RefreshMode') 
            DownloadManagerName = (Resolve-DscConfigurationProperty -Node $node -PropertyName 'DownloadManagerName') 
            DownloadManagerCustomData = (Resolve-DscConfigurationProperty -Node $node -PropertyName 'DownloadManagerCustomData') 
        }
    }
}

In the AllNodes under DSC_Configuration
AllNodes.psd1

@{
    NodeName = '*'
    PSDscAllowPlainTextPssword=$false
    CertificateID = '0F7E02A2AD4D7D6808F70F33FC09EF6D52F1BA55'
    AllowModuleOverwrite = 'True'
    ConfigurationModeFrequencyMins = 30 
    ConfigurationMode = 'ApplyAndAutoCorrect'
    RebootNodeIfNeeded = 'True'
    RefreshMode = 'PULL' 
    DownloadManagerName = 'WebDownloadManager'
    DownloadManagerCustomData = (@{ServerUrl = 'https://DSC-Pull.domain.com:8443/psdscpullserver.svc'})
    WIMSource = 'wim:\\itfiles\DSC$\WIM\install.wim:4'
}

Hyper-vServerBranch1.psd1

@{
Name = 'BranchServer1-HV'
Location = 'Branch1'
MachineType = 'Phisical'
NodeName = 'a5583c86-bddf-4c22-82a6-e1a50b2c519c'
BranchServerGUID = 'e754ea9d-b01d-4489-a612-6265c6b466dc'
}

BranchServer1.psd1

@{
    Name = 'BranchServer1'
    Location = 'Branch1
    MachineType = 'VM'
    NodeName = 'e754ea9d-b01d-4489-a612-6265c6b466dc'
    AccessControlEntry = @{
        Ace = @(
            @{
                Name = 'AllBranchUsersModifyShares'
                AceType = 'AccessAllowed'
                ObjectType = 'Directory'
                Path = 'E:\Shares'
                Principal = 'Domain\All-Branch1Users'
                AccessMask = [System.Security.AccessControl.FileSystemRights] 'Modify'
                DependsOn = '[File]AppFolder', '[xComputer]NewName' 
                Ensure = 'Present'
            }
        )
    }
}

in the DSC_Configuration\Services folder
BranchHypserVHost.psd1

@{
    #region Node List
    Nodes = 'Branch1-HV' 
    
    #endregion        

    BranchHostFileCopyScript = $true

    InstallPackages = @{
        Package = @(
            @{
                Name      = 'OMSA'
                Path      = "\\itfiles\DSC$\MSI\OMSA_74x64\SysMgmtx64.msi"
                ProductId = 'D21351E1-B98E-4F49-94C1-9E0BE31BA4A4'
                DependsOn = '[xComputer]NewName' 
                Ensure    = 'Present'
            }
        )
    }
    
    
    WindowsFeatureSettings = @{
        WindowsFeature = @(
            @{
                Ensure = 'Present' 
                Name   = 'Hyper-V'
            }
        )
    }
    FileSettings = @{
        File = @(
            @{
                Name            = 'winISO'
                Ensure          = 'Present'
                SourcePath      = '\\itfiles\DSC$\ISO\Win_Svr_2012_R2_64Bit_English.ISO'
                DestinationPath = 'E:\ISO\Win_Svr_2012_R2_64Bit_English.ISO'
                Checksum        = 'SHA-256'
                Force           = $true
                DependsOn         = '[xComputer]NewName' 
                Type            = 'file'
            }
            @{
                Name            = 'VMImage'
                Ensure          = 'Present'
                SourcePath      = '\\itfiles\DSC$\VHD\2012R2u1_Std.vhdx'
                DestinationPath = 'E:\Hyper-V\Virtual Hard Disks\2012R2u1_Std.vhdx'
                Checksum        = 'SHA-256'
                Force           = $true
                DependsOn         = '[xComputer]NewName' 
                Type            = 'file'
            }
            @{
                Name            = 'BranchServerGUIDFile'
                Ensure          = 'Present'
                DestinationPath = 'E:\Hyper-V\BranchServerGUID'
                Contents        = 'BranchServerGUID'
                DependsOn         = '[xComputer]NewName' 
                Type            = 'file'
            }
        )
    }
        
    HypervSettings = @{
        vmSwitch = @(
            @{
                Ensure            = 'present'
                Name              = 'VM'
                Type              = 'External'
                NetAdapterName    = 'Ethernet 2'
                AllowManagementOS = $true
                DependsOn         = '[WindowsFeature]Hyper-V' 

            }
        )
        VirtualMachine = @(
            @{
                Ensure          = 'Present'
                Name            = 'BranchServer'
                SwitchName      = 'VM'
                vhdPath         = 'E:\Hyper-V\Virtual Hard Disks\BranchServer_Boot.vhdx'
                State           = 'Running'
                Path            = 'E:\Hyper-V'
                Generation      = 'VHDX'
                StartupMemory   = 1GB
                MaximumMemory   = 1GB
                MinimumMemory   = 1GB
                ProcessorCount  = 2
                RestartIfNeeded = $true
                DependsOn       = '[script]BranchServerBootDrive', '[xVMSwitch]VM', '[WindowsFeature]Hyper-V', '[cVhdFileInjection]CopyBranchServerGUID'
            }
        )

        VHDX = @(
            @{
                Ensure           = 'Present'
                Name             = 'BranchServer_Data.vhdx'
                Path             = 'E:\Hyper-V\Virtual Hard Disks'
                MaximumSizeBytes = 127GB
                Generation       = 'VHDX'
            }
        )

        VMHardDiskDrive = @(
            @{
                Name      = 'BranchServerDataDrive'
                Ensure    = 'Present'
                VMName    = 'BranchServer'
                VHDPath   = 'E:\Hyper-V\Virtual Hard Disks\BranchServer_Data.vhdx'
                DependsOn = '[xVMHyperV]BranchServer'
            }
        )
        VMDvdDrive = @(
            @{
                Name      = 'BranchServerWinDVD'
                Ensure    = 'Present'
                VMName    = 'BranchServer'
                ISOPath   = 'E:\ISO\Win_Svr_2012_R2_64Bit_English.ISO'
                DependsOn = '[xVMHyperV]BranchServer'
            }
        )

    }
}

BranchServerVM.psd1

@{
#region Node List
Nodes = 'BranchServer1'
        
#endregion


     WindowsFeatureSettings = @{
        WindowsFeature = @(
            @{
                Ensure = 'Present' 
                Name   = 'Server-Gui-Shell'
                DependsOn = '[xComputer]NewName'
                SourceRef = 'WIMSource'
            }
            @{
                Ensure = 'Present' 
                Name   = 'Server-Gui-Mgmt-Infra'
                DependsOn = '[xComputer]NewName' 
                SourceRef = 'WIMSource'
            }
            @{
                Ensure = 'Present' 
                Name   = 'File-Services'
            }
            @{
                Ensure = 'Present' 
                Name   = 'FS-Data-Deduplication'
            }
            @{
                Ensure = 'Present' 
                Name   = 'FS-DFS-Namespace'
            }
            @{
                Ensure = 'Present' 
                Name   = 'FS-DFS-Replication'
            }
            @{
                Ensure = 'Present' 
                Name   = 'FS-Resource-Manager'
            }
            @{
                Ensure = 'Present' 
                Name   = 'Print-Server'
            }
        )
    }
    SmbShareSettings = @{
        SmbShare = @(
            @{
                Name        = 'Apps'
                Path        = 'E:\Shares\Apps' 
                Description = 'Branch Application Share'
                Ensure      = 'Present'
                DependsOn   = '[File]AppFolder'
                FullAccess  = 'Everyone'
            }
            @{
                Name        = 'User$'
                Path        = 'E:\Shares\User' 
                Description = 'Branch User Share'
                Ensure      = 'Present'
                DependsOn   = '[File]UserFolder'
                FullAccess  = 'Everyone'
            }
            @{
                Name        = 'Share'
                Path        = 'E:\Shares\Share' 
                Description = 'Branch Group Share'
                Ensure      = 'Present'
                DependsOn   = '[File]ShareFolder'
                FullAccess  = 'Everyone'
            }
        )
    }

    FileSettings = @{
        File = @(
            @{
                Name            = 'AppFolder'
                Ensure          = 'Present'
                DestinationPath = 'E:\Shares\Apps'
                Type            = 'Directory'
            }
            @{
                Name            = 'UserFolder'
                Ensure          = 'Present'
                DestinationPath = 'E:\Shares\User'
                Type            = 'Directory'
            }
            @{
                Name            = 'ShareFolder'
                Ensure          = 'Present'
                DestinationPath = 'E:\Shares\Share'
                Type            = 'Directory'
            }
        )
    }
    
}

The VM image is a Core2012R1 install that has been sysprep with some files in the C:\DSCTemp that are run automatically to install the password decryption certificate key (sysprep removes all cert keys) look for the GUID file and start DSC for the VM.
I have something similar in an ESXi template for the VMWare hosts so we add the GUID when deploying the template and it builds the system from scratch.

Using "if (Test-DscConfigurationPropertyExists -Node $Node -PropertyName … " has some advantages but when it does limit more advanced logic. I have to work around the special cases as is obvious with the OMSA install I’m doing on only Physical hosts.

We are still in our infancy and don’t deploy the data into the file shares for replacement servers yet. but this is an example of what can be done.

Interesting breakdown, David. We might actually compare notes at some point. I use a different model for the configurationdata encryption though. Rather than a “Set” certificate with private key that needs to be installed on every box, I’m leveraging the default workstation key that gets auto-enrolled in my environment via our PKI infrastructure.

Basically I wrote a custom module/resource that queries which certificate in the local cert store has that latest expiration-date and still has a private key, then compares that to a “certstore” directory on a file share. If the target public cert doesn’t exist or the thumbprint doesn’t match, it exports the public key and overwrites the cert in that store.

On the backend I wrote a “update-ConfigurationData” function that takes in the configurationdata, scans the certificate store for matching certificates with a simple foreach loop (it matches the node name to the filename) and adds the variable into the hashtable if a matching certificate is found (I do similar for the GUID … I have a csv file that keeps all the GUID/Node associations that it uses to rename the Node variable).

Basically it means we run two functions instead of one… one grabs the hashtable then updates it with current GUID/Certificate info … then we run the expected Configuration against said table.

Pet projects for me are(were):

  1. Find a nice way to store passwords (looks like the DSCTools will help there)
  2. See if I can’t write another resource for the Pull Server so it can monitor changes in the certificate store directory and possibly “kick-off” a recompile of a new MOF/checksum when change is detected. In my head this is mapped out already … just have to test it.

We have a PKI infrastructure but I did not want to be tied to having to touch the machine at all to join it to the domain and kick off DSC. I know there are other tools to help there but we are not that advanced in our departments evolution. So I designed around DSC joining the domain and that requires an existing certificate.

What I want is a way to separate the computer names from the domain joining. I probably will have to build yet another resource.

Ill see if I can’t post my little modules to Github then. We actually have separated computer names from the whole process borrowing a technique I read on an MS blog. Basically we maintain a csv file (via functions) that generates a GUID for every node sense DSC pull servers relate that way anyway. By storing this in another file, it becomes easy to manipulate the node name independant of the GUID reference and still keep track of it… heck I can technically even make the node name, domain join status, etc just another variable controlled by a DSC resource.

You’re right though … this is now getting knee deep in the “how do we manage this” world. Someone needs to release a toolkit asap and make a killing :slight_smile:

When some one does it’ will cost an arm and a leg for the per-node license and all our current work on DSC will have to be re-engineered to work with it. (not to mention it will probably be mouse driven)

Next, Next, Finish. :wink:

This isn’t actually too complex of a problem; you just need to figure out how the workflow looks. Here’s what I’m reading so far:

[ul]
[li]Your hosts already have their own certificates that are suitable for password encryption, assigned in some way when they’re provisioned. [/li]
[li]It’s easy to have your MOF file use these certificates when it’s compiled, provided that the certificate is available on the machine where the MOF is compiled, and you’ve updated your DSC configuration data to point that particular node to the correct certificate file. [/li]
[/ul]

What’s missing is getting that certificate from your VMs over ot the DSC build server. You’d either need to pull it back after the server is online and accessible, or tie this to your VM provisioning process in some way. For example:

[ol]
[li]VM provisioning tool sets up a new instance. Certificate is automatically created. VM provisioning tool somehow pulls the .cer file for that cert and sends it over to a file share. New ConfigurationID guid is likely assigned to the LCM at this point as well, along with configuration to talk to the DSC pull server. [/li]
[li]ConfigurationData update is triggered which adds our new node (along with its newly-generated ConfigurationID guid, and path to its .cer file) to the ConfigurationData store. [/li]
[li]This triggers a MOF compliation and publish to the DSC pull server. [/li]
[/ol]

The details will depend on how you’re provisioning your VMs, and how you can pull back information about the certificate and the LCM configuration ID. I suspect there won’t be a one-size-fits-all solution here; it’ll depend on your cloud provider or Hypervisor technology, etc.

Provisioning new vms on vmware courrently goes something like : Next, Next, Next, Finish

on Hper-v DSC builds the vm from a syspreped image and injects the GUID into the file system.