├── .gitignore ├── AD-Group.json ├── AD-OU.json ├── AD-Users.json ├── Instructions.md ├── PostSetup ├── Download-Git.ps1 ├── Install-Sysinternals.ps1 ├── Install-VSCode.ps1 ├── README.md ├── Run-WindowsUpdate.ps1 └── RunAll.ps1 ├── README.md ├── VMConfiguration.ps1 ├── VMConfigurationData.psd1 └── VMValidate.test.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | *.mof 2 | *.mof.error 3 | *.bak 4 | .gitignore 5 | thumbs.db 6 | Desktop.ini 7 | *.cab 8 | *.zip 9 | *.msi 10 | *.log 11 | 12 | -------------------------------------------------------------------------------- /AD-Group.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "DistinguishedName": "CN=IT,OU=IT,DC=Company,DC=Pri", 4 | "Name": "IT", 5 | "GroupCategory": "Security", 6 | "GroupScope": "Global", 7 | "Members": [ 8 | "MikeS", 9 | "MaryL", 10 | "ArtD", 11 | "AprilS" 12 | ] 13 | }, 14 | { 15 | "DistinguishedName": "CN=Sales,OU=Sales,DC=Company,DC=Pri", 16 | "Name": "Sales", 17 | "GroupCategory": "Security", 18 | "GroupScope": "Global", 19 | "Members": [ 20 | "SamanthaS", 21 | "SonyaS", 22 | "SamS" 23 | ] 24 | }, 25 | { 26 | "DistinguishedName": "CN=Marketing,OU=Marketing,DC=Company,DC=Pri", 27 | "Name": "Marketing", 28 | "GroupCategory": "Security", 29 | "GroupScope": "Global", 30 | "Members": [ 31 | "MattS", 32 | "MonicaS", 33 | "MarkS" 34 | ] 35 | }, 36 | { 37 | "DistinguishedName": "CN=Accounting,OU=Accounting,DC=Company,DC=Pri", 38 | "Name": "Accounting", 39 | "GroupCategory": "Security", 40 | "GroupScope": "Global", 41 | "Members": [ 42 | "AaronS", 43 | "AndreaS", 44 | "AndyS" 45 | ] 46 | }, 47 | { 48 | "DistinguishedName": "CN=JEA Operators,OU=JEA_Operators,DC=Company,DC=Pri", 49 | "Name": "JEA Operators", 50 | "GroupCategory": "Security", 51 | "GroupScope": "Global", 52 | "Members": [ 53 | "JimJ", 54 | "JillJ", 55 | "ArtD", 56 | "AprilS" 57 | ] 58 | }, 59 | { 60 | "DistinguishedName": "CN=Domain Admins,CN=Users,DC=Company,DC=Pri", 61 | "Name": "Domain Admins", 62 | "GroupCategory": "Security", 63 | "GroupScope": "Global", 64 | "Members": [ 65 | "ArtD", 66 | "AprilS", 67 | "Administrator" 68 | ] 69 | } 70 | 71 | ] 72 | -------------------------------------------------------------------------------- /AD-OU.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "IT", 4 | "Description": null 5 | }, 6 | { 7 | "Name": "Dev", 8 | "Description": null 9 | }, 10 | { 11 | "Name": "Marketing", 12 | "Description": null 13 | }, 14 | { 15 | "Name": "Sales", 16 | "Description": null 17 | }, 18 | { 19 | "Name": "Accounting", 20 | "Description": null 21 | }, 22 | { 23 | "Name": "JEA_Operators", 24 | "Description": null 25 | }, 26 | { 27 | "Name": "Servers", 28 | "Description": "company servers" 29 | }, 30 | { 31 | "Name": "Employees", 32 | "Description": "company employees" 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /AD-Users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "DistinguishedName": "CN=MaryL,OU=IT,DC=Company,DC=Pri", 4 | "Name": "MaryL", 5 | "SamAccountName": "MaryL", 6 | "GivenName": "Mary", 7 | "Surname": "Lennon", 8 | "DisplayName": "Mary Lennon", 9 | "Description": "Main IT", 10 | "Department": "IT" 11 | }, 12 | { 13 | "DistinguishedName": "CN=ArtD,OU=IT,DC=Company,DC=Pri", 14 | "Name": "ArtD", 15 | "SamAccountName": "ArtD", 16 | "GivenName": "Art", 17 | "Surname": "Deco", 18 | "DisplayName": "Art Deco", 19 | "Description": "PowerShell Guru", 20 | "Department": "IT" 21 | }, 22 | { 23 | "DistinguishedName": "CN=AprilS,OU=IT,DC=Company,DC=Pri", 24 | "Name": "AprilS", 25 | "SamAccountName": "AprilS", 26 | "GivenName": "April", 27 | "Surname": "Showers", 28 | "DisplayName": "April Showers", 29 | "Description": "PowerShell Guru", 30 | "Department": "IT" 31 | }, 32 | { 33 | "DistinguishedName": "CN=MikeS,OU=IT,DC=Company,DC=Pri", 34 | "Name": "MikeS", 35 | "SamAccountName": "MikeS", 36 | "GivenName": "Mike", 37 | "Surname": "Smith", 38 | "DisplayName": "Mike Smith", 39 | "Description": "Backup IT", 40 | "Department": "IT" 41 | }, 42 | { 43 | "DistinguishedName": "CN=SimonS,OU=Dev,DC=Company,DC=Pri", 44 | "Name": "SimonS", 45 | "SamAccountName": "SimonS", 46 | "GivenName": "Simon", 47 | "Surname": "Smith", 48 | "DisplayName": "Simon Smith", 49 | "Description": "The Developer", 50 | "Department": "Dev" 51 | }, 52 | { 53 | "DistinguishedName": "CN=AaronS,OU=Accounting,DC=Company,DC=Pri", 54 | "Name": "AaronS", 55 | "SamAccountName": "AaronS", 56 | "GivenName": "Aaron", 57 | "Surname": "Smith", 58 | "DisplayName": "Aaron Smith", 59 | "Description": "Accountant", 60 | "Department": "Accounting" 61 | }, 62 | { 63 | "DistinguishedName": "CN=AndreaS,OU=Accounting,DC=Company,DC=Pri", 64 | "Name": "AndreaS", 65 | "SamAccountName": "AndreaS", 66 | "GivenName": "Andrea", 67 | "Surname": "Smith", 68 | "DisplayName": "Andrea Smith", 69 | "Description": "Accountant", 70 | "Department": "Accounting" 71 | }, 72 | { 73 | "DistinguishedName": "CN=AndyS,OU=Accounting,DC=Company,DC=Pri", 74 | "Name": "AndyS", 75 | "SamAccountName": "AndyS", 76 | "GivenName": "Andy", 77 | "Surname": "Smith", 78 | "DisplayName": "Andy Smith", 79 | "Description": "Accountant", 80 | "Department": "Accounting" 81 | }, 82 | { 83 | "DistinguishedName": "CN=SamS,OU=Sales,DC=Company,DC=Pri", 84 | "Name": "SamS", 85 | "SamAccountName": "SamS", 86 | "GivenName": "Sam", 87 | "Surname": "Smith", 88 | "DisplayName": "Sam Smith", 89 | "Description": "Sales", 90 | "Department": "Sales" 91 | }, 92 | { 93 | "DistinguishedName": "CN=SonyaS,OU=Sales,DC=Company,DC=Pri", 94 | "Name": "SonyaS", 95 | "SamAccountName": "SonyaS", 96 | "GivenName": "Sonya", 97 | "Surname": "Smith", 98 | "DisplayName": "Sonya Smith", 99 | "Description": "Sales", 100 | "Department": "Sales" 101 | }, 102 | { 103 | "DistinguishedName": "CN=SamanthaS,OU=Sales,DC=Company,DC=Pri", 104 | "Name": "SamanthaS", 105 | "SamAccountName": "SamanthaS", 106 | "GivenName": "Samantha", 107 | "Surname": "Smith", 108 | "DisplayName": "Samantha Smith", 109 | "Description": "Sales", 110 | "Department": "Sales" 111 | }, 112 | { 113 | "DistinguishedName": "CN=MarkS,OU=Marketing,DC=Company,DC=Pri", 114 | "Name": "MarkS", 115 | "SamAccountName": "MarkS", 116 | "GivenName": "Mark", 117 | "Surname": "Smith", 118 | "DisplayName": "Mark Smith", 119 | "Description": "Marketing", 120 | "Department": "Marketing" 121 | }, 122 | { 123 | "DistinguishedName": "CN=MonicaS,OU=Marketing,DC=Company,DC=Pri", 124 | "Name": "MonicaS", 125 | "SamAccountName": "MonicaS", 126 | "GivenName": "Monica", 127 | "Surname": "Smith", 128 | "DisplayName": "Monica Smith", 129 | "Description": "Marketing", 130 | "Department": "Marketing" 131 | }, 132 | { 133 | "DistinguishedName": "CN=MattS,OU=Marketing,DC=Company,DC=Pri", 134 | "Name": "MattS", 135 | "SamAccountName": "MattS", 136 | "GivenName": "Matt", 137 | "Surname": "Smith", 138 | "DisplayName": "Matt Smith", 139 | "Description": "Marketing", 140 | "Department": "Marketing" 141 | }, 142 | { 143 | "DistinguishedName": "CN=JimJ,OU=JEA_Operators,DC=Company,DC=Pri", 144 | "Name": "JimJ", 145 | "SamAccountName": "JimJ", 146 | "GivenName": "Jim", 147 | "Surname": "Jea", 148 | "DisplayName": "Jim Jea", 149 | "Description": "JEA", 150 | "Department": "IT" 151 | }, 152 | { 153 | "DistinguishedName": "CN=JillJ,OU=JEA_Operators,DC=Company,DC=Pri", 154 | "Name": "JillJ", 155 | "SamAccountName": "JillJ", 156 | "GivenName": "Jill", 157 | "Surname": "Jea", 158 | "DisplayName": "Jill Jea", 159 | "Description": "JEA", 160 | "Department": "IT" 161 | }, 162 | { 163 | "DistinguishedName": "CN=E.Ratti,DC=Company,DC=pri", 164 | "Name": "Erich Ratti", 165 | "SAMAccountname": "E.Ratti", 166 | "GivenName": "Erich", 167 | "Surname": "Ratti", 168 | "DisplayName": "Erich Ratti", 169 | "Description": null, 170 | "Department": "Marketing" 171 | }, 172 | { 173 | "DistinguishedName": "CN=L.Kuja,DC=Company,DC=pri", 174 | "Name": "Louis Kuja", 175 | "SAMAccountname": "L.Kuja", 176 | "GivenName": "Louis", 177 | "Surname": "Kuja", 178 | "DisplayName": "Louis Kuja", 179 | "Description": null, 180 | "Department": "Development" 181 | }, 182 | { 183 | "DistinguishedName": "CN=M.Nierer,DC=Company,DC=pri", 184 | "Name": "Myron Nierer", 185 | "SAMAccountname": "M.Nierer", 186 | "GivenName": "Myron", 187 | "Surname": "Nierer", 188 | "DisplayName": "Myron Nierer", 189 | "Description": null, 190 | "Department": "Physical Plant" 191 | }, 192 | { 193 | "DistinguishedName": "CN=R.Freil,DC=Company,DC=pri", 194 | "Name": "Rayford Freil", 195 | "SAMAccountname": "R.Freil", 196 | "GivenName": "Rayford", 197 | "Surname": "Freil", 198 | "DisplayName": "Rayford Freil", 199 | "Description": null, 200 | "Department": "Security" 201 | }, 202 | { 203 | "DistinguishedName": "CN=T.Shult,DC=Company,DC=pri", 204 | "Name": "Tom Shult", 205 | "SAMAccountname": "T.Shult", 206 | "GivenName": "Tom", 207 | "Surname": "Shult", 208 | "DisplayName": "Tom Shult", 209 | "Description": null, 210 | "Department": "Manufacturing" 211 | }, 212 | { 213 | "DistinguishedName": "CN=C.Melve,DC=Company,DC=pri", 214 | "Name": "Cyrus Melve", 215 | "SAMAccountname": "C.Melve", 216 | "GivenName": "Cyrus", 217 | "Surname": "Melve", 218 | "DisplayName": "Cyrus Melve", 219 | "Description": null, 220 | "Department": "Security" 221 | }, 222 | { 223 | "DistinguishedName": "CN=B.Storr,DC=Company,DC=pri", 224 | "Name": "Bennett Storr", 225 | "SAMAccountname": "B.Storr", 226 | "GivenName": "Bennett", 227 | "Surname": "Storr", 228 | "DisplayName": "Bennett Storr", 229 | "Description": null, 230 | "Department": "Payroll" 231 | }, 232 | { 233 | "DistinguishedName": "CN=S.Montbriand,DC=Company,DC=pri", 234 | "Name": "Sammy Montbriand", 235 | "SAMAccountname": "S.Montbriand", 236 | "GivenName": "Sammy", 237 | "Surname": "Montbriand", 238 | "DisplayName": "Sammy Montbriand", 239 | "Description": null, 240 | "Department": "Manufacturing" 241 | }, 242 | { 243 | "DistinguishedName": "CN=N.Wobser,DC=Company,DC=pri", 244 | "Name": "Napoleon Wobser", 245 | "SAMAccountname": "N.Wobser", 246 | "GivenName": "Napoleon", 247 | "Surname": "Wobser", 248 | "DisplayName": "Napoleon Wobser", 249 | "Description": null, 250 | "Department": "Quality Assurance" 251 | }, 252 | { 253 | "DistinguishedName": "CN=J.Clineman,DC=Company,DC=pri", 254 | "Name": "James Clineman", 255 | "SAMAccountname": "J.Clineman", 256 | "GivenName": "James", 257 | "Surname": "Clineman", 258 | "DisplayName": "James Clineman", 259 | "Description": null, 260 | "Department": "Physical Plant" 261 | }, 262 | { 263 | "DistinguishedName": "CN=A.Henaire,DC=Company,DC=pri", 264 | "Name": "Alexander Henaire", 265 | "SAMAccountname": "A.Henaire", 266 | "GivenName": "Alexander", 267 | "Surname": "Henaire", 268 | "DisplayName": "Alexander Henaire", 269 | "Description": null, 270 | "Department": "Customer Service" 271 | }, 272 | { 273 | "DistinguishedName": "CN=E.Muhtaseb,DC=Company,DC=pri", 274 | "Name": "Eliseo Muhtaseb", 275 | "SAMAccountname": "E.Muhtaseb", 276 | "GivenName": "Eliseo", 277 | "Surname": "Muhtaseb", 278 | "DisplayName": "Eliseo Muhtaseb", 279 | "Description": null, 280 | "Department": "Manufacturing" 281 | }, 282 | { 283 | "DistinguishedName": "CN=Y.Graffney,DC=Company,DC=pri", 284 | "Name": "Yong Graffney", 285 | "SAMAccountname": "Y.Graffney", 286 | "GivenName": "Yong", 287 | "Surname": "Graffney", 288 | "DisplayName": "Yong Graffney", 289 | "Description": null, 290 | "Department": "Customer Service" 291 | }, 292 | { 293 | "DistinguishedName": "CN=D.Monroy,DC=Company,DC=pri", 294 | "Name": "Dee Monroy", 295 | "SAMAccountname": "D.Monroy", 296 | "GivenName": "Dee", 297 | "Surname": "Monroy", 298 | "DisplayName": "Dee Monroy", 299 | "Description": null, 300 | "Department": "Consumer Affairs" 301 | }, 302 | { 303 | "DistinguishedName": "CN=E.Capece,DC=Company,DC=pri", 304 | "Name": "Everette Capece", 305 | "SAMAccountname": "E.Capece", 306 | "GivenName": "Everette", 307 | "Surname": "Capece", 308 | "DisplayName": "Everette Capece", 309 | "Description": null, 310 | "Department": "Executive" 311 | }, 312 | { 313 | "DistinguishedName": "CN=L.Saller,DC=Company,DC=pri", 314 | "Name": "Lanny Saller", 315 | "SAMAccountname": "L.Saller", 316 | "GivenName": "Lanny", 317 | "Surname": "Saller", 318 | "DisplayName": "Lanny Saller", 319 | "Description": null, 320 | "Department": "Physical Plant" 321 | }, 322 | { 323 | "DistinguishedName": "CN=G.Bushmaker,DC=Company,DC=pri", 324 | "Name": "Gordon Bushmaker", 325 | "SAMAccountname": "G.Bushmaker", 326 | "GivenName": "Gordon", 327 | "Surname": "Bushmaker", 328 | "DisplayName": "Gordon Bushmaker", 329 | "Description": null, 330 | "Department": "Manufacturing" 331 | }, 332 | { 333 | "DistinguishedName": "CN=G.Guillary,DC=Company,DC=pri", 334 | "Name": "Garret Guillary", 335 | "SAMAccountname": "G.Guillary", 336 | "GivenName": "Garret", 337 | "Surname": "Guillary", 338 | "DisplayName": "Garret Guillary", 339 | "Description": null, 340 | "Department": "Sales" 341 | }, 342 | { 343 | "DistinguishedName": "CN=D.Waldow,DC=Company,DC=pri", 344 | "Name": "Diego Waldow", 345 | "SAMAccountname": "D.Waldow", 346 | "GivenName": "Diego", 347 | "Surname": "Waldow", 348 | "DisplayName": "Diego Waldow", 349 | "Description": null, 350 | "Department": "Public Affairs" 351 | }, 352 | { 353 | "DistinguishedName": "CN=D.Colato,DC=Company,DC=pri", 354 | "Name": "Duncan Colato", 355 | "SAMAccountname": "D.Colato", 356 | "GivenName": "Duncan", 357 | "Surname": "Colato", 358 | "DisplayName": "Duncan Colato", 359 | "Description": null, 360 | "Department": "Manufacturing" 361 | }, 362 | { 363 | "DistinguishedName": "CN=D.Fierst,DC=Company,DC=pri", 364 | "Name": "Dewitt Fierst", 365 | "SAMAccountname": "D.Fierst", 366 | "GivenName": "Dewitt", 367 | "Surname": "Fierst", 368 | "DisplayName": "Dewitt Fierst", 369 | "Description": null, 370 | "Department": "Customer Service" 371 | }, 372 | { 373 | "DistinguishedName": "CN=A.Fieldhouse,DC=Company,DC=pri", 374 | "Name": "Aron Fieldhouse", 375 | "SAMAccountname": "A.Fieldhouse", 376 | "GivenName": "Aron", 377 | "Surname": "Fieldhouse", 378 | "DisplayName": "Aron Fieldhouse", 379 | "Description": null, 380 | "Department": "Physical Plant" 381 | }, 382 | { 383 | "DistinguishedName": "CN=S.Talone,DC=Company,DC=pri", 384 | "Name": "Stanton Talone", 385 | "SAMAccountname": "S.Talone", 386 | "GivenName": "Stanton", 387 | "Surname": "Talone", 388 | "DisplayName": "Stanton Talone", 389 | "Description": null, 390 | "Department": "Public Affairs" 391 | }, 392 | { 393 | "DistinguishedName": "CN=D.Hamsher,DC=Company,DC=pri", 394 | "Name": "Donte Hamsher", 395 | "SAMAccountname": "D.Hamsher", 396 | "GivenName": "Donte", 397 | "Surname": "Hamsher", 398 | "DisplayName": "Donte Hamsher", 399 | "Description": null, 400 | "Department": "Consumer Affairs" 401 | }, 402 | { 403 | "DistinguishedName": "CN=K.Moshos,DC=Company,DC=pri", 404 | "Name": "Kendall Moshos", 405 | "SAMAccountname": "K.Moshos", 406 | "GivenName": "Kendall", 407 | "Surname": "Moshos", 408 | "DisplayName": "Kendall Moshos", 409 | "Description": null, 410 | "Department": "Manufacturing" 411 | } 412 | ] -------------------------------------------------------------------------------- /Instructions.md: -------------------------------------------------------------------------------- 1 | # Lab definition 2 | 3 | This lab builds the following: 4 | 5 | * 1 Windows Server 2016 domain controller with users, groups and OU's - GUI 6 | * 1 DHCP server on the DOM1 7 | * 2 Domain joined servers (SRV1 and SRV2) running Windows Server 2016 Core 8 | * 1 workgroup based server (SRV3) running Windows Server 2019 Core 9 | * 1 Domain joined Windows 10 Client with RSAT tools installed 10 | 11 | ## To get started 12 | 13 | To run the full lab setup, which includes Setup-Lab, Run-Lab, Enable-Internet, and Validate-Lab. You should run all commands from the directory with the MOF and psd1 files. 14 | 15 | ```powershell 16 | PS> Unattend-Lab 17 | ``` 18 | 19 | To run the commands individually to setup the lab environment: 20 | 21 | Run the following for initial setup: 22 | 23 | ```powershell 24 | PS> Setup-Lab 25 | ``` 26 | 27 | To start the Lab, and apply configurations the first time: 28 | 29 | ```powershell 30 | PS> Run-Lab 31 | ``` 32 | 33 | To enable Internet access for the VM's, run: 34 | 35 | ```powershell 36 | PS> Enable-Internet 37 | ``` 38 | 39 | To validate when configurations have converged: 40 | 41 | ```powershell 42 | PS> Validate-Lab 43 | ``` 44 | 45 | Or you can run the Pester test directly 46 | 47 | ```powershell 48 | PS> Invoke-Pester vmvalidate.test.ps1 49 | ``` 50 | 51 | ## To Stop and snapshot the lab 52 | 53 | To stop the lab VM's: 54 | 55 | ```powershell 56 | PS> Shutdown-lab 57 | ``` 58 | 59 | To checkpoint the VM's: 60 | 61 | ```powershell 62 | PS> Snapshot-Lab 63 | ``` 64 | 65 | To quickly rebuild the labs from the checkpoint, run: 66 | 67 | ```powershell 68 | PS> Refresh-Lab 69 | ``` 70 | 71 | ## To remove a lab 72 | 73 | To destroy the lab to build again: 74 | 75 | ```powershell 76 | PS> Wipe-Lab 77 | ``` 78 | -------------------------------------------------------------------------------- /PostSetup/Download-Git.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.1 2 | 3 | [CmdletBinding(DefaultParameterSetName = "VM")] 4 | Param( 5 | [Parameter(Mandatory, ParameterSetName = 'VM')] 6 | #specify the name of a VM 7 | [string]$VMName, 8 | [Parameter(Mandatory, ParameterSetName = 'VM')] 9 | #Specify the user credential 10 | [pscredential]$Credential, 11 | [Parameter(Mandatory, ParameterSetName = "session")] 12 | #specify an existing PSSession object 13 | [System.Management.Automation.Runspaces.PSSession[]]$Session, 14 | [switch]$Install 15 | ) 16 | 17 | Try { 18 | if ($PSCmdlet.ParameterSetName -eq 'VM') { 19 | Write-Host "Creating PSSession to $VMName" -ForegroundColor cyan 20 | 21 | $session = New-PSSession @PSBoundParameters -ErrorAction stop 22 | } 23 | 24 | $sb = { 25 | Param([switch]$Install) 26 | 27 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 28 | 29 | #download the latest 64bit version of Git for Windows 30 | $uri = 'https://git-scm.com/download/win' 31 | #path to store the downloaded file 32 | $path = "C:\" 33 | Write-Host "Getting latest version of git from $uri" -ForegroundColor cyan 34 | #get the web page 35 | $page = Invoke-WebRequest -Uri $uri -UseBasicParsing -DisableKeepAlive 36 | 37 | #get the download link 38 | $dl = ($page.links | where-object outerhtml -match 'git-.*-64-bit.exe' | Select-Object -first 1 * ).href 39 | Write-Host "Found download link $dl" -ForegroundColor cyan 40 | 41 | #split out the filename 42 | $filename = split-path $dl -leaf 43 | 44 | #construct a filepath for the download 45 | $out = Join-Path -Path $path -ChildPath $filename 46 | Write-Host "Downloading $out from $dl" -ForegroundColor cyan 47 | 48 | #download the file 49 | Try { 50 | Invoke-WebRequest -uri $dl -OutFile $out -UseBasicParsing -DisableKeepAlive -ErrorAction Stop 51 | 52 | if ($install) { 53 | &$out /verysilent /norestart /suppressmessageboxes 54 | } 55 | else { 56 | #check it out 57 | Get-Item $out 58 | } 59 | } 60 | Catch { 61 | Throw $_ 62 | } 63 | } 64 | 65 | Invoke-Command -ScriptBlock $sb -Session $session -ArgumentList $Install 66 | 67 | if ($PSCmdlet.ParameterSetName -eq 'VM') { 68 | Write-Host "Removing PSSession" -ForegroundColor cyan 69 | $Session | Remove-PSSession 70 | } 71 | } 72 | Catch { 73 | Throw $_ 74 | } 75 | 76 | 77 | -------------------------------------------------------------------------------- /PostSetup/Install-Sysinternals.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.1 2 | 3 | <# 4 | Download Sysinternals tools from web to a local folder in a VM 5 | 6 | **************************************************************** 7 | * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED * 8 | * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF * 9 | * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, * 10 | * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. * 11 | **************************************************************** 12 | #> 13 | 14 | 15 | [CmdletBinding(DefaultParameterSetName="VM")] 16 | Param( 17 | [Parameter(Mandatory,ParameterSetName='VM')] 18 | #specify the name of a VM 19 | [string]$VMName, 20 | [Parameter(Mandatory,ParameterSetName='VM')] 21 | #Specify the user credential 22 | [pscredential]$Credential, 23 | [Parameter(Mandatory,ParameterSetName="session")] 24 | #specify an existing PSSession object 25 | [System.Management.Automation.Runspaces.PSSession[]]$Session 26 | ) 27 | 28 | Try { 29 | if ($PSCmdlet.ParameterSetName -eq 'VM') { 30 | Write-Host "Creating PSSession to $VMName" -ForegroundColor cyan 31 | $session = New-PSSession @PSBoundParameters -ErrorAction stop 32 | } 33 | 34 | $sb = { 35 | [string]$Destination = "C:\SysInternals" 36 | if (-Not (Test-Path $Destination)) { 37 | new-Item -Path $Destination -ItemType Directory 38 | } 39 | 40 | #start the WebClient service if it is not running 41 | if ((Get-Service WebClient).Status -eq 'Stopped') { 42 | Write-host "Starting WebClient" -ForegroundColor Magenta 43 | #always start the webclient service even if using -Whatif 44 | Start-Service WebClient -WhatIf:$false 45 | $Stopped = $True 46 | } 47 | else { 48 | <# 49 | Define a variable to indicate service was already running 50 | so that we don't stop it. Making an assumption that the 51 | service is already running for a reason. 52 | #> 53 | $Stopped = $False 54 | } 55 | 56 | Write-Host "Updating Sysinternals tools from \\live.sysinternals.com\tools to $destination" -ForegroundColor Cyan 57 | 58 | Get-ChildItem -path \\live.sysinternals.com\tools -file | Copy-Item -Destination $Destination -PassThru 59 | 60 | <# 61 | alternative but this might still copy files that haven't 62 | really changed 63 | Robocopy \\live.sysinternals.com\tools $destination /MIR 64 | #> 65 | 66 | if ( $Stopped ) { 67 | Write-host "Stopping web client" -ForegroundColor Magenta 68 | #always stop the service even if using -Whatif 69 | Stop-Service WebClient -WhatIf:$False 70 | } 71 | 72 | Write-Host "Sysinternals Update Complete" -ForegroundColor Cyan 73 | } 74 | 75 | Invoke-Command -ScriptBlock $sb -Session $session 76 | 77 | if ($PSCmdlet.ParameterSetName -eq 'VM') { 78 | Write-Host "Removing PSSession" -ForegroundColor cyan 79 | $Session | Remove-PSSession 80 | } 81 | } 82 | Catch { 83 | Throw $_ 84 | } 85 | 86 | -------------------------------------------------------------------------------- /PostSetup/Install-VSCode.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.1 2 | 3 | #Download and install the latest 64bit version of VSCode 4 | 5 | [CmdletBinding(DefaultParameterSetName = "VM")] 6 | Param( 7 | [Parameter(Mandatory, ParameterSetName = 'VM')] 8 | #specify the name of a VM 9 | [string]$VMName, 10 | [Parameter(Mandatory, ParameterSetName = 'VM')] 11 | #Specify the user credential 12 | [pscredential]$Credential, 13 | [Parameter(Mandatory, ParameterSetName = "session")] 14 | #specify an existing PSSession object 15 | [System.Management.Automation.Runspaces.PSSession]$Session 16 | ) 17 | 18 | #download the setup file on the host and then copy to VM to avoid strange name resolution problems 19 | $path = $env:temp 20 | $uri = 'https://vscode-update.azurewebsites.net/latest/win32-x64/stable' 21 | # 'https://vscode-update.azurewebsites.net/latest/win32-x64/stable' 22 | # 'https://go.microsoft.com/fwlink/?Linkid=852157' 23 | $out = Join-Path -Path $Path -ChildPath VSCodeSetup-x64.exe 24 | 25 | Try { 26 | Write-Host " Downloading from $uri" -foreground magenta 27 | Invoke-WebRequest -Uri $uri -OutFile $out -DisableKeepAlive -UseBasicParsing 28 | } 29 | Catch { 30 | Throw $_ 31 | #bail out 32 | Return 33 | } 34 | 35 | Try { 36 | if ($PSCmdlet.ParameterSetName -eq 'VM') { 37 | Write-Host "Creating PSSession to $VMName" -ForegroundColor cyan 38 | $session = New-PSSession @PSBoundParameters -ErrorAction stop 39 | } 40 | 41 | #copy the file to the VM 42 | copy-item -Path $out -Destination C:\ -ToSession $Session 43 | 44 | $sb = { 45 | $file = 'C:\VSCodeSetup-x64.exe' 46 | Write-Host "[$($env:computername)] Installing VSCode" -foreground magenta 47 | $loadInf = '@ 48 | [Setup] 49 | Lang=english 50 | Dir=C:\Program Files\Microsoft VS Code 51 | Group=Visual Studio Code 52 | NoIcons=0 53 | Tasks=desktopicon,addcontextmenufiles,addcontextmenufolders,addtopath 54 | @' 55 | $infPath = "${env:TEMP}\load.inf" 56 | $loadInf | Out-File $infPath 57 | 58 | Start-Process -FilePath $file -ArgumentList "/VERYSILENT /LOADINF=${infPath}" -Wait 59 | Write-Host "[$($env:computername)] Finished Installing VSCode" -foreground magenta 60 | } #close scriptblock 61 | 62 | Invoke-Command -ScriptBlock $sb -Session $session 63 | 64 | if ($PSCmdlet.ParameterSetName -eq 'VM') { 65 | Write-Host "Removing PSSession" -ForegroundColor cyan 66 | $Session | Remove-PSSession 67 | } 68 | } 69 | Catch { 70 | Throw $_ 71 | } 72 | 73 | -------------------------------------------------------------------------------- /PostSetup/README.md: -------------------------------------------------------------------------------- 1 | # Post Setup 2 | 3 | The PowerShell scripts in this directory can be run after the lab setup is complete. You can use these scripts to perform some additional configuration of the virtual machines. Because these files are all PowerShell scripts you need to specify the full path to the file. If you are in the current directory you can use a .\ to reference the current location. 4 | 5 | Usage of these scripts is completely optional and are provided for your convenience. You may elect to manually accomplish these tasks from the PostSetup folder. For any installations to the Windows 10 client, you should launch an interactive session first to the virtual machine with the credential you intend to use to force a profile creation. 6 | 7 | ```powershell 8 | vmconnect localhost Win10 9 | ``` 10 | 11 | ## Run-WindowsUpdate.ps1 12 | 13 | Use this script to run Windows Update on one or more virtual machines. The script will install all updates. The process may take some time depending on the number of virtual machines. 14 | 15 | ```powershell 16 | .\Run-WindowsUpdate -vmname srv1,srv2,win10 -credential company\administrator 17 | ``` 18 | 19 | The credential you specify will be used for all virtual machines. The default password should be `P@ssw0rd`. 20 | You can also run the command as a background job. 21 | 22 | ```powershell 23 | .\Run-WindowsUpdate -vmname srv3 -credential srv3\administrator -asjob 24 | ``` 25 | 26 | When finished, you can retrieve the job results. 27 | 28 | ```powershell 29 | PS C:\AutoLabConfigurations\PowerShellLab\PostSetup> receive-job 12 -keep 30 | [SRV3] Create update session objects 31 | [SRV3] Retrieving available updates 32 | [SRV3] Retrieved 2 updates 33 | [SRV3] Processing 2 updates 34 | [SRV3] Installing 2 updates 35 | [SRV3] Installing 2017-08 Update for Windows Server 2016 for x64-based Systems (KB4035631) 36 | 37 | 38 | RebootRequired : True 39 | InstallDate : 10/4/2017 2:59:19 PM 40 | Result : Success 41 | Computername : SRV3 42 | Severity : 43 | Title : 2017-08 Update for Windows Server 2016 for x64-based Systems (KB4035631) 44 | PSComputerName : SRV3 45 | RunspaceId : 3591e46b-e37f-42e0-a516-388f63ecc105 46 | 47 | [SRV3] Installing 2017-09 Cumulative Update for Windows Server 2016 for x64-based Systems (KB4038782) 48 | RebootRequired : False 49 | InstallDate : 10/4/2017 2:59:26 PM 50 | Result : Success 51 | Computername : SRV3 52 | Severity : Critical 53 | Title : 2017-09 Cumulative Update for Windows Server 2016 for x64-based Systems (KB4038782) 54 | PSComputerName : SRV3 55 | RunspaceId : 3591e46b-e37f-42e0-a516-388f63ecc105 56 | 57 | [SRV3] One or more updates requires a reboot. 58 | ``` 59 | 60 | You can then remove any PSSessions. 61 | 62 | ```powershell 63 | get-pssession | remove-pssession 64 | ``` 65 | 66 | ## Install-SysInternals.ps1 67 | 68 | Use this script to download the SysInternals suite from Microsoft. All of the files will be stored in a new folder, `C:\Sysinternals`. It is assumed you will only need to run this for the client virtual machine. 69 | 70 | ```powerShell 71 | .\Install-SysInternals -vmname win10 -credential company\administrator 72 | ``` 73 | 74 | If you already have an existing PSSession to the virtual machine you can use that instead: 75 | 76 | ```powershell 77 | .\Install-SysInternals -session $sess 78 | ``` 79 | 80 | ## Download-Git.ps1 81 | 82 | This script will download the current Windows version of the git setup file. The file will be saved to the root of C:\. You will need to manually setup and configure git in the virtual machine. 83 | 84 | ```powershell 85 | .\Download-Git -VMName win10 -Credential company\artd 86 | ``` 87 | 88 | If you already have an existing PSSession to the virtual machine you can use that of the VMName and credential. 89 | 90 | ## Install-VSCode.ps1 91 | 92 | This script will download and install the current version of Visual Studio Code. The file will be saved to the root of C:\. It is assumed you will run this for the client virtual machine. 93 | 94 | ```powershell 95 | .\Install-VSCode -vmname win10 -credential company\aprils 96 | ``` 97 | 98 | If you already have an existing PSSession to the virtual machine you can use that of the VMName and credential. 99 | Once installed, you can logon as and finish configuration such as installing the PowerShell VSCode extension. 100 | 101 | ## Notes 102 | 103 | If you want to restart all of the virtual machines, use a command like this: 104 | 105 | ```powershell 106 | Get-VM Dom1,Srv*,Win10 | Stop-VM -force -passthru | Start-VM -passthru 107 | ``` 108 | 109 | The virtual machines must be running before using any of these scripts. 110 | -------------------------------------------------------------------------------- /PostSetup/Run-WindowsUpdate.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/PowerShellLab/79ac98e30563ab9be7a8bd20674d0cfae4796a41/PostSetup/Run-WindowsUpdate.ps1 -------------------------------------------------------------------------------- /PostSetup/RunAll.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.1 2 | 3 | [cmdletbinding()] 4 | 5 | Param( 6 | #run the script without any prompting 7 | [Switch]$Force 8 | ) 9 | #this is here in case the script is aborted from previous runs 10 | [void]$PSDefaultParameterValues.remove("write-host:foregroundcolor") 11 | 12 | if (-Not $Force) { 13 | $msg = @" 14 | 15 | This control script will run all post update tasks on virtual machines that are 16 | part of the PowerShellLab Autolab configuration. It is assumed you have not 17 | modified anything in the configuration files. 18 | 19 | The script will do the following: 20 | - Run Windows Update on all virtual machines 21 | - Download Git on the Win10 client 22 | - Download Sysinternals tools on the Win10 client 23 | - Install VSCode on the Win10 client 24 | - Update PowerShell help on the Win10 client 25 | - Reboot all virtual machines 26 | 27 | Before running this script, all configuration setup must be complete. You 28 | must also have performed an interactive logon on the Windows 10 client with the 29 | Company\Administrator account to complete the Windows 10 setup. 30 | 31 | Once the profile is complete, you can log off. 32 | 33 | If any of the commands fail, try running it separately. 34 | 35 | Make sure all virtual machines are running before continuing. 36 | 37 | $(Get-VM Win10,SRV1,SRV2,SRV3,DOM1 | Out-String) 38 | "@ 39 | 40 | Clear-Host 41 | Write-Host $msg -ForegroundColor Cyan 42 | 43 | $coll = @() 44 | $coll+= [System.Management.Automation.Host.ChoiceDescription]::new("Yes &Y") 45 | $coll+= [System.Management.Automation.Host.ChoiceDescription]::new("No &N") 46 | $r = $host.ui.PromptForChoice("Do you want to continue with this script?","",$coll,1) 47 | If ($r -eq 1) { 48 | return 49 | } 50 | } #if not using -Force 51 | 52 | #set a default color for the progress messages 53 | $PSDefaultParameterValues.add("write-host:foregroundcolor","yellow") 54 | Get-Job | Remove-Job -force 55 | 56 | $pass = ConvertTo-SecureString -String "P@ssw0rd" -AsPlainText -Force 57 | #credential for domain administrator 58 | $admin = New-Object PSCredential "company\administrator",$pass 59 | #password for SRV3 which is in a workgroup 60 | $wg = New-Object PSCredential "srv3\administrator",$pass 61 | 62 | Write-Host "[$(Get-Date -format T)] Starting the post process" 63 | Write-Host "[$(Get-Date -format T)] Creating PSSessions" 64 | 65 | #create PSSessions. Some of these variables are un-used at this time 66 | $all = @() 67 | $srv3 = New-PSSession -VMName SRV3 -Credential $wg 68 | $all+=$srv3 69 | $win10 = New-PSSession -VMName Win10 -Credential $admin 70 | $all+=$win10 71 | $servers = New-PSSession -VMName SRV1,SRV2,DOM1 -Credential $admin 72 | $all+=$servers 73 | 74 | Write-Host "[$(Get-Date -format T)] Waiting for sessions to become available" 75 | Do { 76 | Start-Sleep -Seconds 1 77 | } Until ( ($all| Get-PSSession).Availability -notcontains 'busy') 78 | Write-Host "[$(Get-Date -format T)] Running Windows Update" 79 | &$PSScriptRoot\Run-WindowsUpdate.ps1 -session $all -asJob 80 | 81 | Write-Host "[$(Get-Date -format T)] Waiting for Win10 session to become available" 82 | 83 | Do { 84 | Start-Sleep -Seconds 1 85 | } Until ( ($win10|Get-PSSession).Availability -eq 'available') 86 | Write-Host "[$(Get-Date -format T)] Install SysInternals on Windows 10" 87 | &$PSScriptRoot\Install-Sysinternals.ps1 -Session $win10 | Out-Null 88 | 89 | Write-Host "[$(Get-Date -format T)] Waiting for Win10 session to become available" 90 | Do { 91 | Start-Sleep -Seconds 1 92 | } Until ( ($win10|Get-PSSession).Availability -eq 'available') 93 | Write-Host "[$(Get-Date -format T)] Downloading Git on Windows 10" 94 | &$PSScriptRoot\Download-Git.ps1 -session $win10 | Out-Null 95 | 96 | Write-Host "[$(Get-Date -format T)] Waiting for Win10 session to become available" 97 | Do { 98 | Start-Sleep -Seconds 1 99 | } Until ( ($win10|Get-PSSession).Availability -eq 'available') 100 | 101 | <# 102 | #add some name resolution calls to help with errors I'm getting about 103 | #being unable to resolve the redirected name 104 | Invoke-Command { 105 | Resolve-DnsName go.microsoft.com | Out-Null 106 | Resolve-DnsName azurewebsites.net | out-null 107 | Resolve-DnsName az764295.vo.msecnd.net | out-null 108 | } -Session $all 109 | #> 110 | Write-Host "[$(Get-Date -format T)] Installing VSCode on Windows 10" 111 | #for some unknown reason this fails when using an existing session 112 | # .\install-vscode -session $win10 | Out-Null 113 | &$PSScriptRoot\install-vscode.ps1 -VMName Win10 -Credential $admin | Out-Null 114 | 115 | Write-Host "[$(Get-Date -format T)] Updating PowerShell help on Windows 10" 116 | Do { 117 | Start-Sleep -Seconds 1 118 | } Until ( ($win10 | Get-PSSession).Availability -eq 'available') 119 | #update help and suppress all error messages 120 | Invoke-Command { Update-Help -force -ErrorAction SilentlyContinue } -session $win10 121 | 122 | Write-Host "[$(Get-Date -format T)] Waiting for Windows Update Jobs to complete" 123 | Wait-Job WinUp* 124 | 125 | Write-Host "[$(Get-Date -format T)] Stopping Windows 10" 126 | Stop-VM Win10 -AsJob | Wait-Job | Out-Null 127 | 128 | Write-Host "[$(Get-Date -format T)] Stopping member servers" 129 | Get-VM SRV1,SRV2,SRV3 | Stop-VM -Force -AsJob 130 | 131 | Write-Host "[$(Get-Date -format T)] Stopping DOM1" 132 | Stop-VM DOM1 -AsJob | Wait-Job | Out-Null 133 | 134 | Write-Host "[$(Get-Date -format T)] Restarting all virtual machines" 135 | Start-VM SRV3,DOM1 -asjob | Wait-Job | Out-Null 136 | Start-VM SRV1,SRV2,Win10 -asjob | Out-Null 137 | 138 | #clean up 139 | $all | Remove-PSSession 140 | $PSDefaultParameterValues.remove("write-host:foregroundcolor") 141 | 142 | Write-Host "[$(Get-Date -format T)] Ending the post process script" -ForegroundColor green -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerShellLab 2 | 3 | ## Deprecated 4 | 5 | This lab configuration is no longer maintained in this repository. They are part of the [PSAutolab](https://github.com/pluralsight/PS-AutoLab-Env) module. It is recommended you look at the README file in that repository. 6 | 7 | You can install the PSAutolab module from the PowerShell Gallery. 8 | 9 | ```powershell 10 | Install-Module -Name PSAutolab 11 | ``` 12 | 13 | This repository is maintained for historical purposes and should not be used for new installations. 14 | 15 | ## Legacy Instructions 16 | 17 | To use the scripts and configurations, download the [current release ](https://github.com/jdhitsolutions/PowerShellLab/archive/0.10.0.zip). Extract the contents of the zip file folder to your Autolab Configurations directory. You should end up with something like C:\Autolab\Configurations\PowerShellLab which contains the files from this repository. 18 | 19 | ```powershell 20 | PS C:\> dir C:\Autolab\Configurations\PowerShellLab\ 21 | 22 | 23 | Directory: C:\Autolab\Configurations\PowerShellLab 24 | 25 | 26 | Mode LastWriteTime Length Name 27 | ---- ------------- ------ ---- 28 | d----- 8/25/2017 7:35 PM PostSetup 29 | -a---- 8/25/2017 7:35 PM 24 .gitignore 30 | -a---- 8/25/2017 7:35 PM 2017 AD-Group.json 31 | -a---- 8/25/2017 7:35 PM 506 AD-OU.json 32 | -a---- 8/25/2017 7:35 PM 5017 AD-Users.json 33 | -a---- 8/25/2017 7:35 PM 1054 Instructions.md 34 | -a---- 8/25/2017 7:35 PM 1576 README.md 35 | -a---- 8/25/2017 7:35 PM 36159 VMConfiguration.ps1 36 | -a---- 8/25/2017 7:35 PM 8903 VMConfigurationData.psd1 37 | -a---- 8/25/2017 7:35 PM 6020 VMValidate.test.ps1 38 | ``` 39 | 40 | Change to that directory and continue with the Autolab setup instructions. 41 | 42 | ## Domain Setup 43 | 44 | Domain name: Company.pri 45 | Password for all accounts is: `P@ssw0rd` 46 | 47 | You most likely will want to use one or more of these accounts. 48 | 49 | - The user Art Deco (ArtD) is a member of the Domain Admins group. 50 | - The user April Showers (AprilS) is a member of the Domain Admins group. 51 | - The user Mike Smith (MikeS) is a standard, non-domain admin, user. 52 | 53 | ## Servers 54 | 55 | All servers run an evaluation version of Windows Server 2016 Core: 56 | 57 | - DOM1 Domain Controller 58 | - SRV1 Domain Member server 59 | - SRV2 Domain Member server 60 | - SRV3 Workgroup server 61 | 62 | ## Desktops 63 | 64 | - Win10 - Windows 10 Enterprise (evaluation version) with Remote Server Administration Tools (RSAT) installed. 65 | - PowerShell remoting has been enabled. You will need to run `Update-Help` and manually install items like [VS Code](https://code.visualstudio.com/Download). 66 | 67 | ## Notes 68 | 69 | - All computers are set for Mountain Time with a location of Phoenix, Arizona. 70 | - It is strongly recommended that you run Windows update on the virtual machines, especially the Windows 10 client. 71 | - The PostSetup folder contains a number of optional scripts you might want to run after the Autolab setup is complete. It has a separate [README](./PostSetup/README.md) file. 72 | - To connect to a virtual machine, in a PowerShell prompt you can use the `vmconnect` command: `vmconnect localhost win10` where you specify the name of the Hyper-V host (your local machine) and the name of the virtual machine. 73 | 74 | *Last updated 10 September 2019* 75 | -------------------------------------------------------------------------------- /VMConfiguration.ps1: -------------------------------------------------------------------------------- 1 | <# Notes: 2 | 3 | Authors: Jason Helmick and Melissa (Missy) Januszko 4 | 5 | The bulk of this DC, DHCP, ADCS config is authored by Melissa (Missy) Januszko and Jason Helmick. 6 | Currently on her public DSC hub located here: https://github.com/majst32/DSC_public.git 7 | 8 | Additional contributors of note: Jeff Hicks 9 | 10 | 11 | Disclaimer 12 | 13 | This example code is provided without copyright and AS IS. It is free for you to use and modify. 14 | Note: These demos should not be run as a script. These are the commands that I use in the 15 | demonstrations and would need to be modified for your environment. 16 | 17 | #> 18 | 19 | Configuration AutoLab { 20 | 21 | $LabData = Import-PowerShellDataFile -Path .\*.psd1 22 | $Secure = ConvertTo-SecureString -String "$($labdata.allnodes.labpassword)" -AsPlainText -Force 23 | $credential = New-Object -typename Pscredential -ArgumentList Administrator, $secure 24 | 25 | #region DSC Resources 26 | Import-DSCresource -ModuleName PSDesiredStateConfiguration, 27 | @{ModuleName = "xPSDesiredStateConfiguration"; ModuleVersion = "8.9.0.0"}, 28 | @{ModuleName = "xActiveDirectory"; ModuleVersion = "3.0.0.0"}, 29 | @{ModuleName = "xComputerManagement"; ModuleVersion = "2.0.0.0"}, 30 | @{ModuleName = "xNetworking"; ModuleVersion = "5.7.0.0"}, 31 | @{ModuleName = "xDhcpServer"; ModuleVersion = "2.0.0.0"}, 32 | @{ModuleName = 'xWindowsUpdate'; ModuleVersion = '2.8.0.0'}, 33 | @{ModuleName = 'xPendingReboot'; ModuleVersion = '0.4.0.0'}, 34 | @{ModuleName = 'xADCSDeployment'; ModuleVersion = '1.4.0.0'}, 35 | @{ModuleName = 'xDnsServer'; ModuleVersion = '1.14.0.0'}, 36 | @{ModuleName = 'xWebAdministration'; ModuleVersion = '2.7.0.0'} 37 | 38 | #endregion 39 | #region All Nodes 40 | node $AllNodes.Where( {$true}).NodeName { 41 | #endregion 42 | #region LCM configuration 43 | 44 | LocalConfigurationManager { 45 | RebootNodeIfNeeded = $true 46 | AllowModuleOverwrite = $true 47 | ConfigurationMode = 'ApplyOnly' 48 | } 49 | 50 | #endregion 51 | 52 | #region IPaddress settings 53 | 54 | If (-not [System.String]::IsNullOrEmpty($node.IPAddress)) { 55 | xIPAddress 'PrimaryIPAddress' { 56 | IPAddress = $node.IPAddress 57 | InterfaceAlias = $node.InterfaceAlias 58 | AddressFamily = $node.AddressFamily 59 | } 60 | 61 | If (-not [System.String]::IsNullOrEmpty($node.DefaultGateway)) { 62 | xDefaultGatewayAddress 'PrimaryDefaultGateway' { 63 | InterfaceAlias = $node.InterfaceAlias 64 | Address = $node.DefaultGateway 65 | AddressFamily = $node.AddressFamily 66 | } 67 | } 68 | 69 | If (-not [System.String]::IsNullOrEmpty($node.DnsServerAddress)) { 70 | xDnsServerAddress 'PrimaryDNSClient' { 71 | Address = $node.DnsServerAddress 72 | InterfaceAlias = $node.InterfaceAlias 73 | AddressFamily = $node.AddressFamily 74 | } 75 | } 76 | 77 | If (-not [System.String]::IsNullOrEmpty($node.DnsConnectionSuffix)) { 78 | xDnsConnectionSuffix 'PrimaryConnectionSuffix' { 79 | InterfaceAlias = $node.InterfaceAlias 80 | ConnectionSpecificSuffix = $node.DnsConnectionSuffix 81 | } 82 | } 83 | } #End IF 84 | 85 | #endregion 86 | 87 | #region Firewall Rules 88 | 89 | $LabData = Import-PowerShellDataFile .\*.psd1 90 | $FireWallRules = $labdata.Allnodes.FirewallRuleNames 91 | 92 | foreach ($Rule in $FireWallRules) { 93 | xFirewall $Rule { 94 | Name = $Rule 95 | Enabled = 'True' 96 | } 97 | } #End foreach 98 | 99 | } #end Firewall Rules 100 | #endregion 101 | 102 | #region Domain Controller config 103 | 104 | node $AllNodes.Where( {$_.Role -eq 'DC'}).NodeName { 105 | 106 | $DomainCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ("$($node.DomainName)\$($Credential.UserName)", $Credential.Password) 107 | 108 | xComputer ComputerName { 109 | Name = $Node.NodeName 110 | } 111 | 112 | ## Hack to fix DependsOn with hypens "bug" :( 113 | foreach ($feature in @( 114 | 'DNS', 115 | 'AD-Domain-Services', 116 | 'RSAT-AD-Tools', 117 | 'RSAT-AD-PowerShell', 118 | 'GPMC' 119 | #For Gui, might like 120 | #'RSAT-DNS-Server', 121 | #'RSAT-AD-AdminCenter', 122 | #'RSAT-ADDS-Tools' 123 | 124 | )) { 125 | WindowsFeature $feature.Replace('-', '') { 126 | Ensure = 'Present'; 127 | Name = $feature; 128 | IncludeAllSubFeature = $False; 129 | } 130 | } #End foreach 131 | 132 | xADDomain FirstDC { 133 | DomainName = $Node.DomainName 134 | DomainAdministratorCredential = $Credential 135 | SafemodeAdministratorPassword = $Credential 136 | DatabasePath = $Node.DCDatabasePath 137 | LogPath = $Node.DCLogPath 138 | SysvolPath = $Node.SysvolPath 139 | DependsOn = '[WindowsFeature]ADDomainServices' 140 | } 141 | 142 | #Add OU, Groups, and Users 143 | $OUs = (Get-Content .\AD-OU.json | ConvertFrom-Json) 144 | $Users = (Get-Content .\AD-Users.json | ConvertFrom-Json) 145 | $Groups = (Get-Content .\AD-Group.json | ConvertFrom-Json) 146 | 147 | foreach ($OU in $OUs) { 148 | xADOrganizationalUnit $OU.Name { 149 | Path = $node.DomainDN 150 | Name = $OU.Name 151 | Description = $OU.Description 152 | ProtectedFromAccidentalDeletion = $False 153 | Ensure = "Present" 154 | DependsOn = '[xADDomain]FirstDC' 155 | } 156 | } #OU 157 | 158 | foreach ($user in $Users) { 159 | 160 | xADUser $user.samaccountname { 161 | Ensure = "Present" 162 | Path = $user.distinguishedname.split(",", 2)[1] 163 | DomainName = $node.domainname 164 | Username = $user.samaccountname 165 | GivenName = $user.givenname 166 | Surname = $user.Surname 167 | DisplayName = $user.Displayname 168 | Description = $user.description 169 | Department = $User.department 170 | Enabled = $true 171 | Password = $DomainCredential 172 | DomainAdministratorCredential = $DomainCredential 173 | PasswordNeverExpires = $True 174 | DependsOn = '[xADDomain]FirstDC' 175 | PasswordAuthentication = 'Negotiate' 176 | } 177 | } #user 178 | 179 | Foreach ($group in $Groups) { 180 | xADGroup $group.Name { 181 | GroupName = $group.name 182 | Ensure = 'Present' 183 | Path = $group.distinguishedname.split(",", 2)[1] 184 | Category = $group.GroupCategory 185 | GroupScope = $group.GroupScope 186 | Members = $group.members 187 | DependsOn = '[xADDomain]FirstDC' 188 | } 189 | } 190 | 191 | #prestage Web Server Computer objects 192 | 193 | [string[]]$WebServers = $Null 194 | 195 | foreach ($N in $AllNodes) { 196 | if ($N.Role -eq "Web") { 197 | 198 | $WebServers = $WebServers + "$($N.NodeName)$" 199 | 200 | xADComputer "CompObj_$($N.NodeName)" { 201 | ComputerName = "$($N.NodeName)" 202 | DependsOn = '[xADOrganizationalUnit]Servers' 203 | DisplayName = $N.NodeName 204 | Path = "OU=Servers,$($N.DomainDN)" 205 | Enabled = $True 206 | DomainAdministratorCredential = $DomainCredential 207 | } 208 | } 209 | } 210 | 211 | #add Web Servers group with Web Server computer objects as members 212 | 213 | xADGroup WebServerGroup { 214 | GroupName = 'Web Servers' 215 | GroupScope = 'Global' 216 | DependsOn = '[xADOrganizationalUnit]IT' 217 | Members = $WebServers 218 | Credential = $DomainCredential 219 | Category = 'Security' 220 | Path = "OU=IT,$($Node.DomainDN)" 221 | Ensure = 'Present' 222 | } 223 | 224 | #region DNS 225 | #add a DNS entry for the workgroup server 226 | xDnsRecord SRV3 { 227 | Name = "srv3" 228 | Target = "192.168.3.60" 229 | Type = 'ARecord' 230 | Zone = "company.pri" 231 | Ensure = 'present' 232 | DependsOn = '[xADDomain]FirstDC' 233 | } 234 | 235 | #Add reverse lookup zone 236 | xDNSServerPrimaryZone Reverse { 237 | Name = "3.168.192.in-addr.arpa" 238 | Ensure = "present" 239 | DynamicUpdate = "NonsecureAndSecure" 240 | DependsOn = '[xADDomain]FirstDC' 241 | 242 | } 243 | #endregion 244 | } #end nodes DC 245 | 246 | #endregion 247 | 248 | #region DHCP 249 | node $AllNodes.Where( {$_.Role -eq 'DHCP'}).NodeName { 250 | 251 | foreach ($feature in @( 252 | 'DHCP' 253 | #'RSAT-DHCP' 254 | )) { 255 | 256 | WindowsFeature $feature.Replace('-', '') { 257 | Ensure = 'Present' 258 | Name = $feature 259 | IncludeAllSubFeature = $False; 260 | DependsOn = '[xADDomain]FirstDC' 261 | } 262 | } #End foreach 263 | 264 | xDhcpServerAuthorization 'DhcpServerAuthorization' { 265 | Ensure = 'Present' 266 | DependsOn = '[WindowsFeature]DHCP' 267 | } 268 | 269 | xDhcpServerScope 'DhcpScope' { 270 | Name = $Node.DHCPName 271 | ScopeID = $node.DHCPScopeID 272 | IPStartRange = $Node.DHCPIPStartRange 273 | IPEndRange = $Node.DHCPIPEndRange 274 | SubnetMask = $Node.DHCPSubnetMask 275 | LeaseDuration = $Node.DHCPLeaseDuration 276 | State = $Node.DHCPState 277 | AddressFamily = $Node.DHCPAddressFamily 278 | DependsOn = '[WindowsFeature]DHCP' 279 | } 280 | 281 | xDhcpServerOption 'DhcpOption' { 282 | ScopeID = $Node.DHCPScopeID 283 | DnsServerIPAddress = $Node.DHCPDnsServerIPAddress 284 | Router = $node.DHCPRouter 285 | AddressFamily = $Node.DHCPAddressFamily 286 | DependsOn = '[xDhcpServerScope]DhcpScope' 287 | } 288 | 289 | } #end DHCP Config 290 | #endregion 291 | 292 | #region Web config 293 | node $AllNodes.Where({$_.Role -eq 'Web'}).NodeName { 294 | 295 | foreach ($feature in @( 296 | 'web-Server' 297 | 298 | )) { 299 | WindowsFeature $feature.Replace('-', '') { 300 | Ensure = 'Present' 301 | Name = $feature 302 | IncludeAllSubFeature = $True 303 | } 304 | } 305 | 306 | File SampleService { 307 | Ensure = 'Present' 308 | Type = 'Directory' 309 | DestinationPath = 'C:\MyWebServices' 310 | } 311 | 312 | $asmx = @" 313 | <%@ WebService language = "C#" class = "FirstService" %> 314 | 315 | using System; 316 | using System.Web.Services; 317 | using System.Xml.Serialization; 318 | 319 | [WebService(Namespace="http://localhost/MyWebServices/")] 320 | public class FirstService : WebService 321 | { 322 | [WebMethod] 323 | public int Add(int a, int b) 324 | { 325 | return a + b; 326 | } 327 | 328 | [WebMethod] 329 | public String SayHello() 330 | { 331 | return "Hello World. It is a wonderful day for learning PowerShell."; 332 | } 333 | 334 | } 335 | "@ 336 | File SampleServiceASMX { 337 | Ensure = 'Present' 338 | Type = 'File' 339 | DependsOn = "[File]SampleService" 340 | DestinationPath = "c:\MyWebServices\firstservice.asmx" 341 | Contents = $asmx 342 | Force = $True 343 | } 344 | xWebApplication SampleWebService { 345 | DependsOn = "[File]SampleServiceASMX","[WindowsFeature]webServer" 346 | Website = 'default web site' 347 | PhysicalPath = 'c:\MyWebServices' 348 | Name = 'MyWebServices' 349 | WebAppPool = 'DefaultAppPool' 350 | } 351 | 352 | }#end Web Config 353 | #endregion 354 | 355 | #region DomainJoin config 356 | node $AllNodes.Where( {$_.Role -eq 'DomainJoin'}).NodeName { 357 | 358 | $DomainCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ("$($node.DomainName)\$($Credential.UserName)", $Credential.Password) 359 | 360 | xWaitForADDomain DscForestWait { 361 | DomainName = $Node.DomainName 362 | DomainUserCredential = $DomainCredential 363 | RetryCount = '20' 364 | RetryIntervalSec = '60' 365 | } 366 | 367 | xComputer JoinDC { 368 | Name = $Node.NodeName 369 | DomainName = $Node.DomainName 370 | Credential = $DomainCredential 371 | DependsOn = '[xWaitForADDomain]DSCForestWait' 372 | } 373 | }#end DomianJoin Config 374 | #endregion 375 | 376 | #region RSAT config 377 | node $AllNodes.Where( {$_.Role -eq 'RSAT'}).NodeName { 378 | 379 | # Adds RSAT which is now a Windows Capability in Windows 10 380 | 381 | Script RSAT { 382 | TestScript = { 383 | $packages = Get-WindowsCapability -online -Name Rsat* 384 | if ($packages.state -match "Installed") { 385 | Return $True 386 | } 387 | else { 388 | Return $False 389 | } 390 | } 391 | 392 | GetScript = { 393 | $packages = Get-WindowsCapability -online -Name Rsat* | Select-Object Displayname, State 394 | $installed = $packages.Where({$_.state -eq "Installed"}) 395 | Return @{Result = "$($installed.count)/$($packages.count) RSAT features installed"} 396 | } 397 | 398 | SetScript = { 399 | Get-WindowsCapability -online -Name Rsat* | Where-Object {$_.state -ne "installed"} | Add-WindowsCapability -online 400 | } 401 | } 402 | 403 | #since RSAT is added to the client go ahead and create a Scripts folder 404 | File scripts { 405 | DestinationPath = 'C:\Scripts' 406 | Ensure = 'present' 407 | type = 'directory' 408 | } 409 | 410 | }#end RSAT Config 411 | 412 | #region RDP config 413 | node $AllNodes.Where( {$_.Role -eq 'RDP'}).NodeName { 414 | # Adds RDP support and opens Firewall rules 415 | 416 | Registry RDP { 417 | Key = 'HKLM:\System\ControlSet001\Control\Terminal Server' 418 | ValueName = 'fDenyTSConnections' 419 | ValueType = 'Dword' 420 | ValueData = '0' 421 | Ensure = 'Present' 422 | } 423 | foreach ($Rule in @( 424 | 'RemoteDesktop-UserMode-In-TCP', 425 | 'RemoteDesktop-UserMode-In-UDP', 426 | 'RemoteDesktop-Shadow-In-TCP' 427 | )) { 428 | xFirewall $Rule { 429 | Name = $Rule 430 | Enabled = 'True' 431 | DependsOn = '[Registry]RDP' 432 | } 433 | } # End RDP 434 | } 435 | #endregion 436 | #region ADCS 437 | 438 | node $AllNodes.Where( {$_.Role -eq 'ADCS'}).NodeName { 439 | 440 | ## Hack to fix DependsOn with hypens "bug" :( 441 | foreach ($feature in @( 442 | 'ADCS-Cert-Authority', 443 | 'ADCS-Enroll-Web-Pol', 444 | 'ADCS-Enroll-Web-Svc', 445 | 'ADCS-Web-Enrollment' 446 | # For the GUI version - uncomment the following 447 | #'RSAT-ADCS', 448 | #'RSAT-ADCS-Mgmt' 449 | )) { 450 | 451 | WindowsFeature $feature.Replace('-', '') { 452 | Ensure = 'Present'; 453 | Name = $feature; 454 | IncludeAllSubFeature = $False; 455 | DependsOn = '[xADDomain]FirstDC' 456 | } 457 | } #End foreach 458 | 459 | xWaitForADDomain WaitForADADCSRole { 460 | DomainName = $Node.DomainName 461 | RetryIntervalSec = '30' 462 | RetryCount = '10' 463 | DomainUserCredential = $DomainCredential 464 | DependsOn = '[WindowsFeature]ADCSCertAuthority' 465 | } 466 | 467 | xAdcsCertificationAuthority ADCSConfig { 468 | CAType = $Node.ADCSCAType 469 | Credential = $Credential 470 | CryptoProviderName = $Node.ADCSCryptoProviderName 471 | HashAlgorithmName = $Node.ADCSHashAlgorithmName 472 | KeyLength = $Node.ADCSKeyLength 473 | CACommonName = $Node.CACN 474 | CADistinguishedNameSuffix = $Node.CADNSuffix 475 | DatabaseDirectory = $Node.CADatabasePath 476 | LogDirectory = $Node.CALogPath 477 | ValidityPeriod = $node.ADCSValidityPeriod 478 | ValidityPeriodUnits = $Node.ADCSValidityPeriodUnits 479 | DependsOn = '[xWaitForADDomain]WaitForADADCSRole' 480 | } 481 | 482 | #Add GPO for PKI AutoEnroll 483 | script CreatePKIAEGpo { 484 | Credential = $DomainCredential 485 | TestScript = { 486 | if ((get-gpo -name "PKI AutoEnroll" -domain $Using:Node.DomainName -ErrorAction SilentlyContinue) -eq $Null) { 487 | return $False 488 | } 489 | else { 490 | return $True 491 | } 492 | } 493 | SetScript = { 494 | new-gpo -name "PKI AutoEnroll" -domain $Using:Node.DomainName 495 | } 496 | GetScript = { 497 | $GPO = (get-gpo -name "PKI AutoEnroll" -domain $Using:Node.DomainName) 498 | return @{Result = $($GPO.DisplayName)} 499 | } 500 | DependsOn = '[xWaitForADDomain]WaitForADADCSRole' 501 | } 502 | 503 | script setAEGPRegSetting1 { 504 | Credential = $DomainCredential 505 | TestScript = { 506 | if ((Get-GPRegistryValue -name "PKI AutoEnroll" -domain $Using:Node.DomainName -Key "HKLM\SOFTWARE\Policies\Microsoft\Cryptography\AutoEnrollment" -ValueName "AEPolicy" -ErrorAction SilentlyContinue).Value -eq 7) { 507 | return $True 508 | } 509 | else { 510 | return $False 511 | } 512 | } 513 | SetScript = { 514 | Set-GPRegistryValue -name "PKI AutoEnroll" -domain $Using:Node.DomainName -Key "HKLM\SOFTWARE\Policies\Microsoft\Cryptography\AutoEnrollment" -ValueName "AEPolicy" -Value 7 -Type DWord 515 | } 516 | GetScript = { 517 | $RegVal1 = (Get-GPRegistryValue -name "PKI AutoEnroll" -domain $Using:Node.DomainName -Key "HKLM\SOFTWARE\Policies\Microsoft\Cryptography\AutoEnrollment" -ValueName "AEPolicy") 518 | return @{Result = "$($RegVal1.FullKeyPath)\$($RegVal1.ValueName)\$($RegVal1.Value)"} 519 | } 520 | DependsOn = '[Script]CreatePKIAEGpo' 521 | } 522 | 523 | script setAEGPRegSetting2 { 524 | Credential = $DomainCredential 525 | TestScript = { 526 | if ((Get-GPRegistryValue -name "PKI AutoEnroll" -domain $Using:Node.DomainName -Key "HKLM\SOFTWARE\Policies\Microsoft\Cryptography\AutoEnrollment" -ValueName "OfflineExpirationPercent" -ErrorAction SilentlyContinue).Value -eq 10) { 527 | return $True 528 | } 529 | else { 530 | return $False 531 | } 532 | } 533 | SetScript = { 534 | Set-GPRegistryValue -Name "PKI AutoEnroll" -domain $Using:Node.DomainName -Key "HKLM\SOFTWARE\Policies\Microsoft\Cryptography\AutoEnrollment" -ValueName "OfflineExpirationPercent" -value 10 -Type DWord 535 | } 536 | GetScript = { 537 | $Regval2 = (Get-GPRegistryValue -name "PKI AutoEnroll" -domain $Using:Node.DomainName -Key "HKLM\SOFTWARE\Policies\Microsoft\Cryptography\AutoEnrollment" -ValueName "OfflineExpirationPercent") 538 | return @{Result = "$($RegVal2.FullKeyPath)\$($RegVal2.ValueName)\$($RegVal2.Value)"} 539 | } 540 | DependsOn = '[Script]setAEGPRegSetting1' 541 | 542 | } 543 | 544 | script setAEGPRegSetting3 { 545 | Credential = $DomainCredential 546 | TestScript = { 547 | if ((Get-GPRegistryValue -Name "PKI AutoEnroll" -domain $Using:Node.DomainName -Key "HKLM\SOFTWARE\Policies\Microsoft\Cryptography\AutoEnrollment" -ValueName "OfflineExpirationStoreNames" -ErrorAction SilentlyContinue).value -match "MY") { 548 | return $True 549 | } 550 | else { 551 | return $False 552 | } 553 | } 554 | SetScript = { 555 | Set-GPRegistryValue -Name "PKI AutoEnroll" -domain $Using:Node.DomainName -Key "HKLM\SOFTWARE\Policies\Microsoft\Cryptography\AutoEnrollment" -ValueName "OfflineExpirationStoreNames" -value "MY" -Type String 556 | } 557 | GetScript = { 558 | $RegVal3 = (Get-GPRegistryValue -Name "PKI AutoEnroll" -domain $Using:Node.DomainName -Key "HKLM\SOFTWARE\Policies\Microsoft\Cryptography\AutoEnrollment" -ValueName "OfflineExpirationStoreNames") 559 | return @{Result = "$($RegVal3.FullKeyPath)\$($RegVal3.ValueName)\$($RegVal3.Value)"} 560 | } 561 | DependsOn = '[Script]setAEGPRegSetting2' 562 | } 563 | 564 | Script SetAEGPLink { 565 | Credential = $DomainCredential 566 | TestScript = { 567 | try { 568 | $GPLink = (get-gpo -Name "PKI AutoEnroll" -Domain $Using:Node.DomainName).ID 569 | $GPLinks = (Get-GPInheritance -Domain $Using:Node.DomainName -Target $Using:Node.DomainDN).gpolinks | Where-Object {$_.GpoID -like "*$GPLink*"} 570 | if ($GPLinks.Enabled -eq $True) {return $True} 571 | else {return $False} 572 | } 573 | catch { 574 | Return $False 575 | } 576 | } 577 | SetScript = { 578 | New-GPLink -name "PKI AutoEnroll" -domain $Using:Node.DomainName -Target $Using:Node.DomainDN -LinkEnabled Yes 579 | } 580 | GetScript = { 581 | $GPLink = (get-gpo -Name "PKI AutoEnroll" -Domain $Using:Node.DomainName).ID 582 | $GPLinks = (Get-GPInheritance -Domain $Using:Node.DomainName -Target $Using:Node.DomainDN).gpolinks | Where-Object {$_.GpoID -like "*$GPLink*"} 583 | return @{Result = "$($GPLinks.DisplayName) = $($GPLinks.Enabled)"} 584 | } 585 | DependsOn = '[Script]setAEGPRegSetting3' 586 | } 587 | 588 | #region Create and publish templates 589 | 590 | #Note: The Test section is pure laziness. Future enhancement: test for more than just existence. 591 | script CreateWebServer2Template { 592 | DependsOn = '[xAdcsCertificationAuthority]ADCSConfig' 593 | Credential = $DomainCredential 594 | TestScript = { 595 | try { 596 | $WSTemplate = get-ADObject -Identity "CN=WebServer2,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" -Properties * -ErrorAction Stop 597 | return $True 598 | } 599 | catch { 600 | return $False 601 | } 602 | } 603 | SetScript = { 604 | $WebServerTemplate = @{'flags' = '131649'; 605 | 'msPKI-Cert-Template-OID' = '1.3.6.1.4.1.311.21.8.8211880.1779723.5195193.12600017.10487781.44.7319704.6725493'; 606 | 'msPKI-Certificate-Application-Policy' = '1.3.6.1.5.5.7.3.1'; 607 | 'msPKI-Certificate-Name-Flag' = '268435456'; 608 | 'msPKI-Enrollment-Flag' = '32'; 609 | 'msPKI-Minimal-Key-Size' = '2048'; 610 | 'msPKI-Private-Key-Flag' = '50659328'; 611 | 'msPKI-RA-Signature' = '0'; 612 | 'msPKI-Supersede-Templates' = 'WebServer'; 613 | 'msPKI-Template-Minor-Revision' = '3'; 614 | 'msPKI-Template-Schema-Version' = '2'; 615 | 'pKICriticalExtensions' = '2.5.29.15'; 616 | 'pKIDefaultCSPs' = '2,Microsoft DH SChannel Cryptographic Provider', '1,Microsoft RSA SChannel Cryptographic Provider'; 617 | 'pKIDefaultKeySpec' = '1'; 618 | 'pKIExtendedKeyUsage' = '1.3.6.1.5.5.7.3.1'; 619 | 'pKIMaxIssuingDepth' = '0'; 620 | 'revision' = '100' 621 | } 622 | 623 | 624 | New-ADObject -name "WebServer2" -Type pKICertificateTemplate -Path "CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" -DisplayName WebServer2 -OtherAttributes $WebServerTemplate 625 | $WSOrig = Get-ADObject -Identity "CN=WebServer,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" -Properties * | Select-Object pkiExpirationPeriod, pkiOverlapPeriod, pkiKeyUsage 626 | Get-ADObject -Identity "CN=WebServer2,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" | Set-ADObject -Add @{'pKIKeyUsage' = $WSOrig.pKIKeyUsage; 'pKIExpirationPeriod' = $WSOrig.pKIExpirationPeriod; 'pkiOverlapPeriod' = $WSOrig.pKIOverlapPeriod} 627 | } 628 | GetScript = { 629 | try { 630 | $WS2 = get-ADObject -Identity "CN=WebServer2,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" -Properties * -ErrorAction Stop 631 | return @{Result = $WS2.DistinguishedName} 632 | } 633 | catch { 634 | return @{Result = $Null} 635 | } 636 | } 637 | } 638 | 639 | 640 | #Note: The Test section is pure laziness. Future enhancement: test for more than just existence. 641 | script CreateDSCTemplate { 642 | DependsOn = '[xAdcsCertificationAuthority]ADCSConfig' 643 | Credential = $DomainCredential 644 | TestScript = { 645 | try { 646 | $DSCTemplate = get-ADObject -Identity "CN=DSCTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" -Properties * -ErrorAction Stop 647 | return $True 648 | } 649 | catch { 650 | return $False 651 | } 652 | } 653 | SetScript = { 654 | $DSCTemplateProps = @{'flags' = '131680'; 655 | 'msPKI-Cert-Template-OID' = '1.3.6.1.4.1.311.21.8.16187918.14945684.15749023.11519519.4925321.197.13392998.8282280'; 656 | 'msPKI-Certificate-Application-Policy' = '1.3.6.1.4.1.311.80.1'; 657 | 'msPKI-Certificate-Name-Flag' = '1207959552'; 658 | #'msPKI-Enrollment-Flag'='34'; 659 | 'msPKI-Enrollment-Flag' = '32'; 660 | 'msPKI-Minimal-Key-Size' = '2048'; 661 | 'msPKI-Private-Key-Flag' = '0'; 662 | 'msPKI-RA-Signature' = '0'; 663 | #'msPKI-Supersede-Templates'='WebServer'; 664 | 'msPKI-Template-Minor-Revision' = '3'; 665 | 'msPKI-Template-Schema-Version' = '2'; 666 | 'pKICriticalExtensions' = '2.5.29.15'; 667 | 'pKIDefaultCSPs' = '1,Microsoft RSA SChannel Cryptographic Provider'; 668 | 'pKIDefaultKeySpec' = '1'; 669 | 'pKIExtendedKeyUsage' = '1.3.6.1.4.1.311.80.1'; 670 | 'pKIMaxIssuingDepth' = '0'; 671 | 'revision' = '100' 672 | } 673 | 674 | 675 | New-ADObject -name "DSCTemplate" -Type pKICertificateTemplate -Path "CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" -DisplayName DSCTemplate -OtherAttributes $DSCTemplateProps 676 | $WSOrig = Get-ADObject -Identity "CN=Workstation,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" -Properties * | Select-Object pkiExpirationPeriod, pkiOverlapPeriod, pkiKeyUsage 677 | [byte[]] $WSOrig.pkiKeyUsage = 48 678 | Get-ADObject -Identity "CN=DSCTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" | Set-ADObject -Add @{'pKIKeyUsage' = $WSOrig.pKIKeyUsage; 'pKIExpirationPeriod' = $WSOrig.pKIExpirationPeriod; 'pkiOverlapPeriod' = $WSOrig.pKIOverlapPeriod} 679 | } 680 | GetScript = { 681 | try { 682 | $dsctmpl = get-ADObject -Identity "CN=DSCTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" -Properties * -ErrorAction Stop 683 | return @{Result = $dsctmpl.DistinguishedName} 684 | } 685 | catch { 686 | return @{Result = $Null} 687 | } 688 | } 689 | } 690 | 691 | script PublishWebServerTemplate2 { 692 | DependsOn = '[Script]CreateWebServer2Template' 693 | Credential = $DomainCredential 694 | TestScript = { 695 | $Template = Get-CATemplate | Where-Object {$_.Name -match "WebServer2"} 696 | if ($Template -eq $Null) {return $False} 697 | else {return $True} 698 | } 699 | SetScript = { 700 | add-CATemplate -name "WebServer2" -force 701 | } 702 | GetScript = { 703 | $pubWS2 = Get-CATemplate | Where-Object {$_.Name -match "WebServer2"} 704 | return @{Result = $pubws2.Name} 705 | } 706 | } 707 | 708 | script PublishDSCTemplate { 709 | DependsOn = '[Script]CreateDSCTemplate' 710 | Credential = $DomainCredential 711 | TestScript = { 712 | $Template = Get-CATemplate | Where-Object {$_.Name -match "DSCTemplate"} 713 | if ($Template -eq $Null) {return $False} 714 | else {return $True} 715 | } 716 | SetScript = { 717 | add-CATemplate -name "DSCTemplate" -force 718 | write-verbose -Message ("Publishing Template DSCTemplate...") 719 | } 720 | GetScript = { 721 | $pubDSC = Get-CATemplate | Where-Object {$_.Name -match "DSCTemplate"} 722 | return @{Result = $pubDSC.Name} 723 | } 724 | } 725 | 726 | 727 | #endregion - Create and publish templates 728 | 729 | #region template permissions 730 | #Permission beginning with 0e10... is "Enroll". Permission beginning with "a05b" is autoenroll. 731 | #TODO: Write-Verbose in other script resources. 732 | #TODO: Make $Perms a has table with GUID and permission name. Use name in resource name. 733 | 734 | [string[]]$Perms = "0e10c968-78fb-11d2-90d4-00c04f79dc55", "a05b8cc2-17bc-4802-a710-e7c15ab866a2" 735 | 736 | foreach ($P in $Perms) { 737 | 738 | script "Perms_WebCert_$($P)" { 739 | DependsOn = '[Script]CreateWebServer2Template' 740 | Credential = $DomainCredential 741 | TestScript = { 742 | Import-Module activedirectory -Verbose:$false 743 | $WebServerCertACL = (get-acl "AD:CN=WebServer2,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)").Access | Where-Object {$_.IdentityReference -like "*Web Servers"} 744 | if ($WebServerCertACL -eq $Null) { 745 | write-verbose -message ("Web Servers Group does not have permissions on Web Server template...") 746 | Return $False 747 | } 748 | elseif (($WebServerCertACL.ActiveDirectoryRights -like "*ExtendedRight*") -and ($WebServerCertACL.ObjectType -notcontains $Using:P)) { 749 | write-verbose -message ("Web Servers group has permission, but not the correct permission...") 750 | Return $False 751 | } 752 | else { 753 | write-verbose -message ("ACL on Web Server Template is set correctly for this GUID for Web Servers Group...") 754 | Return $True 755 | } 756 | } 757 | SetScript = { 758 | Import-Module activedirectory -Verbose:$false 759 | $WebServersGroup = get-adgroup -Identity "Web Servers" | Select-Object SID 760 | $EnrollGUID = [GUID]::Parse($Using:P) 761 | $ACL = get-acl "AD:CN=WebServer2,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" 762 | $ACL.AddAccessRule((New-Object System.DirectoryServices.ExtendedRightAccessRule $WebServersGroup.SID, 'Allow', $EnrollGUID, 'None')) 763 | #$ACL.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule $WebServersGroup.SID,'ReadProperty','Allow')) 764 | #$ACL.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule $WebServersGroup.SID,'GenericExecute','Allow')) 765 | set-ACL "AD:CN=WebServer2,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" -AclObject $ACL 766 | write-verbose -Message ("Permissions set for Web Servers Group") 767 | } 768 | GetScript = { 769 | Import-Module activedirectory -Verbose:$false 770 | $WebServerCertACL = (get-acl "AD:CN=WebServer2,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)").Access | Where-Object {$_.IdentityReference -like "*Web Servers"} 771 | if ($WebServerCertACL -ne $Null) { 772 | return @{Result = $WebServerCertACL} 773 | } 774 | else { 775 | Return @{} 776 | } 777 | } 778 | } 779 | 780 | script "Perms_DSCCert_$($P)" { 781 | DependsOn = '[Script]CreateWebServer2Template' 782 | Credential = $DomainCredential 783 | TestScript = { 784 | Import-Module activedirectory -Verbose:$false 785 | $DSCCertACL = (get-acl "AD:CN=DSCTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)").Access | Where-Object {$_.IdentityReference -like "*Domain Computers*"} 786 | if ($DSCCertACL -eq $Null) { 787 | write-verbose -Message ("Domain Computers does not have permissions on DSC template") 788 | Return $False 789 | } 790 | elseif (($DSCCertACL.ActiveDirectoryRights -like "*ExtendedRight*") -and ($DSCCertACL.ObjectType -notcontains $Using:P)) { 791 | write-verbose -Message ("Domain Computers group has permission, but not the correct permission...") 792 | Return $False 793 | } 794 | else { 795 | write-verbose -Message ("ACL on DSC Template is set correctly for this GUID for Domain Computers...") 796 | Return $True 797 | } 798 | } 799 | SetScript = { 800 | Import-Module activedirectory -Verbose:$false 801 | $DomainComputersGroup = get-adgroup -Identity "Domain Computers" | Select-Object SID 802 | $EnrollGUID = [GUID]::Parse($Using:P) 803 | $ACL = get-acl "AD:CN=DSCTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" 804 | $ACL.AddAccessRule((New-Object System.DirectoryServices.ExtendedRightAccessRule $DomainComputersGroup.SID, 'Allow', $EnrollGUID, 'None')) 805 | #$ACL.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule $WebServersGroup.SID,'ReadProperty','Allow')) 806 | #$ACL.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule $WebServersGroup.SID,'GenericExecute','Allow')) 807 | set-ACL "AD:CN=DSCTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)" -AclObject $ACL 808 | write-verbose -Message ("Permissions set for Domain Computers...") 809 | } 810 | GetScript = { 811 | Import-Module activedirectory -Verbose:$false 812 | $DSCCertACL = (get-acl "AD:CN=WebServer2,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$($Using:Node.DomainDN)").Access | Where-Object {$_.IdentityReference -like "*Domain Computers"} 813 | if ($DSCCertACL -ne $Null) { 814 | return @{Result = $DSCCertACL} 815 | } 816 | else { 817 | Return @{} 818 | } 819 | } 820 | } 821 | } 822 | 823 | } #end ADCS Config 824 | 825 | } # End AllNodes 826 | #endregion 827 | 828 | AutoLab -OutputPath .\ -ConfigurationData .\*.psd1 829 | 830 | -------------------------------------------------------------------------------- /VMConfigurationData.psd1: -------------------------------------------------------------------------------- 1 | <# Notes: 2 | 3 | Authors: Jason Helmick, Melissa (Missy) Januszko, and Jeff Hicks 4 | 5 | The bulk of this DC, DHCP, ADCS config is authored by Melissa (Missy) Januszko and Jason Helmick. 6 | Currently on her public DSC hub located here: https://github.com/majst32/DSC_public.git 7 | 8 | 9 | Disclaimer 10 | 11 | This example code is provided without copyright and AS IS. It is free for you to use and modify. 12 | Note: These demos should not be run as a script. These are the commands that I use in the 13 | demonstrations and would need to be modified for your environment. 14 | 15 | #> 16 | 17 | @{ 18 | AllNodes = @( 19 | @{ 20 | NodeName = '*' 21 | 22 | # Lab Password - assigned to Administrator and Users 23 | LabPassword = 'P@ssw0rd' 24 | 25 | # Common networking 26 | InterfaceAlias = 'Ethernet' 27 | DefaultGateway = '192.168.3.1' 28 | SubnetMask = 24 29 | AddressFamily = 'IPv4' 30 | IPNetwork = '192.168.3.0/24' 31 | IPNatName = 'LabNat' 32 | DnsServerAddress = '192.168.3.10' 33 | 34 | # Firewall settings to enable 35 | FirewallRuleNames = @( 36 | 'FPS-ICMP4-ERQ-In', 37 | 'FPS-ICMP6-ERQ-In', 38 | 'FPS-SMB-In-TCP', 39 | 'WMI-WINMGMT-In-TCP-NoScope', 40 | 'WMI-WINMGMT-Out-TCP-NoScope', 41 | 'WMI-WINMGMT-In-TCP', 42 | 'WMI-WINMGMT-Out-TCP' 43 | ) 44 | 45 | # Domain and Domain Controller information 46 | DomainName = "Company.Pri" 47 | DomainDN = "DC=Company,DC=Pri" 48 | DCDatabasePath = "C:\NTDS" 49 | DCLogPath = "C:\NTDS" 50 | SysvolPath = "C:\Sysvol" 51 | PSDscAllowPlainTextPassword = $true 52 | PSDscAllowDomainUser = $true 53 | 54 | # DHCP Server Data 55 | DHCPName = 'LabNet' 56 | DHCPIPStartRange = '192.168.3.200' 57 | DHCPIPEndRange = '192.168.3.250' 58 | DHCPSubnetMask = '255.255.255.0' 59 | DHCPState = 'Active' 60 | DHCPAddressFamily = 'IPv4' 61 | DHCPLeaseDuration = '00:08:00' 62 | DHCPScopeID = '192.168.3.0' 63 | DHCPDnsServerIPAddress = '192.168.3.10' 64 | DHCPRouter = '192.168.3.1' 65 | 66 | # ADCS Certificate Services information 67 | CACN = 'Company.Pri' 68 | CADNSuffix = "C=US,L=Phoenix,S=Arizona,O=Company" 69 | CADatabasePath = "C:\windows\system32\CertLog" 70 | CALogPath = "C:\CA_Logs" 71 | ADCSCAType = 'EnterpriseRootCA' 72 | ADCSCryptoProviderName = 'RSA#Microsoft Software Key Storage Provider' 73 | ADCSHashAlgorithmName = 'SHA256' 74 | ADCSKeyLength = 2048 75 | ADCSValidityPeriod = 'Years' 76 | ADCSValidityPeriodUnits = 2 77 | 78 | # Lability default node settings 79 | Lability_SwitchName = 'LabNet' 80 | Lability_ProcessorCount = 1 81 | Lability_MinimumMemory = 1GB 82 | Lability_MaximumMemory = 3GB 83 | SecureBoot = $false 84 | Lability_Media = '2016_x64_Standard_Core_EN_Eval' 85 | <# 86 | 87 | Id Description 88 | -- ----------- 89 | 2019_x64_Standard_EN_Eval Windows Server 2019 Standard 64bit English Evaluation with Desktop Experience 90 | 2019_x64_Standard_EN_Core_Eval Windows Server 2019 Standard 64bit English Evaluation 91 | 2019_x64_Datacenter_EN_Eval Windows Server 2019 Datacenter 64bit English Evaluation with Desktop Experience 92 | 2019_x64_Datacenter_EN_Core_Eval Windows Server 2019 Datacenter Evaluation in Core mode 93 | 2016_x64_Standard_EN_Eval Windows Server 2016 Standard 64bit English Evaluation 94 | 2016_x64_Standard_Core_EN_Eval Windows Server 2016 Standard Core 64bit English Evaluation 95 | 2016_x64_Datacenter_EN_Eval Windows Server 2016 Datacenter 64bit English Evaluation 96 | 2016_x64_Datacenter_Core_EN_Eval Windows Server 2016 Datacenter Core 64bit English Evaluation 97 | 2016_x64_Standard_Nano_EN_Eval Windows Server 2016 Standard Nano 64bit English Evaluation 98 | 2016_x64_Datacenter_Nano_EN_Eval Windows Server 2016 Datacenter Nano 64bit English Evaluation 99 | 2012R2_x64_Standard_EN_Eval Windows Server 2012 R2 Standard 64bit English Evaluation 100 | 2012R2_x64_Standard_EN_V5_Eval Windows Server 2012 R2 Standard 64bit English Evaluation with WMF 5 101 | 2012R2_x64_Standard_EN_V5_1_Eval Windows Server 2012 R2 Standard 64bit English Evaluation with WMF 5.1 102 | 2012R2_x64_Standard_Core_EN_Eval Windows Server 2012 R2 Standard Core 64bit English Evaluation 103 | 2012R2_x64_Standard_Core_EN_V5_Eval Windows Server 2012 R2 Standard Core 64bit English Evaluation with WMF 5 104 | 2012R2_x64_Standard_Core_EN_V5_1_Eval Windows Server 2012 R2 Standard Core 64bit English Evaluation with WMF 5.1 105 | 2012R2_x64_Datacenter_EN_Eval Windows Server 2012 R2 Datacenter 64bit English Evaluation 106 | 2012R2_x64_Datacenter_EN_V5_Eval Windows Server 2012 R2 Datacenter 64bit English Evaluation with WMF 5 107 | 2012R2_x64_Datacenter_EN_V5_1_Eval Windows Server 2012 R2 Datacenter 64bit English Evaluation with WMF 5.1 108 | 2012R2_x64_Datacenter_Core_EN_Eval Windows Server 2012 R2 Datacenter Core 64bit English Evaluation 109 | 2012R2_x64_Datacenter_Core_EN_V5_Eval Windows Server 2012 R2 Datacenter Core 64bit English Evaluation with WMF 5 110 | 2012R2_x64_Datacenter_Core_EN_V5_1_Eval Windows Server 2012 R2 Datacenter Core 64bit English Evaluation with WMF 5.1 111 | WIN81_x64_Enterprise_EN_Eval Windows 8.1 64bit Enterprise English Evaluation 112 | WIN81_x64_Enterprise_EN_V5_Eval Windows 8.1 64bit Enterprise English Evaluation with WMF 5 113 | WIN81_x64_Enterprise_EN_V5_1_Eval Windows 8.1 64bit Enterprise English Evaluation with WMF 5.1 114 | WIN81_x86_Enterprise_EN_Eval Windows 8.1 32bit Enterprise English Evaluation 115 | WIN81_x86_Enterprise_EN_V5_Eval Windows 8.1 32bit Enterprise English Evaluation with WMF 5 116 | WIN81_x86_Enterprise_EN_V5_1_Eval Windows 8.1 32bit Enterprise English Evaluation with WMF 5.1 117 | WIN10_x64_Enterprise_EN_Eval Windows 10 64bit Enterprise 1903 English Evaluation 118 | WIN10_x86_Enterprise_EN_Eval Windows 10 32bit Enterprise 1903 English Evaluation 119 | WIN10_x64_Enterprise_LTSC_EN_Eval Windows 10 64bit Enterprise LTSC 2019 English Evaluation 120 | WIN10_x86_Enterprise_LTSC_EN_Eval Windows 10 32bit Enterprise LTSC 2019 English Evaluation 121 | #> 122 | }, 123 | 124 | <# Available Roles for computers 125 | DC = Domain Controller 126 | DHCP = Dynamic Host Configuration Protocol 127 | ADCS = Active Directory Certificate SErvices - plus autoenrollment GPO's and DSC and web server certs 128 | Web = Basic web server 129 | RSAT = Remote Server Administration Tools for the client 130 | RDP = enables RDP and opens up required firewall rules 131 | DomainJoin = joins a computer to the domain 132 | #> 133 | @{ 134 | NodeName = 'DOM1' 135 | IPAddress = '192.168.3.10' 136 | Role = @('DC', 'DHCP', 'ADCS') 137 | Lability_BootOrder = 10 138 | Lability_BootDelay = 60 # Number of seconds to delay before others 139 | Lability_timeZone = 'US Mountain Standard Time' #[System.TimeZoneInfo]::GetSystemTimeZones() 140 | Lability_Media = '2016_x64_Standard_Core_EN_Eval' 141 | Lability_MinimumMemory = 2GB 142 | Lability_ProcessorCount = 2 143 | CustomBootStrap = @' 144 | # This must be set to handle larger .mof files 145 | Set-Item -path wsman:\localhost\maxenvelopesize -value 1000 146 | '@ 147 | }, 148 | 149 | @{ 150 | NodeName = 'SRV1' 151 | IPAddress = '192.168.3.50' 152 | #Role = 'DomainJoin' # example of multiple roles @('DomainJoin', 'Web') 153 | Role = @('DomainJoin') 154 | Lability_BootOrder = 20 155 | Lability_timeZone = 'US Mountain Standard Time' #[System.TimeZoneInfo]::GetSystemTimeZones() 156 | Lability_Media = '2016_x64_Standard_Core_EN_Eval' 157 | }, 158 | 159 | @{ 160 | NodeName = 'SRV2' 161 | IPAddress = '192.168.3.51' 162 | #Role = 'DomainJoin' # example of multiple roles @('DomainJoin', 'Web') 163 | Role = @('DomainJoin','Web') 164 | Lability_BootOrder = 20 165 | Lability_timeZone = 'US Mountain Standard Time' #[System.TimeZoneInfo]::GetSystemTimeZones() 166 | Lability_Media = '2016_x64_Standard_Core_EN_Eval' 167 | }, 168 | 169 | @{ 170 | NodeName = 'SRV3' 171 | IPAddress = '192.168.3.60' 172 | Lability_BootOrder = 20 173 | Lability_Media = '2019_x64_Standard_EN_Core_Eval' 174 | Lability_ProcessorCount = 1 175 | Lability_StartupMemory = 1GB 176 | }, 177 | 178 | @{ 179 | NodeName = 'WIN10' 180 | IPAddress = '192.168.3.100' 181 | Role = @('domainJoin', 'RDP','RSAT') 182 | Lability_ProcessorCount = 2 183 | Lability_MinimumMemory = 2GB 184 | Lability_Media = 'WIN10_x64_Enterprise_EN_Eval' 185 | Lability_BootOrder = 20 186 | Lability_timeZone = 'US Mountain Standard Time' #[System.TimeZoneInfo]::GetSystemTimeZones() 187 | Lability_Resource = @() 188 | CustomBootStrap = '' 189 | } 190 | #> 191 | 192 | ); 193 | NonNodeData = @{ 194 | Lability = @{ 195 | # EnvironmentPrefix = 'PS-GUI-' # this will prefix the VM names 196 | Network = @( # Virtual switch in Hyper-V 197 | @{ Name = 'LabNet'; Type = 'Internal'; NetAdapterName = 'Ethernet'; AllowManagementOS = $true;} 198 | ); 199 | DSCResource = @( 200 | ## Download published version from the PowerShell Gallery or Github 201 | @{ Name = 'xActiveDirectory'; RequiredVersion="3.0.0.0"; Provider = 'PSGallery'; }, 202 | @{ Name = 'xComputerManagement'; RequiredVersion = '4.1.0.0'; Provider = 'PSGallery'; }, 203 | @{ Name = 'xNetworking'; RequiredVersion = '5.7.0.0'; Provider = 'PSGallery'; }, 204 | @{ Name = 'xDhcpServer'; RequiredVersion = '2.0.0.0'; Provider = 'PSGallery'; }, 205 | @{ Name = 'xWindowsUpdate' ; RequiredVersion = '2.8.0.0'; Provider = 'PSGallery';}, 206 | @{ Name = 'xPSDesiredStateConfiguration'; RequiredVersion = '8.9.0.0'; }, 207 | @{ Name = 'xPendingReboot'; RequiredVersion = '0.4.0.0'; Provider = 'PSGallery';}, 208 | @{ Name = 'xADCSDeployment'; RequiredVersion = '1.4.0.0'; Provider = 'PSGallery';}, 209 | @{ Name = 'xDnsServer';RequiredVersion = "1.14.0.0";Provider = 'PSGallery';}, 210 | @{ Name = 'xWebAdministration';RequiredVersion = '2.7.0.0';Provider = 'PSGallery'} 211 | 212 | ); 213 | Resource = @( 214 | @{ 215 | Id = 'Win10RSAT' 216 | Filename = 'WindowsTH-RSAT_WS2016-x64.msu' 217 | Uri = 'https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS2016-x64.msu' 218 | Expand = $false 219 | #DestinationPath = '\software' # Default is resources folder 220 | } 221 | ); 222 | 223 | }; 224 | }; 225 | }; 226 | -------------------------------------------------------------------------------- /VMValidate.test.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.1 2 | 3 | #test if VM setup is complete 4 | 5 | $LabData = Import-PowerShellDataFile -Path .\*.psd1 6 | $Secure = ConvertTo-SecureString -String "$($labdata.allnodes.labpassword)" -AsPlainText -Force 7 | $Domain = "company" 8 | $cred = New-Object PSCredential "$Domain\Administrator", $Secure 9 | $wgcred = New-Object PSCredential "administrator", $secure 10 | 11 | #define an array to hold all of the PSSessions 12 | $all = @() 13 | Describe DOM1 { 14 | 15 | $dc = New-PSSession -VMName DOM1 -Credential $cred -ErrorAction SilentlyContinue 16 | $all+=$dc 17 | #set error action preference to suppress all error messsages which would be normal while configurations are converging 18 | if ($dc) { 19 | Invoke-Command { $errorActionPreference = 'silentlyContinue'} -session $dc 20 | } 21 | 22 | It "[DOM1] Should belong to the COMPANY domain" { 23 | $test = Invoke-Command {Get-Ciminstance -ClassName win32_computersystem -property domain} -session $DC 24 | $test.domain | Should Be "company.pri" 25 | } 26 | 27 | #test for features 28 | $feat = Invoke-Command { Get-WindowsFeature | Where-Object installed} -session $dc 29 | $needed = 'AD-Domain-Services', 'DNS', 'RSAT-AD-Tools', 30 | 'RSAT-AD-PowerShell' 31 | foreach ($item in $needed) { 32 | It "[DOM1] Should have feature $item installed" { 33 | $feat.Name -contains $item | Should Be "True" 34 | } 35 | } 36 | 37 | It "[DOM1] Should have an IP address of 192.168.3.10" { 38 | $i = Invoke-command -ScriptBlock { Get-NetIPAddress -interfacealias 'Ethernet' -AddressFamily IPv4} -Session $dc 39 | $i.ipv4Address | should be '192.168.3.10' 40 | } 41 | 42 | It "[DOM1] Should have a domain name of $domain" { 43 | $r = Invoke-command { Get-ADDomain -ErrorAction SilentlyContinue } -session $dc 44 | $r.name | should Be $domain 45 | } 46 | 47 | $OUs = Invoke-command { Get-ADorganizationalUnit -filter * -ErrorAction SilentlyContinue} -session $dc 48 | $needed = 'IT', 'Dev', 'Marketing', 'Sales', 'Accounting', 'JEA_Operators', 'Servers' 49 | foreach ($item in $needed) { 50 | It "[DOM1] Should have organizational unit $item" { 51 | $OUs.name -contains $item | Should Be "True" 52 | } 53 | } 54 | $groups = Invoke-Command { Get-ADGroup -filter * -ErrorAction SilentlyContinue} -session $dc 55 | $target = "IT", "Sales", "Marketing", "Accounting", "JEA Operators" 56 | foreach ($item in $target) { 57 | 58 | It "[DOM1] Should have a group called $item" { 59 | $groups.Name -contains $item | Should Be "True" 60 | } 61 | 62 | } 63 | 64 | $users = Invoke-Command { Get-AdUser -filter * -ErrorAction SilentlyContinue} -session $dc 65 | It "[DOM1] Should have at least 15 user accounts" { 66 | $users.count | should BeGreaterThan 15 67 | } 68 | 69 | $admins = Invoke-Command {Get-ADGroupMember "Domain Admins"-ErrorAction SilentlyContinue} -session $dc 70 | It "[DOM1] ArtD is a member of Domain Admins" { 71 | $admins.name -contains 'artd' 72 | } 73 | 74 | It "[DOM1] AprilS is a member of Domain Admins" { 75 | $admins.name -contains 'aprils' 76 | } 77 | 78 | $computer = Invoke-Command { Get-ADComputer -filter * -ErrorAction SilentlyContinue} -session $dc 79 | It "[DOM1] Should have a computer account for WIN10" { 80 | $computer.name -contains "Win10" | Should Be "True" 81 | } 82 | 83 | It "[DOM1] Should have a computer account for SRV1" { 84 | $computer.name -contains "SRV1" | Should Be "True" 85 | } 86 | 87 | It "[DOM1] Should have a computer account for SRV2" { 88 | $computer.name -contains "SRV2" | Should Be "True" 89 | } 90 | 91 | $rec = Invoke-command {Resolve-DNSName Srv3.company.pri} -session $DC 92 | It "[DOM1] Should have a DNS record for SRV3.COMPANY.PRI" { 93 | $rec.name | Should be 'srv3.company.pri' 94 | $rec.ipaddress | Should be '192.168.3.60' 95 | } 96 | 97 | It "[DOM1] Should be running Windows Server 2016" { 98 | $test = Invoke-Command {Get-Ciminstance -ClassName win32_operatingsystem -property caption} -session $dc 99 | $test.caption | Should BeLike '*2016*' 100 | } 101 | } #DOM1 102 | 103 | Describe SRV1 { 104 | $SRV1 = New-PSSession -VMName SRV1 -Credential $cred -ErrorAction SilentlyContinue 105 | $all+=$srv1 106 | It "[SRV1] Should belong to the COMPANY domain" { 107 | $test = Invoke-Command {Get-Ciminstance -ClassName win32_computersystem -property domain} -session $SRV1 108 | $test.domain | Should Be "company.pri" 109 | } 110 | 111 | It "[SRV1] Should have an IP address of 192.168.3.50" { 112 | $i = Invoke-command -ScriptBlock { Get-NetIPAddress -interfacealias 'Ethernet' -AddressFamily IPv4} -Session $SRV1 113 | $i.ipv4Address | should be '192.168.3.50' 114 | } 115 | $dns = Invoke-Command {Get-DnsClientServerAddress -InterfaceAlias ethernet -AddressFamily IPv4} -session $SRV1 116 | It "[SRV1] Should have a DNS server configuration of 192.168.3.10" { 117 | $dns.ServerAddresses -contains '192.168.3.10' | Should Be "True" 118 | } 119 | 120 | It "[SRV1] Should be running Windows Server 2016" { 121 | $test = Invoke-Command {Get-Ciminstance -ClassName win32_operatingsystem -property caption} -session $srv1 122 | $test.caption | Should BeLike '*2016*' 123 | } 124 | } #SRV1 125 | 126 | Describe SRV2 { 127 | $SRV2 = New-PSSession -VMName SRV2 -Credential $cred -ErrorAction SilentlyContinue 128 | $all+=$srv2 129 | It "[SRV2] Should belong to the COMPANY domain" { 130 | $test = Invoke-Command {Get-Ciminstance -ClassName win32_computersystem -property domain} -session $SRV2 131 | $test.domain | Should Be "company.pri" 132 | } 133 | 134 | It "[SRV2] Should have an IP address of 192.168.3.51" { 135 | $i = Invoke-command -ScriptBlock { Get-NetIPAddress -interfacealias 'Ethernet' -AddressFamily IPv4} -Session $SRV2 136 | $i.ipv4Address | should be '192.168.3.51' 137 | } 138 | $dns = Invoke-Command {Get-DnsClientServerAddress -InterfaceAlias ethernet -AddressFamily IPv4} -session $SRV2 139 | It "[SRV2] Should have a DNS server configuration of 192.168.3.10" { 140 | $dns.ServerAddresses -contains '192.168.3.10' | Should Be "True" 141 | } 142 | 143 | It "[SRV2] Should have the Web-Server feature installed" { 144 | $feature = Invoke-command { Get-WindowsFeature -Name web-server} -session $SRV2 145 | $feature.Installed | Should be $True 146 | } 147 | 148 | It "[SRV2] Should have a sample web service file" { 149 | $file = Invoke-Command { Get-item C:\MyWebServices\firstservice.asmx} -session $SRV2 150 | $file.name | should be 'firstservice.asmx' 151 | } 152 | It "[SRV2] Should have a WebApplication called MyWebServices" { 153 | $app = Invoke-command {Get-WebApplication -Name MyWebServices} -session $SRV2 154 | $app.path | Should be "/MyWebServices" 155 | $app.physicalpath | should be "c:\MyWebServices" 156 | } 157 | 158 | It "[SRV2] Should be running Windows Server 2016" { 159 | $test = Invoke-Command {Get-Ciminstance -ClassName win32_operatingsystem -property caption} -session $srv2 160 | $test.caption | Should BeLike '*2016*' 161 | } 162 | } #SRV2 163 | 164 | 165 | Describe SRV3 { 166 | 167 | $srv3 = New-PSSession -VMName SRV3 -Credential $wgCred -ErrorAction Stop 168 | $all += $srv3 169 | 170 | It "[SRV3] Should respond to WSMan requests" { 171 | $srv3.Computername | Should Be 'SRV3' 172 | } 173 | 174 | It "[SRV3] Should have an IP address of 192.168.3.60" { 175 | $r = Invoke-Command { Get-NetIPAddress -InterfaceAlias Ethernet -AddressFamily IPv4} -session $srv3 176 | $r.IPv4Address | Should Be '192.168.3.60' 177 | } 178 | 179 | It "[SRV3] Should belong to the Workgroup domain" { 180 | $sys = Invoke-Command { Get-CimInstance Win32_computersystem} -session $srv3 181 | $sys.Domain | Should Be "Workgroup" 182 | } 183 | 184 | It "[SRV3] Should be running Windows Server 2019" { 185 | $test = Invoke-Command {Get-Ciminstance -ClassName win32_operatingsystem -property caption} -session $srv3 186 | $test.caption | Should BeLike '*2019*' 187 | } 188 | 189 | } 190 | #> 191 | 192 | Describe Win10 { 193 | 194 | $cl = New-PSSession -VMName Win10 -Credential $cred -ErrorAction SilentlyContinue 195 | $all += $cl 196 | It "[WIN10] Should belong to the COMPANY domain" { 197 | $test = Invoke-Command {Get-Ciminstance -ClassName win32_computersystem -property domain} -session $cl 198 | $test.domain | Should Be "company.pri" 199 | } 200 | 201 | It "[WIN10] Should be running Windows 10 Enterprise version 18362" { 202 | $test = Invoke-Command {Get-Ciminstance -ClassName win32_operatingsystem -property version,caption} -session $cl 203 | $test.Version | Should Be '10.0.18362' 204 | $test.caption | Should BeLike "*Enterprise*" 205 | } 206 | 207 | It "[Win10] Should have an IP address of 192.168.3.100" { 208 | $i = Invoke-command -ScriptBlock { Get-NetIPAddress -interfacealias 'Ethernet' -AddressFamily IPv4} -session $cl 209 | $i.ipv4Address | should be '192.168.3.100' 210 | } 211 | 212 | $dns = Invoke-Command {Get-DnsClientServerAddress -InterfaceAlias ethernet -AddressFamily IPv4} -session $cl 213 | It "[Win10] Should have a DNS server configuration of 192.168.3.10" { 214 | $dns.ServerAddresses -contains '192.168.3.10' | Should Be "True" 215 | } 216 | 217 | It "[Win10] Should have RSAT installed" { 218 | $pkg = Invoke-Command {Get-WindowsCapability -online -name *rsat*} -session $cl 219 | 220 | # write-host ($pkg | Select-object Name,Displayname,State | format-list | Out-String) -ForegroundColor cyan 221 | $pkg.State| should match "Installed" 222 | 223 | } 224 | } #client 225 | 226 | $all | Remove-PSSession 227 | --------------------------------------------------------------------------------