How are people managing their configuration?

This topic contains 13 replies, has 5 voices, and was last updated by Profile photo of David Jones David Jones 2 years ago.

  • Author
    Posts
  • #20242
    Profile photo of Justin King
    Justin King
    Participant

    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:

    [b]Use one large configuration file[/b]
    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.

    [b]Use multiple small configurations[/b]
    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.

    [b]Master Configuration that references other configurations[/b]
    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?

    [b]Make a mof reference other MOFs[/b]
    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.

  • #20243
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    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.

  • #20249
    Profile photo of Don Jones
    Don Jones
    Keymaster

    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.

  • #20256
    Profile photo of Justin King
    Justin King
    Participant

    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 🙂

    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".

  • #20263
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    You should check out the DSC tooling modules at https://github.com/PowerShellOrg/DSC . 🙂 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.)

  • #20278
    Profile photo of Justin King
    Justin King
    Participant

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

  • #20294

    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.

  • #20397
    Profile photo of David Jones
    David Jones
    Participant

    I have been using https://github.com/PowerShellOrg/DSC 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.

  • #20411
    Profile photo of Justin King
    Justin King
    Participant

    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.

  • #20448
    Profile photo of David Jones
    David Jones
    Participant

    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.

  • #20453
    Profile photo of Justin King
    Justin King
    Participant

    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 🙂

  • #20487
    Profile photo of David Jones
    David Jones
    Participant

    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)

  • #20488
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Next, Next, Finish. 😉

    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.

  • #20705
    Profile photo of David Jones
    David Jones
    Participant

    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.

You must be logged in to reply to this topic.