github.com/cloudfoundry-incubator/stembuild@v0.0.0-20211223202937-5b61d62226c6/modules/BOSH.Sysprep/BOSH.Sysprep.Tests.ps1 (about) 1 Remove-Module -Name BOSH.Sysprep -ErrorAction Ignore 2 Import-Module ./BOSH.Sysprep.psm1 3 4 #We remove WinRM as it imports BOSH.Utils 5 Remove-Module -Name BOSH.WinRM -ErrorAction Ignore 6 7 Remove-Module -Name BOSH.Utils -ErrorAction Ignore 8 Import-Module ../BOSH.Utils/BOSH.Utils.psm1 9 10 11 function New-TempDir 12 { 13 $parent = [System.IO.Path]::GetTempPath() 14 [string]$name = [System.Guid]::NewGuid() 15 (New-Item -ItemType Directory -Path (Join-Path $parent $name)).FullName 16 } 17 18 Describe "Remove-WasPassProcessed" { 19 Context "when no answer file path is provided" { 20 It "throws" { 21 { Remove-WasPassProcessed } | Should -Throw "Cannot bind argument to parameter 'Path' because it is an empty string." 22 } 23 } 24 25 Context "when provided a nonexistent answer file" { 26 It "throws" { 27 { Remove-WasPassProcessed -AnswerFilePath "C:\IDoNotExist.xml" } | Should -Throw "Answer file C:\IDoNotExist.xml does not exist" 28 } 29 } 30 31 Context "when provided an answer file with invalid XML" { 32 BeforeEach { 33 $BadAnswerXmlDirectory = (New-TempDir) 34 $BadAnswerXmlPath = Join-Path $BadAnswerXmlDirectory "bad.xml" 35 36 "bad xml" | Out-File $BadAnswerXmlPath 37 } 38 39 AfterEach { 40 Remove-Item -Recurse -Force $BadAnswerXmlDirectory 41 } 42 43 It "throws" { 44 { Remove-WasPassProcessed -AnswerFilePath $BadAnswerXmlPath } | Should -Throw "Cannot convert value `"bad xml`" to type `"System.Xml.XmlDocument`". Error: `"The specified node cannot be inserted as the valid child of this node, because the specified node is the wrong type.`"" 45 } 46 } 47 48 Context "when provided an answer file which contains valid XML and a 'specialize' block containing 'Microsoft-Windows-Deployment' which has the attribute 'wasPassProcessed'" { 49 BeforeEach { 50 $answerFileDirectory = (New-TempDir) 51 $answerFilePath = Join-Path $answerFileDirectory "validanswer.xml" 52 53 "<unattend> 54 <settings pass=`"specialize`" wasPassProcessed=`"true`"> 55 <component name=`"Microsoft-Windows-Deployment`"> 56 </component> 57 </settings> 58 <settings pass=`"oobeSystem`" wasPassProcessed=`"false`"> 59 <component name=`"Microsoft-Windows-OOBE`"> 60 </component> 61 </settings> 62 <settings pass=`"foo`"> 63 <component name=`"Microsoft-Windows-Foo`"> 64 </component> 65 </settings> 66 <settings pass=`"bar`" wasPassProcessed=`"true`"> 67 <component name=`"Microsoft-Windows-Deployment`"> 68 </component> 69 </settings> 70 </unattend>" | Out-File $answerFilePath 71 } 72 73 AfterEach { 74 Remove-Item -Recurse -Force $answerFileDirectory 75 } 76 77 It "removes the attribute regardless of its value" { 78 Remove-WasPassProcessed $answerFilePath 79 $content = [xml](Get-Content $answerFilePath) 80 81 foreach ($specializeBlock in $content.unattend.settings) 82 { 83 $specializeBlock.HasAttribute("wasPassProcessed") | Should Be False 84 } 85 } 86 } 87 88 Context "when processing several 'specialize' blocks which have the attribute 'wasProcessed = true'" { 89 BeforeEach { 90 $GoodAnswerFileDirectory = (New-TempDir) 91 $GoodAnswerFilePath = Join-Path $GoodAnswerFileDirectory "validanswer.xml" 92 93 "<unattend> 94 <settings pass=`"specialize`"> 95 <component name=`"Microsoft-Windows-Deployment`" processorArchitecture=`"x86`" publicKeyToken=`"31bf3856ad364e35`" language=`"neutral`" versionScope=`"nonSxS`" xmlns:wcm=`"http://schemas.microsoft.com/WMIConfig/2002/State`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`"> 96 </component> 97 </settings> 98 </unattend>" | Out-File $GoodAnswerFilePath 99 } 100 101 AfterEach { 102 Remove-Item -Recurse -Force $GoodAnswerFileDirectory 103 } 104 105 It "does nothing" { 106 Remove-WasPassProcessed $GoodAnswerFilePath 107 $content = [xml](Get-Content $GoodAnswerFilePath) 108 $mwdBlock = ((($content.unattend.settings|where { $_.pass -eq 'specialize' }).component|where { $_.name -eq "Microsoft-Windows-Deployment" })) 109 $specializeBlock = $mwdBlock.ParentNode 110 $specializeBlock.hasAttribute("wasPassProcessed") | Should Be False 111 } 112 } 113 } 114 115 Describe "Remove-UserAccounts" { 116 Context "when no answer file path is provided" { 117 It "throws" { 118 { Remove-UserAccounts } | Should -Throw "Cannot bind argument to parameter 'Path' because it is an empty string." 119 } 120 } 121 122 Context "when provided a nonexistent answer file" { 123 It "throws" { 124 { Remove-UserAccounts -AnswerFilePath "C:\IDoNotExist.xml" } | Should -Throw "Answer file C:\IDoNotExist.xml does not exist" 125 } 126 } 127 128 Context "when provided an answer file with invalid XML" { 129 BeforeEach { 130 $BadAnswerXmlDirectory = (New-TempDir) 131 $BadAnswerXmlPath = Join-Path $BadAnswerXmlDirectory "bad.xml" 132 133 "bad xml" | Out-File $BadAnswerXmlPath 134 } 135 136 AfterEach { 137 Remove-Item -Recurse -Force $BadAnswerXmlDirectory 138 } 139 140 It "throws" { 141 { Remove-UserAccounts -AnswerFilePath $BadAnswerXmlPath } | Should -Throw "Cannot convert value `"bad xml`" to type `"System.Xml.XmlDocument`". Error: `"The specified node cannot be inserted as the valid child of this node, because the specified node is the wrong type.`"" 142 } 143 } 144 145 Context "when provided an answer file containing XML but without an 'oobeSystem' block containing 'Microsoft-Windows-Shell-Setup'" { 146 BeforeEach { 147 $noOOBEXmlDirectory = (New-TempDir) 148 $noOOBEXmlPath = Join-Path $noOOBEXmlDirectory "invalidanswer.xml" 149 150 "<unattend> 151 <settings pass=`"Not-oobeSystem`"> 152 <component name=`"Microsoft-Windows-Shell-Setup`"> 153 </component> 154 </settings> 155 </unattend>" | Out-File $noOOBEXmlPath 156 } 157 158 AfterEach { 159 Remove-Item -Recurse -Force $noOOBEXmlDirectory 160 } 161 162 It "does nothing" { 163 { Remove-UserAccounts -AnswerFilePath $noOOBEXmlPath } | Should -Throw "Could not locate oobeSystem XML block. You may not be running this function on an answer file." 164 } 165 } 166 167 Context "when provided a valid XML answer file containing an 'oobeSystem' block which contains a 'Microsoft-Windows-Shell-Setup' block which DOES NOT contain a 'UserAccounts' block" { 168 BeforeEach { 169 $GoodAnswerFileDirectory = (New-TempDir) 170 $GoodAnswerFilePath = Join-Path $GoodAnswerFileDirectory "validanswer.xml" 171 172 "<unattend> 173 <settings pass=`"oobeSystem`"> 174 <component name=`"Microsoft-Windows-Shell-Setup`"> 175 </component> 176 </settings> 177 </unattend>" | Out-File $GoodAnswerFilePath 178 } 179 180 AfterEach { 181 Remove-Item -Recurse -Force $GoodAnswerFileDirectory 182 } 183 184 It "does nothing" { 185 Remove-UserAccounts -AnswerFilePath $GoodAnswerFilePath 186 $content = [xml](Get-Content $GoodAnswerFilePath) 187 $userAccountsBlock = (($content.unattend.settings|where { $_.pass -eq 'oobeSystem' }).component|where { $_.name -eq "Microsoft-Windows-Shell-Setup" }).UserAccounts 188 $userAccountsBlock | Should Be $Null 189 } 190 } 191 192 Context "when provided a valid XML answer file containing an 'oobeSystem' block which contains a 'Microsoft-Windows-Shell-Setup' block which contains a 'UserAccounts' block" { 193 BeforeEach { 194 $GoodAnswerFileDirectory = (New-TempDir) 195 $GoodAnswerFilePath = Join-Path $GoodAnswerFileDirectory "validanswer.xml" 196 197 "<unattend> 198 <settings pass=`"oobeSystem`"> 199 <component name=`"Microsoft-Windows-Shell-Setup`"> 200 <UserAccounts> 201 <AdministratorPassword> 202 foo 203 </AdministratorPassword> 204 </UserAccounts> 205 </component> 206 </settings> 207 </unattend>" | Out-File $GoodAnswerFilePath 208 } 209 210 AfterEach { 211 Remove-Item -Recurse -Force $GoodAnswerFileDirectory 212 } 213 214 It "Removes the UserAccounts xml block" { 215 Remove-UserAccounts -AnswerFilePath $GoodAnswerFilePath 216 $content = [xml](Get-Content $GoodAnswerFilePath) 217 $userAccountsBlock = (($content.unattend.settings|where { $_.pass -eq 'oobeSystem' }).component|where { $_.name -eq "Microsoft-Windows-Shell-Setup" }).UserAccounts 218 $userAccountsBlock | Should Be $Null 219 } 220 } 221 } 222 223 Describe "Invoke-Sysprep" { 224 Context "when not provided an IaaS" { 225 It "throws" { 226 { Invoke-Sysprep -OsVersion "windows2012R2" } | Should -Throw "Provide the IaaS this stemcell will be used for" 227 } 228 } 229 230 Context "when provided an invalid Iaas" { 231 It "throws" { 232 { Invoke-Sysprep -IaaS "OpenShift" -SkipLGPO -OsVersion "windows2012R2" } | Should -Throw "Invalid IaaS 'OpenShift' supported platforms are: AWS, Azure, GCP and Vsphere" 233 } 234 } 235 236 Context "handles OS version differences" { 237 BeforeEach { 238 Mock Get-ItemProperty { } -ModuleName BOSH.Sysprep 239 Mock Stop-Computer { } -ModuleName BOSH.Sysprep 240 Mock Start-Process { } -ModuleName BOSH.Sysprep 241 Mock Test-Path { $True } -ParameterFilter { $Path -cmatch "C:\\Windows\\LGPO.exe" } -ModuleName BOSH.Sysprep 242 243 Mock Write-Log { } -ModuleName BOSH.Sysprep 244 245 Mock Allow-NTPSync { } -ModuleName BOSH.Sysprep 246 Mock Enable-LocalSecurityPolicy { } -ModuleName BOSH.Sysprep 247 Mock Update-AWS2012R2Config { } -ModuleName BOSH.Sysprep 248 Mock Update-AWS2016Config { } -ModuleName BOSH.Sysprep 249 Mock Enable-AWS2016Sysprep { } -ModuleName BOSH.Sysprep 250 251 Mock Create-Unattend { } -ModuleName BOSH.Sysprep 252 Mock Create-Unattend-GCP { } -ModuleName BOSH.Sysprep 253 254 function GCESysprep {} 255 Mock GCESysprep {} -ModuleName BOSH.Sysprep 256 257 Mock Invoke-Expression { } -ModuleName BOSH.Sysprep 258 } 259 260 Context "for AWS" { 261 It "handles Windows 2012R2" { 262 Mock Get-OSVersion { "windows2012R2" } -ModuleName BOSH.SysPrep 263 264 { Invoke-Sysprep -Iaas "aws" } | Should -Not -Throw 265 266 Assert-MockCalled Update-AWS2012R2Config -Times 1 -Scope It -ModuleName BOSH.Sysprep 267 Assert-MockCalled Start-Process -Times 1 -Scope It -ParameterFilter { $FilePath -eq "C:\Program Files\Amazon\Ec2ConfigService\Ec2Config.exe" -and $ArgumentList -eq "-sysprep" } -ModuleName BOSH.Sysprep 268 269 Assert-MockCalled Get-OSVersion -Times 1 -Scope It -ModuleName BOSH.Sysprep 270 271 Assert-MockCalled Update-AWS2016Config -Times 0 -Scope It -ModuleName BOSH.Sysprep 272 Assert-MockCalled Enable-AWS2016Sysprep -Times 0 -Scope It -ModuleName BOSH.Sysprep 273 } 274 275 It "handles Windows 1709" { 276 Mock Get-OSVersion { "windows2016" } -ModuleName BOSH.Sysprep 277 278 { Invoke-Sysprep -Iaas "aws" } | Should -Not -Throw 279 280 Assert-MockCalled Update-AWS2016Config -Times 1 -Scope It -ModuleName BOSH.Sysprep 281 Assert-MockCalled Enable-AWS2016Sysprep -Times 1 -Scope It -ModuleName BOSH.Sysprep 282 283 Assert-MockCalled Get-OSVersion -Times 1 -Scope It -ModuleName BOSH.Sysprep 284 285 Assert-MockCalled Update-AWS2012R2Config -Times 0 -Scope It -ModuleName BOSH.Sysprep 286 Assert-MockCalled Start-Process -Times 0 -Scope It -ParameterFilter { $FilePath -eq "C:\Program Files\Amazon\Ec2ConfigService\Ec2Config.exe" -and $ArgumentList -eq "-sysprep" } -ModuleName BOSH.Sysprep 287 } 288 289 It "handles Windows 1803" { 290 Mock Get-OSVersion { "windows1803" } -ModuleName Bosh.Sysprep 291 292 { Invoke-Sysprep -Iaas "aws" } | Should -Not -Throw 293 294 Assert-MockCalled Update-AWS2016Config -Times 1 -Scope It -ModuleName BOSH.Sysprep 295 Assert-MockCalled Enable-AWS2016Sysprep -Times 1 -Scope It -ModuleName BOSH.Sysprep 296 297 Assert-MockCalled Get-OSVersion -Times 1 -Scope It -ModuleName BOSH.Sysprep 298 299 Assert-MockCalled Update-AWS2012R2Config -Times 0 -Scope It -ModuleName BOSH.Sysprep 300 Assert-MockCalled Start-Process -Times 0 -Scope It -ParameterFilter { $FilePath -eq "C:\Program Files\Amazon\Ec2ConfigService\Ec2Config.exe" -and $ArgumentList -eq "-sysprep" } -ModuleName BOSH.Sysprep 301 } 302 303 It "handles other OS'" { 304 Mock Get-OSVersion { Throw "invalid OS detected" } -ModuleName Bosh.Sysprep 305 306 { Invoke-Sysprep -Iaas "aws" } | Should -Throw "invalid OS detected" 307 308 Assert-MockCalled Get-OSVersion -Times 1 -Scope It -ModuleName BOSH.Sysprep 309 310 Assert-MockCalled Update-AWS2016Config -Times 0 -Scope It -ModuleName BOSH.Sysprep 311 Assert-MockCalled Enable-AWS2016Sysprep -Times 0 -Scope It -ModuleName BOSH.Sysprep 312 Assert-MockCalled Update-AWS2012R2Config -Times 0 -Scope It -ModuleName BOSH.Sysprep 313 Assert-MockCalled Start-Process -Times 0 -Scope It -ParameterFilter { $FilePath -eq "C:\Program Files\Amazon\Ec2ConfigService\Ec2Config.exe" -and $ArgumentList -eq "-sysprep" } -ModuleName BOSH.Sysprep 314 } 315 } 316 Context "for vSphere" { 317 It "creates an unattend file" { 318 Invoke-Sysprep -IaaS vsphere 319 320 Assert-MockCalled Create-Unattend -ModuleName BOSH.Sysprep -ParameterFilter { 321 $NewPassword -ne $null ` 322 -and $ProductKey -ne $null ` 323 -and $Organization -ne $null ` 324 -and $Owner -ne $null 325 } 326 } 327 328 It "calls windows sysprep and shuts down" { 329 330 Invoke-Sysprep -IaaS vsphere 331 332 Assert-MockCalled Invoke-Expression -Scope It -ModuleName BOSH.Sysprep -ParameterFilter { 333 $Command -like '*sysprep.exe*/shutdown*' } 334 } 335 It "calls windows sysprep with unattend file" { 336 337 Invoke-Sysprep -IaaS vsphere 338 339 Assert-MockCalled Invoke-Expression -Scope It -ModuleName BOSH.Sysprep -ParameterFilter { 340 $Command -like '*sysprep.exe*/unattend*unattend.xml*' } 341 } 342 It "calls windows sysprep with correct parameters to generalize the image, and next boot with oobe" { 343 344 Invoke-Sysprep -IaaS vsphere 345 346 Assert-MockCalled Invoke-Expression -Scope It -ModuleName BOSH.Sysprep -ParameterFilter { 347 $Command -like '*sysprep.exe*/generalize*/oobe*' } 348 } 349 } 350 351 Context "for GCP" { 352 It "creates an unattend file" { 353 Invoke-Sysprep -IaaS gcp 354 355 Assert-MockCalled Create-Unattend-GCP -ModuleName BOSH.Sysprep 356 } 357 } 358 359 Context "for LGPO" { 360 # We use AWS as the IaaS as it is the only IaaS that is fully mocked right now 361 # We don't want to trigger Sysprep during our test 362 It "handles Windows 2012R2" { 363 Mock Get-OSVersion { "windows2012R2" } -ModuleName Bosh.Sysprep 364 $ExpectedPath = Join-Path $PSScriptRoot "cis-merge-2012R2" 365 { Invoke-Sysprep -Iaas "aws" } | Should -Not -Throw 366 367 Assert-MockCalled Enable-LocalSecurityPolicy -ParameterFilter { $PolicySource -eq $ExpectedPath } -Times 1 -Scope It -ModuleName BOSH.Sysprep 368 } 369 370 It "handles Windows 1709" { 371 Mock Get-OSVersion { "windows2016" } -ModuleName Bosh.Sysprep 372 373 { Invoke-Sysprep -Iaas "aws" } | Should -Not -Throw 374 375 Assert-MockCalled Enable-LocalSecurityPolicy -Times 0 -Scope It -ModuleName BOSH.Sysprep 376 } 377 378 It "handles Windows 1803" { 379 Mock Get-OSVersion { "windows1803" } -ModuleName Bosh.Sysprep 380 $ExpectedPath = Join-Path $PSScriptRoot "cis-merge-1803" 381 { Invoke-Sysprep -Iaas "aws" } | Should -Not -Throw 382 383 Assert-MockCalled Enable-LocalSecurityPolicy -ParameterFilter { $PolicySource -eq $ExpectedPath } -Times 1 -Scope It -ModuleName BOSH.Sysprep 384 } 385 386 It "handles Windows 2019" { 387 Mock Get-OSVersion { "windows2019" } -ModuleName Bosh.Sysprep 388 $ExpectedPath = Join-Path $PSScriptRoot "cis-merge-2019" 389 { Invoke-Sysprep -Iaas "aws" } | Should -Not -Throw 390 391 Assert-MockCalled Enable-LocalSecurityPolicy -ParameterFilter { $PolicySource -eq $ExpectedPath } -Times 1 -Scope It -ModuleName BOSH.Sysprep 392 } 393 394 It "skips local policy update if -SkipLGPO is set" { 395 Mock Get-OSVersion { "windows2012R2" } -ModuleName Bosh.Sysprep 396 397 { Invoke-Sysprep -Iaas "aws" -SkipLGPO } | Should -Not -Throw 398 399 Assert-MockCalled Enable-LocalSecurityPolicy -Times 0 -Scope It -ModuleName BOSH.Sysprep 400 } 401 402 It "handles all other OS'" { 403 Mock Get-OSVersion { Throw "invalid OS detected" } -ModuleName Bosh.Sysprep 404 405 { Invoke-Sysprep -Iaas "aws" } | Should -Throw "invalid OS detected" 406 407 Assert-MockCalled Enable-LocalSecurityPolicy -Times 0 -Scope It -ModuleName BOSH.Sysprep 408 } 409 } 410 } 411 } 412 413 Describe "ModifyInfFile" { 414 BeforeEach { 415 $InfFileDirectory = (New-TempDir) 416 $InfFilePath = Join-Path $InfFileDirectory "infFile.inf" 417 418 "something=something`nkey=blah`nx=x" | Out-File $InfFilePath 419 } 420 421 AfterEach { 422 Remove-Item -Recurse -Force $InfFileDirectory 423 } 424 425 It "modifies the inf key" { 426 ModifyInfFile -InfFilePath $InfFilePath -KeyName 'key' -KeyValue 'value' 427 428 $actual = (Get-Content $InfFilePath) -join "`n" 429 430 $actual | Should Be "something=something`nkey=value`nx=x" 431 } 432 } 433 434 Describe "Create-Unattend" { 435 BeforeEach { 436 $UnattendDestination = (New-TempDir) 437 $NewPassword = "NewPassword" 438 $ProductKey = "ProductKey" 439 $Organization = "Organization" 440 $Owner = "Owner" 441 } 442 443 AfterEach { 444 Remove-Item -Recurse -Force $UnattendDestination 445 } 446 447 It "places the generated Unattend file in the specified directory" { 448 { 449 Create-Unattend -UnattendDestination $UnattendDestination ` 450 -NewPassword $NewPassword ` 451 -ProductKey $ProductKey ` 452 -Organization $Organization ` 453 -Owner $Owner 454 } | Should -Not -Throw 455 Test-Path (Join-Path $UnattendDestination "unattend.xml") | Should Be $True 456 } 457 458 It "handles special chars in passwords" { 459 $NewPassword = "<!--Password123" 460 { 461 Create-Unattend -UnattendDestination $UnattendDestination ` 462 -NewPassword $NewPassword ` 463 -ProductKey $ProductKey ` 464 -Organization $Organization ` 465 -Owner $Owner 466 } | Should -Not -Throw 467 468 $unattendPath = (Join-Path $UnattendDestination "unattend.xml") 469 [xml]$unattendXML = Get-Content -Path $unattendPath 470 471 $encodedPassword = $unattendXML.unattend.settings.component.UserAccounts.AdministratorPassword.Value 472 [system.text.encoding]::Unicode.GetString([system.convert]::Frombase64string($encodedPassword)) | Should Be ($NewPassword + "AdministratorPassword") 473 } 474 475 It "handles null for NewPassword" { 476 { 477 Create-Unattend -UnattendDestination $UnattendDestination ` 478 -NewPassword $null ` 479 -ProductKey $ProductKey ` 480 -Organization $Organization ` 481 -Owner $Owner 482 } | Should -Not -Throw 483 484 $unattendPath = (Join-Path $UnattendDestination "unattend.xml") 485 [xml]$unattendXML = Get-Content -Path $unattendPath 486 487 $ns = New-Object System.Xml.XmlNamespaceManager($unattendXML.NameTable) 488 $ns.AddNamespace("ns", $unattendXML.DocumentElement.NamespaceURI) 489 $unattendXML.SelectSingleNode("//ns:UserAccounts", $ns) | Should Be $Null 490 } 491 492 It "handles empty string for NewPassword" { 493 { 494 Create-Unattend -UnattendDestination $UnattendDestination ` 495 -NewPassword "" ` 496 -ProductKey $ProductKey ` 497 -Organization $Organization ` 498 -Owner $Owner 499 } | Should -Not -Throw 500 501 $unattendPath = (Join-Path $UnattendDestination "unattend.xml") 502 [xml]$unattendXML = Get-Content -Path $unattendPath 503 504 $ns = New-Object System.Xml.XmlNamespaceManager($unattendXML.NameTable) 505 $ns.AddNamespace("ns", $unattendXML.DocumentElement.NamespaceURI) 506 $unattendXML.SelectSingleNode("//ns:UserAccounts", $ns) | Should Be $Null 507 } 508 509 It "handles not providing NewPassword" { 510 { 511 Create-Unattend -UnattendDestination $UnattendDestination ` 512 -ProductKey $ProductKey ` 513 -Organization $Organization ` 514 -Owner $Owner 515 } | Should -Not -Throw 516 517 $unattendPath = (Join-Path $UnattendDestination "unattend.xml") 518 [xml]$unattendXML = Get-Content -Path $unattendPath 519 520 $ns = New-Object System.Xml.XmlNamespaceManager($unattendXML.NameTable) 521 $ns.AddNamespace("ns", $unattendXML.DocumentElement.NamespaceURI) 522 $unattendXML.SelectSingleNode("//ns:UserAccounts", $ns) | Should Be $Null 523 } 524 525 Context "the generated Unattend file" { 526 BeforeEach { 527 { 528 Create-Unattend -UnattendDestination $UnattendDestination ` 529 -NewPassword $NewPassword ` 530 -ProductKey $ProductKey ` 531 -Organization $Organization ` 532 -Owner $Owner 533 } | Should -Not -Throw 534 $unattendPath = (Join-Path $UnattendDestination "unattend.xml") 535 [xml]$unattendXML = Get-Content -Path $unattendPath 536 $ns = New-Object System.Xml.XmlNamespaceManager($unattendXML.NameTable) 537 $ns.AddNamespace("ns", $unattendXML.DocumentElement.NamespaceURI) 538 } 539 540 It "contains a Product Key, Organization, and Owner when Product Key is provided" { 541 $unattendXML.SelectSingleNode("//ns:ProductKey", $ns).'#text' | Should Be $ProductKey 542 $unattendXML.SelectSingleNode("//ns:RegisteredOrganization", $ns).'#text' | Should Be $Organization 543 $unattendXML.SelectSingleNode("//ns:RegisteredOwner", $ns).'#text' | Should Be $Owner 544 } 545 546 It "when Product Key is not provided, there is no Product Key, Organization, or Owner" { 547 { 548 Create-Unattend -UnattendDestination $UnattendDestination ` 549 -NewPassword $NewPassword 550 } | Should -Not -Throw 551 [xml]$unattendXML = Get-Content -Path $unattendPath 552 $ns = New-Object System.Xml.XmlNamespaceManager($unattendXML.NameTable) 553 $ns.AddNamespace("ns", $unattendXML.DocumentElement.NamespaceURI) 554 $unattendXML.SelectSingleNode("//ns:ProductKey", $ns).'#text' | Should Be $Null 555 } 556 557 It "when Product Key is not provided: Organization and Owner are not removed" { 558 { 559 Create-Unattend -UnattendDestination $UnattendDestination ` 560 -NewPassword $NewPassword -Organization 'Test-Org' -Owner 'Test-Owner' 561 } | Should -Not -Throw 562 [xml]$unattendXML = Get-Content -Path $unattendPath 563 $ns = New-Object System.Xml.XmlNamespaceManager($unattendXML.NameTable) 564 $ns.AddNamespace("ns", $unattendXML.DocumentElement.NamespaceURI) 565 $unattendXML.SelectSingleNode("//ns:RegisteredOrganization", $ns).'#text' | Should Be 'Test-Org' 566 $unattendXML.SelectSingleNode("//ns:RegisteredOwner", $ns).'#text' | Should Be 'Test-Owner' 567 } 568 } 569 } 570 571 Describe "Create-Unattend-GCP" { 572 BeforeEach { 573 $UnattendDestination = (New-TempDir) 574 } 575 AfterEach { 576 Remove-Item -Recurse -Force $UnattendDestination 577 } 578 579 It "places the generated Unattend file in the specified directory" { 580 { 581 Create-Unattend-GCP -UnattendDestination $UnattendDestination 582 } | Should -Not -Throw 583 584 Test-Path (Join-Path $UnattendDestination "unattended.xml") | Should Be $True 585 } 586 587 It "sets the timezone to UTC" { 588 { 589 Create-Unattend-GCP -UnattendDestination $UnattendDestination 590 } | Should -Not -Throw 591 592 $unattendPath = (Join-Path $UnattendDestination "unattended.xml") 593 [xml]$unattendXML = Get-Content -Path $unattendPath 594 $timezones = $unattendXML.unattend.settings.component.TimeZone | 595 Where-Object { $_ -ne $null } 596 597 $timezones.count | Should -BeGreaterOrEqual 1 598 $timezones | ForEach-Object { $_ | Should -Be "UTC" } 599 } 600 } 601 602 Describe "Allow-NTPSync" { 603 It "Sets registry keys that allow the clock to be synced when delta is greater than 15 hours" { 604 $oldMaxNegPhaseCorrection = (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config").'MaxNegPhaseCorrection' 605 $oldMaxPosPhaseCorrection = (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config").'MaxPosPhaseCorrection' 606 607 { Allow-NTPSync } | Should -Not -Throw 608 609 $maxValue = [uint32]::MaxValue 610 (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config").'MaxNegPhaseCorrection' | Should Be $maxValue 611 (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config").'MaxPosPhaseCorrection' | Should Be $maxValue 612 613 Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config" -Name 'MaxNegPhaseCorrection' -Value $oldMaxNegPhaseCorrection 614 Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config" -Name 'MaxPosPhaseCorrection' -Value $oldMaxPosPhaseCorrection 615 } 616 } 617 618 Remove-Module -Name BOSH.Sysprep -ErrorAction Ignore 619 Remove-Module -Name BOSH.Utils -ErrorAction Ignore