First off if you are not already familiar with DSC please take a look at Windows PowerShell Desired State Configuration Overview. PowerShell Desired State Configuration or DSC for short can be used to either setup or keep a specified configuration
Add-WindowsFeature Dsc-Service
Defining $ConfigurationData
I started my Script off by defining $ConfigurationData [Shown Below]. This is where you can specify the computer names you want the pull server to create .MOFs for. Everything listed after NodeName will automatically installed on the given server and will be constantly monitored at any specified interval Fifteen minutes or above. So by just adding ,"Server-Gui-Shell" to the inside of any NodeName bracket you can install server graphical shell. You could also prevent server graphical shell and ensure it doesn't get installed for more than Fifteen minutes, but that come later.001 002 003 004 005 006 007 008 009 010 011 012 013 014 | $ConfigurationData = @{ AllNodes = @( @{NodeName = "MKDC-02";Role="AD-Domain-Services","DHCP","DNS","Server-Gui-Shell","Server-Gui-Mgmt-Infra"}, @{NodeName = "SERVER2012R2-01";Role="Server-Gui-Shell","Server-Gui-Mgmt-Infra";Service="BITS","vmms"}, @{NodeName = "SERVER2012R2-02";Role="Server-Gui-Shell","Server-Gui-Mgmt-Infra";Service="BITS","vmms"}, @{NodeName = "SERVER2012R2-03";Role="Server-Gui-Shell","Server-Gui-Mgmt-Infra";Service="BITS","vmms"}, @{NodeName = "PSDSCPULLSERVER";Role="DSC-Service","Server-Gui-Shell","Server-Gui-Mgmt-Infra";Service="BITS","vmms"}, @{NodeName = "MKDFS-01";Role="Web"}, @{NodeName = "MKDFS-02";Role="FileShare"}, @{NodeName = "RODC";Role="Web","AD-Domain-Services"}, @{NodeName = "MKPSWA";Role="FileShare","Server-Gui-Shell","Server-Gui-Mgmt-Infra","WindowsPowerShellWebAccess"} @{NodeName = "PSWAW12";Role="FileShare","Server-Gui-Shell","Server-Gui-Mgmt-Infra","WindowsPowerShellWebAccess"} ) } |
Defining LCM
Next I defined my Local Configuration Manager, which is more or less the brains of the operation by telling all client computers where to retrieve their .MOF configuration files from. This is also where you specify the intervals and setting that are needed while configuring a PS DSC Server. I created a pull server which basically means I'll have a Server 2012R2 or maybe a Windows 8.1 box hosting the .MOF configuration files for all the servers to go reach out to. This will happen at the time specified in the ConfigurationModeFrequencyMins variable.001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 | Configuration Copy_PowerShell_Scripts { Node $AllNodes.NodeName { file "PowerShell_PROFILE_script" { Ensure = "Present" type = "File" SourcePath = "\\PSDSCPULLSERVER\DSC Configurations\PowerShellScripts\profile.ps1" MatchSource = "$True" DestinationPath = "$PROFILE" } file "Copy_\\PSDSCPULLSERVER\DSC_Configurations\PowerShellScripts__TO__$env:USERPROFILE\Desktop\PowerShell" { Ensure = "Present" type = "Directory" SourcePath = "\\PSDSCPULLSERVER\DSC Configurations\PowerShellScripts" MatchSource = "$True" Recurse = "$True" DestinationPath = "$env:USERPROFILE\Desktop\PowerShell" } } } |
Defining Configuration 1 of 3
Next is to define all Configurations. I've included 3 configurations within my script. The first Configuration is used to ensure my saved PowerShell Profile script is copied to $PROFILE on all servers and that my saved PowerShell Scripts directory(including contents) are copied to all servers as well.001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 | Configuration Copy_PowerShell_Scripts { Node $AllNodes.NodeName { file "PowerShell_PROFILE_script" { Ensure = "Present" type = "File" SourcePath = "\\PSDSCPULLSERVER\DSC Configurations\PowerShellScripts\profile.ps1" MatchSource = "$True" DestinationPath = "$PROFILE" } file "Copy_\\PSDSCPULLSERVER\DSC_Configurations\PowerShellScripts__TO__$env:USERPROFILE\Desktop\PowerShell" { Ensure = "Present" type = "Directory" SourcePath = "\\PSDSCPULLSERVER\DSC Configurations\PowerShellScripts" MatchSource = "$True" Recurse = "$True" DestinationPath = "$env:USERPROFILE\Desktop\PowerShell" } } } |
Defining Configuration 2 of 3
In the Configuration below I set the following services to automatically start a boot [wuauserv, WinRm, EventLog, and VSS] Not only will those services bet set to start at boot -- they will also be check at the interval you configure in you LCM001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 | Configuration Configure_Services { Node $AllNodes.NodeName { Service wuauserv { Name = "wuauserv" State = "Running" StartupType = "Automatic" } Service WinRM { Name = "WinRM" State = "Running" StartupType = "Automatic" } Service EventLog { Name = "EventLog" State = "Running" StartupType = "Automatic" } Service winmgmt { Name = "winmgmt" State = "Running" StartupType = "Automatic" } Service VSS { Name = "VSS" State = "Running" StartupType = "Automatic" } switch ($Node.Service) { "BITS" { Service BITS { Name = "BITS" State = "Running" StartupType = "Automatic" } } "IISADMIN" { Service IISADMIN { Name = "IISADMIN" State = "Running" StartupType = "Automatic" } } "vmms" { Service vmms { Name = "vmms" State = "Running" StartupType = "Automatic" } } "msexchange" { Service msexchange { Name = "msexchange" State = "Running" StartupType = "Automatic" } } "cryptsvc" { Service crypysvc { Name = "cryptsvc" State = "Running" StartupType = "Automatic" } } } } } |
Defining Configuration 3 of 3
In this Configuration called "Add_Features" I start off by ensuring the following features (FilSharing, PowerShell and PowerShell_ISE) are installed on all servers specified under $AllNodes.NodeName which will is all servers listed in my $ConfigurationData array. However for the rest of the WindowsFeatures will not be installed unless they are called out within $ConfigurationData as shown with my Domain Controller MKDC-02 below.
001 | @{NodeName = "MKDC-02";Role="AD-Domain-Services","DHCP","DNS","Server-Gui-Shell","Server-Gui-Mgmt-Infra"}, |
So MKDC-02 will be configured with the following features (FS-FileServer, PowerShell, PowerShell-ISE AND AD-Domain-Services, DHCP, DNS,Server-Gui-Shell, and Server-Gui-Mgmt-Infra)
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 | Configuration Add_Features { Node $AllNodes.NodeName { WindowsFeature FS-FileServer { Name = "FS-FileServer" Ensure = "Present" } WindowsFeature PowerShell { Name = "PowerShell" Ensure = "Present" } WindowsFeature PowerShell-ISE { Name = "PowerShell-ISE" Ensure = "Present" #Source = "wim:d:\sources\install.wim:2" } switch ($Node.Role) { "SNMP" { WindowsFeature snmp { Name = "SNMP-Service" Ensure = "Present" } } "Web-Server" { WindowsFeature Web-Server { Name = "Web-Server" Ensure = "Present" } } "DHCP" { WindowsFeature DHCP { Name = "DHCP" Ensure = "Present" } } "DNS" { WindowsFeature DNS { Name = "DNS" Ensure = "Present" } } "Server-Gui-Shell" { WindowsFeature Server-Gui-Shell { Name = "Server-Gui-Shell" Ensure = "Present" } } "Server-Gui-Mgmt-Infra" { WindowsFeature Server-Gui-Mgmt-Infra { Name = "Server-Gui-Mgmt-Infra" Ensure = "Present" #Source = "wim:d\sources\install.wim:2" } } "AD-Domain-Services" { WindowsFeature AD-Domain-Services { Name = "AD-Domain-Services" Ensure = "Present" } } "DSC-Service" { WindowsFeature DSC-Service { Name = "DSC-Service" Ensure = "Present" } } } } } |
Creating the .MOFs
The next step in my script was to move to the folder that IIS is hosting as this is where the .MOFs need to be created.
Set-Location "C:\Program Files\WindowsPowerShell\DscService"
NOTE: instead of moving to the directory you can specify the -OutputPath Parameter.
The following creates the .MOF file for the Local Configuration Manager
DSC_LCM -ConfigurationData $ConfigurationData -OutputPath .\DSC_LCM
The below Creates the .MOF file for the corresponding "Configuration"
001 002 003 004 005 006 007 008 | ## Creates the .MOF file for the Local Configuration Manager DSC_LCM -ConfigurationData $ConfigurationData -OutputPath .\DSC_LCM ## Creates the .MOF file for the corresonding "Configuration" Add_Features -OutputPath .\Add_Features -ConfigurationData $ConfigurationData Configure_Services -OutputPath .\Configure_Services -ConfigurationData $ConfigurationData Copy_PowerShell_Scripts -OutputPath .\Copy_PowerShell_Scripts -ConfigurationData $ConfigurationData |
"Make it so"
001 002 003 004 005 | Set-DscLocalConfigurationManager -Path .\DSC_LCM -ComputerName $AllNodes -ErrorAction SilentlyContinue ## runs the desired .MOF configuration file Start-DscConfiguration -Path .\Copy_PowerShell_Scripts -ComputerName $AllNodes -ErrorAction SilentlyContinue Start-DscConfiguration -Path .\Add_Features -ComputerName $AllNode -ErrorAction SilentlyContinue Start-DscConfiguration -Path .\Configure_Services -ComputerName $AllNodes -ErrorAction SilentlyContinue |
My thoughts on DSC -- Setup can be a little time consuming but now I can boot up any number of servers and have them configure with any number of roles or features installed with very little modification.
Good-Bye Configuration Drift -- Good-Bye failed backups
What it looks like when it's running:
Here's my full Pull Server Configuration Script
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | <# .CREATED BY: Matthew A. Kerfoot .CREATED ON: 07\14\2014 .UPDATED ON: 07/22/2014 .SYNOPSIS Automating the world one line of code at a time. .DESCRIPTION This is a PowerShell Desired State Push Configuration. .EXAMPLE Run the script as an administrator PS:\>G\DSC\DSC.ps1 .NOTES This is a PowerShell Desired State Push Configuration. .NOTES Version : 4.0 Author/Copyright : © Matthew Kerfoot - All Rights Reserved Email/Blog/Twitter : mkkerfoot@gmail.com www.matthewkerfoot.com @mkkerfoot Disclaimer : THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. While these scripts are tested and working in my environment, it is recommended that you test these scripts in a test environment before using in your production environment Matthew Kerfoot further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of this script and documentation remains with you. In no event shall Matthew Kerfoot, its authors, or anyone else involved in the creation, production, or delivery of this script/tool be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Matthew Kerfoot has been advised of the possibility of such damages Assumptions : ExecutionPolicy of AllSigned (recommended), RemoteSigned or Unrestricted (not recommended) Limitations : This is a Push Configuration which is limited compared to a pull configuration. Ideas/Wish list : backupexec ; Veeam Known issues : Some Paths have been hardcoded Authors notes : This is my first attempt at DSC ##> ################################################################################################### ## # # # # # Variables that need to be edited # # # # # # ## ################################################################################################### $ConfigurationData = @{ AllNodes = @( @{NodeName = "MKDC-02";Role="DHCP","DNS","Server-Gui-Shell","Server-Gui-Mgmt-Infra"}, @{NodeName = "SERVER2012R2-01";Role="Server-Gui-Shell","Server-Gui-Mgmt-Infra";Service="BITS","vmms"}, @{NodeName = "SERVER2012R2-02";Role="Server-Gui-Shell","Server-Gui-Mgmt-Infra";Service="BITS","vmms"}, @{NodeName = "SERVER2012R2-03";Role="Server-Gui-Shell","Server-Gui-Mgmt-Infra";Service="BITS","vmms"}, @{NodeName = "PSDSCPULLSERVER";Role="DSC-Service","Server-Gui-Shell","Server-Gui-Mgmt-Infra";Service="BITS","vmms"}, @{NodeName = "MKDFS-01";Role="Web"}, @{NodeName = "MKDFS-02";Role="FileShare"}, @{NodeName = "RODC";Role="Web"}, @{NodeName = "MKPSWA";Role="FileShare","Server-Gui-Shell","Server-Gui-Mgmt-Infra","WindowsPowerShellWebAccess"} @{NodeName = "PSWAW12";Role="FileShare","Server-Gui-Shell","Server-Gui-Mgmt-Infra","WindowsPowerShellWebAccess"} ) } $StartTime = (Get-Date) ## Begin the timer Clear-Host ## Clears the screen ## Creates Logfile of all messages, verbose and host Start-Transcript -Path "C:\Windows\Temp\DSC_$( get-date -f MM-dd-hh-mm ).log" ## moves to the newly created directory Set-Location "C:\Program Files\WindowsPowerShell\DscService" ## Local Configuration Manager -- Sets all DSC intervals and settings Configuration DSC_LCM { Node $AllNodes.NodeName { LocalConfigurationManager { AllowModuleOverwrite = "$True" ConfigurationID = "a9c09333-54f7-4a25-8a58-ebee9647e313"; DownloadManagerName = "WebDownloadManager" DownloadManagerCustomData = @{ ServerUrl="http://PSDSCPULLSERVER.kerfoot.com:8080/PSDSCPullServer.svc" ; AllowUnsecureConnection= "True" } RefreshMode = "PULL" ConfigurationMode = "ApplyAndAutoCorrect" RebootNodeIfNeeded = "$True" RefreshFrequencyMins = "15" ConfigurationModeFrequencyMins = "30" } } } ## grabs the profile.ps1 file from $D_DSC and adds it to the logged in users PS $PROFILE Configuration Copy_PowerShell_Scripts { Node $AllNodes.NodeName { file "PowerShell_PROFILE_script" { Ensure = "Present" type = "File" SourcePath = "\\PSDSCPULLSERVER\DSC Configurations\PowerShellScripts\profile.ps1" MatchSource = "$True" DestinationPath = "$PROFILE" } file "Copy_\\PSDSCPULLSERVER\DSC_Configurations\PowerShellScripts__TO__$env:USERPROFILE\Desktop\PowerShell" { Ensure = "Present" type = "Directory" SourcePath = "\\PSDSCPULLSERVER\DSC Configurations\PowerShellScripts" MatchSource = "$True" Recurse = "$True" DestinationPath = "$env:USERPROFILE\Desktop\PowerShell" } } } Configuration Configure_Services { Node $AllNodes.NodeName { Service wuauserv { Name = "wuauserv" State = "Running" StartupType = "Automatic" } Service WinRM { Name = "WinRM" State = "Running" StartupType = "Automatic" } Service EventLog { Name = "EventLog" State = "Running" StartupType = "Automatic" } Service winmgmt { Name = "winmgmt" State = "Running" StartupType = "Automatic" } Service VSS { Name = "VSS" State = "Running" StartupType = "Automatic" } switch ($Node.Service) { "BITS" { Service BITS { Name = "BITS" State = "Running" StartupType = "Automatic" } } "IISADMIN" { Service IISADMIN { Name = "IISADMIN" State = "Running" StartupType = "Automatic" } } "vmms" { Service vmms { Name = "vmms" State = "Running" StartupType = "Automatic" } } "msexchange" { Service msexchange { Name = "msexchange" State = "Running" StartupType = "Automatic" } } "cryptsvc" { Service crypysvc { Name = "cryptsvc" State = "Running" StartupType = "Automatic" } } } } } Configuration Add_Features { Node $AllNodes.NodeName { WindowsFeature FileSharing { Name = "FS-FileServer" Ensure = "Present" } WindowsFeature PowerShell { Name = "PowerShell" Ensure = "Present" } WindowsFeature PowerShell-ISE { Name = "PowerShell-ISE" Ensure = "Present" #Source = "wim:d:\sources\install.wim:2" } switch ($Node.Role) { "SNMP" { WindowsFeature snmp { Name = "SNMP-Service" Ensure = "Present" } } "Web-Server" { WindowsFeature Web-Server { Name = "Web-Server" Ensure = "Present" } } "DHCP" { WindowsFeature DHCP { Name = "DHCP" Ensure = "Present" } } "DNS" { WindowsFeature DNS { Name = "DNS" Ensure = "Present" } } "Server-Gui-Shell" { WindowsFeature Server-Gui-Shell { Name = "Server-Gui-Shell" Ensure = "Present" } } "Server-Gui-Mgmt-Infra" { WindowsFeature Server-Gui-Mgmt-Infra { Name = "Server-Gui-Mgmt-Infra" Ensure = "Present" #Source = "wim:d\sources\install.wim:2" } } "AD-Domain-Services" { WindowsFeature AD-Domain-Services { Name = "AD-Domain-Services" Ensure = "Present" } } "DSC-Service" { WindowsFeature DSC-Service { Name = "DSC-Service" Ensure = "Present" } } } } } ## Creates the .MOF file for the Local Configuration Manager DSC_LCM -ConfigurationData $ConfigurationData -OutputPath .\DSC_LCM ## Creates the .MOF file for the corresonding "Configuration" Add_Features -OutputPath .\Add_Features -ConfigurationData $ConfigurationData Configure_Services -OutputPath .\Configure_Services -ConfigurationData $ConfigurationData Copy_PowerShell_Scripts -OutputPath .\Copy_PowerShell_Scripts -ConfigurationData $ConfigurationData ## runs the desired .MOF configuration file Set-DscLocalConfigurationManager -Path .\DSC_LCM -ComputerName $AllNodes -ErrorAction SilentlyContinue Start-DscConfiguration -Path .\Copy_PowerShell_Scripts -ComputerName $AllNodes -ErrorAction SilentlyContinue Start-DscConfiguration -Path .\Add_Features -ComputerName $AllNode -ErrorAction SilentlyContinue Start-DscConfiguration -Path .\Configure_Services -ComputerName $AllNodes -ErrorAction SilentlyContinue $EndTime = (Get-Date) ## Stops the timer "Elapsed Time: $(($EndTime - $StartTime).totalseconds) seconds" ## Calculate amount of seconds your code takes to complete. Set-Location "B:\DSC\" Stop-Transcript -Verbose |