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