diff --git a/DscResources/CommonResourceHelper.psm1 b/DscResources/CommonResourceHelper.psm1 index f1f9d3a..6854e31 100644 --- a/DscResources/CommonResourceHelper.psm1 +++ b/DscResources/CommonResourceHelper.psm1 @@ -8,23 +8,26 @@ function Test-IsNanoServer [CmdletBinding()] param () - $isNanoServer = $false - - if (Test-CommandExists -Name 'Get-ComputerInfo') + $serverLevelsRegKey = 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels' + + if (Test-Path -Path $serverLevelsRegKey) { - $computerInfo = Get-ComputerInfo -ErrorAction 'SilentlyContinue' - - if ($null -ne $computerInfo) + $serverLevels = Get-ItemProperty -Path $serverLevelsRegKey + + if ($serverLevels.NanoServer -eq 1) { - $computerIsServer = 'Server' -ieq $computerInfo.OsProductType - - if ($computerIsServer) - { - $isNanoServer = 'NanoServer' -ieq $computerInfo.OsServerLevel - } + $isNanoServer = $true + } + else + { + $isNanoServer = $false } } - + else + { + $isNanoServer = $false + } + return $isNanoServer } diff --git a/DscResources/MSFT_MsiPackage/MSFT_MsiPackage.psm1 b/DscResources/MSFT_MsiPackage/MSFT_MsiPackage.psm1 index 145ce96..1444b6b 100644 --- a/DscResources/MSFT_MsiPackage/MSFT_MsiPackage.psm1 +++ b/DscResources/MSFT_MsiPackage/MSFT_MsiPackage.psm1 @@ -269,7 +269,7 @@ function Set-TargetResource } finally { - if ($null -ne $responseStream) + if ((Test-Path -Path variable:responseStream) -and ($null -ne $responseStream)) { Close-Stream -Stream $responseStream } diff --git a/README.md b/README.md index 81effe2..d3f6564 100644 --- a/README.md +++ b/README.md @@ -593,9 +593,16 @@ The following parameters will be the same for each process in the set: ### Unreleased -* Fixes issue where MsiPackage Integration tests fail to make an HTTPS - connection if Strong Crypto for .NET is not enabled. Fixes +* Fixes issue where MsiPackage Integration tests fail if the test HttpListener + fails to start. Moves the test HttpListener objects to dynamically assigned, + higher numbered ports to avoid conflicts with other services, and also checks + to ensure that the ports are available before using them. Adds checks to + ensure that no outstanding HTTP server jobs are running before attempting to + setup a new one. Also adds additional instrumentation to make it easier to + troubleshoot issues with the test HttpListener objects in the future. + Specifically fixes [Issue #142](https://github.com/PowerShell/PSDscResources/issues/142) +* Improved speed of Test-IsNanoServer function ### 2.11.0.0 diff --git a/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 b/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 index 78bc29e..6916d9b 100644 --- a/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 +++ b/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 @@ -13,647 +13,650 @@ if ($PSVersionTable.PSVersion -lt [Version] '5.1') return } -# Import CommonTestHelper -$testsFolderFilePath = Split-Path -Path $PSScriptRoot -Parent -$testHelperFolderFilePath = Join-Path -Path $testsFolderFilePath -ChildPath 'TestHelpers' -$commonTestHelperFilePath = Join-Path -Path $testHelperFolderFilePath -ChildPath 'CommonTestHelper.psm1' -Import-Module -Name $commonTestHelperFilePath +Describe 'MsiPackage End to End Tests' { + BeforeAll { + # Import CommonTestHelper + $testsFolderFilePath = Split-Path $PSScriptRoot -Parent + $testHelperFolderFilePath = Join-Path -Path $testsFolderFilePath -ChildPath 'TestHelpers' + $commonTestHelperFilePath = Join-Path -Path $testHelperFolderFilePath -ChildPath 'CommonTestHelper.psm1' + Import-Module -Name $commonTestHelperFilePath -try -{ - # Make sure strong crypto is enabled in .NET for HTTPS tests - $originalStrongCryptoSettings = Enable-StrongCryptoForDotNetFour + $script:testEnvironment = Enter-DscResourceTestEnvironment ` + -DscResourceModuleName 'PSDscResources' ` + -DscResourceName 'MSFT_MsiPackage' ` + -TestType 'Integration' - Describe 'MsiPackage End to End Tests' { - BeforeAll { - # Import CommonTestHelper - $testsFolderFilePath = Split-Path $PSScriptRoot -Parent - $testHelperFolderFilePath = Join-Path -Path $testsFolderFilePath -ChildPath 'TestHelpers' - $commonTestHelperFilePath = Join-Path -Path $testHelperFolderFilePath -ChildPath 'CommonTestHelper.psm1' - Import-Module -Name $commonTestHelperFilePath + # Import MsiPackage resource module for Test-TargetResource + $moduleRootFilePath = Split-Path -Path $testsFolderFilePath -Parent + $dscResourcesFolderFilePath = Join-Path -Path $moduleRootFilePath -ChildPath 'DscResources' + $msiPackageResourceFolderFilePath = Join-Path -Path $dscResourcesFolderFilePath -ChildPath 'MSFT_MsiPackage' + $msiPackageResourceModuleFilePath = Join-Path -Path $msiPackageResourceFolderFilePath -ChildPath 'MSFT_MsiPackage.psm1' + Import-Module -Name $msiPackageResourceModuleFilePath -Force - $script:testEnvironment = Enter-DscResourceTestEnvironment ` - -DscResourceModuleName 'PSDscResources' ` - -DscResourceName 'MSFT_MsiPackage' ` - -TestType 'Integration' + # Import the MsiPackage test helper + $packageTestHelperFilePath = Join-Path -Path $testHelperFolderFilePath -ChildPath 'MSFT_MsiPackageResource.TestHelper.psm1' + Import-Module -Name $packageTestHelperFilePath -Force - # Import MsiPackage resource module for Test-TargetResource - $moduleRootFilePath = Split-Path -Path $testsFolderFilePath -Parent - $dscResourcesFolderFilePath = Join-Path -Path $moduleRootFilePath -ChildPath 'DscResources' - $msiPackageResourceFolderFilePath = Join-Path -Path $dscResourcesFolderFilePath -ChildPath 'MSFT_MsiPackage' - $msiPackageResourceModuleFilePath = Join-Path -Path $msiPackageResourceFolderFilePath -ChildPath 'MSFT_MsiPackage.psm1' - Import-Module -Name $msiPackageResourceModuleFilePath -Force + # Set up the paths to the test configurations + $script:configurationFilePathNoOptionalParameters = Join-Path -Path $PSScriptRoot -ChildPath 'MSFT_MsiPackage_NoOptionalParameters' + $script:configurationFilePathLogPath = Join-Path -Path $PSScriptRoot -ChildPath 'MSFT_MsiPackage_LogPath' - # Import the MsiPackage test helper - $packageTestHelperFilePath = Join-Path -Path $testHelperFolderFilePath -ChildPath 'MSFT_MsiPackageResource.TestHelper.psm1' - Import-Module -Name $packageTestHelperFilePath -Force + <# + This log file is used to log messages from the mock server which is important for debugging since + most of the work of the mock server is done within a separate process. + #> + $script:logFile = Join-Path -Path $PSScriptRoot -ChildPath 'PackageTestLogFile.txt' + $script:environmentInIncorrectStateErrorMessage = 'The current environment is not in the expected state for this test - either something was setup incorrectly on your machine or a previous test failed - results after this may be invalid.' - # Set up the paths to the test configurations - $script:configurationFilePathNoOptionalParameters = Join-Path -Path $PSScriptRoot -ChildPath 'MSFT_MsiPackage_NoOptionalParameters' - $script:configurationFilePathLogPath = Join-Path -Path $PSScriptRoot -ChildPath 'MSFT_MsiPackage_LogPath' + $script:msiName = 'DSCSetupProject.msi' + $script:msiLocation = Join-Path -Path $TestDrive -ChildPath $script:msiName + $script:packageId = '{deadbeef-80c6-41e6-a1b9-8bdb8a05027f}' + + $null = New-TestMsi -DestinationPath $script:msiLocation + + $script:testHttpPort = Get-UnusedTcpPort + $script:testHttpsPort = Get-UnusedTcpPort -ExcludePorts @($script:testHttpPort) + + # Clear the log file + 'Beginning integration tests' > $script:logFile + } + + AfterAll { + # Remove the test MSI if it is still installed + if (Test-PackageInstalledById -ProductId $script:packageId) + { + $null = Start-Process -FilePath 'msiexec.exe' -ArgumentList @("/x$script:packageId", '/passive') -Wait + $null = Start-Sleep -Seconds 1 + } + + if (Test-PackageInstalledById -ProductId $script:packageId) + { + throw 'Test package could not be uninstalled after running all tests. It may cause errors in subsequent test runs.' + } + + Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment + } + + Context 'Uninstall package that is already Absent' { + $configurationName = 'RemoveAbsentMsiPackage' + + $msiPackageParameters = @{ + ProductId = $script:packageId + Path = $script:msiLocation + Ensure = 'Absent' + } + + It 'Should return True from Test-TargetResource with the same parameters before configuration' { + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult | Should Be $true + + if ($testTargetResourceInitialResult -ne $true) + { + <# + Not throwing an error here since the tests should still run correctly after this, + we just want to notify the user that the tests aren't necessarily testing what + they should be + #> + Write-Error -Message $script:environmentInIncorrectStateErrorMessage + } + } + + It 'Package should not exist on the machine before configuration is run' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $false + } + + It 'Should compile and run configuration' { + { + . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @msiPackageParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + } + + It 'Should return True from Test-TargetResource with the same parameters after configuration' { + MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true + } + + It 'Package should not exist on the machine' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $false + } + } + + Context 'Install package that is not installed yet' { + $configurationName = 'InstallMsiPackage' + + $msiPackageParameters = @{ + ProductId = $script:packageId + Path = $script:msiLocation + Ensure = 'Present' + } + + It 'Should return False from Test-TargetResource with the same parameters before configuration' { + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult | Should Be $false + + if ($testTargetResourceInitialResult -ne $false) + { + <# + Not throwing an error here since the tests should still run correctly after this, + we just want to notify the user that the tests aren't necessarily testing what + they should be + #> + Write-Error -Message $script:environmentInIncorrectStateErrorMessage + } + } + + It 'Package should not exist on the machine before configuration is run' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $false + } + + It 'Should compile and run configuration' { + { + . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @msiPackageParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + } + + It 'Should return True from Test-TargetResource with the same parameters after configuration' { + MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true + } + + It 'Package should exist on the machine' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $true + } + } + + Context 'Install package that is already installed' { + $configurationName = 'InstallExistingMsiPackage' + + $msiPackageParameters = @{ + ProductId = $script:packageId + Path = $script:msiLocation + Ensure = 'Present' + } + + It 'Should return True from Test-TargetResource with the same parameters before configuration' { + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult | Should Be $true + + if ($testTargetResourceInitialResult -ne $true) + { + <# + Not throwing an error here since the tests should still run correctly after this, + we just want to notify the user that the tests aren't necessarily testing what + they should be + #> + Write-Error -Message $script:environmentInIncorrectStateErrorMessage + } + } + + It 'Package should exist on the machine before configuration is run' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $true + } + + It 'Should compile and run configuration' { + { + . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @msiPackageParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + } + + It 'Should return True from Test-TargetResource with the same parameters after configuration' { + MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true + } + + It 'Package should exist on the machine' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $true + } + } + + Context 'Uninstall package that is installed' { + $configurationName = 'UninstallExistingMsiPackage' + + $msiPackageParameters = @{ + ProductId = $script:packageId + Path = $script:msiLocation + Ensure = 'Absent' + } + + It 'Should return False from Test-TargetResource with the same parameters before configuration' { + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult | Should Be $false + + if ($testTargetResourceInitialResult -ne $false) + { + <# + Not throwing an error here since the tests should still run correctly after this, + we just want to notify the user that the tests aren't necessarily testing what + they should be + #> + Write-Error -Message $script:environmentInIncorrectStateErrorMessage + } + } + + It 'Package should exist on the machine before configuration is run' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $true + } + + It 'Should compile and run configuration' { + { + . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @msiPackageParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + } + + It 'Should return True from Test-TargetResource with the same parameters after configuration' { + MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true + } + + It 'Package should not exist on the machine' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $false + } + } + + Context 'Install package that is not installed and write to specified log file' { + $configurationName = 'InstallWithLogFile' + + $logPath = Join-Path -Path $TestDrive -ChildPath 'TestMsiLog.txt' + + if (Test-Path -Path $logPath) + { + Remove-Item -Path $logPath -Force + } + + $msiPackageParameters = @{ + ProductId = $script:packageId + Path = $script:msiLocation + Ensure = 'Present' + LogPath = $logPath + } + + It 'Should return False from Test-TargetResource with the same parameters before configuration' { + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult | Should Be $false + + if ($testTargetResourceInitialResult -ne $false) + { + <# + Not throwing an error here since the tests should still run correctly after this, + we just want to notify the user that the tests aren't necessarily testing what + they should be + #> + Write-Error -Message $script:environmentInIncorrectStateErrorMessage + } + } + + It 'Package should not exist on the machine before configuration is run' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $false + } + + It 'Should compile and run configuration' { + { + . $script:configurationFilePathLogPath -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @msiPackageParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + } + + It 'Should return True from Test-TargetResource with the same parameters after configuration' { + MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true + } + + It 'Should have created the log file' { + Test-Path -Path $logPath | Should Be $true + } + + It 'Package should exist on the machine' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $true + } + } + + Context 'Uninstall package that is installed and write to specified log file' { + $configurationName = 'InstallWithLogFile' + + $logPath = Join-Path -Path $TestDrive -ChildPath 'TestMsiLog.txt' + + if (Test-Path -Path $logPath) + { + Remove-Item -Path $logPath -Force + } + + $msiPackageParameters = @{ + ProductId = $script:packageId + Path = $script:msiLocation + Ensure = 'Absent' + LogPath = $logPath + } + + It 'Should return False from Test-TargetResource with the same parameters before configuration' { + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult | Should Be $false + + if ($testTargetResourceInitialResult -ne $false) + { + <# + Not throwing an error here since the tests should still run correctly after this, + we just want to notify the user that the tests aren't necessarily testing what + they should be + #> + Write-Error -Message $script:environmentInIncorrectStateErrorMessage + } + } + + It 'Package should exist on the machine before configuration is run' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $true + } + + It 'Should compile and run configuration' { + { + . $script:configurationFilePathLogPath -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @msiPackageParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + } + + It 'Should return True from Test-TargetResource with the same parameters after configuration' { + MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true + } + + It 'Should have created the log file' { + Test-Path -Path $logPath | Should Be $true + } + + It 'Package should not exist on the machine' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $false + } + } + + Context 'Install package from HTTP Url' { + $configurationName = 'UninstallExistingMsiPackageFromHttp' + + $uriBuilder = [System.UriBuilder]::new('http', 'localhost', $script:testHttpPort) + $uriBuilder.Path = 'package.msi' + $msiUrl = $uriBuilder.Uri.AbsoluteUri + + $fileServerStarted = $null + $job = $null + + $msiPackageParameters = @{ + ProductId = $script:packageId + Path = $msiUrl + Ensure = 'Present' + } + + It 'Should return False from Test-TargetResource with the same parameters before configuration' { + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult | Should Be $false + + if ($testTargetResourceInitialResult -ne $false) + { + <# + Not throwing an error here since the tests should still run correctly after this, + we just want to notify the user that the tests aren't necessarily testing what + they should be + #> + Write-Error -Message $script:environmentInIncorrectStateErrorMessage + } + } + + It 'Package should not exist on the machine before configuration is run' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $false + } + + try + { + # Make sure no existing HTTP(S) test servers are running + Stop-EveryTestServerInstance + + $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false -HttpPort $script:testHttpPort -HttpsPort $script:testHttpsPort + $fileServerStarted = $serverResult.FileServerStarted + $job = $serverResult.Job + + $fileServerStarted.WaitOne(30000) + + It 'Should compile and run configuration' { + { + . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @msiPackageParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + } + } + finally + { <# - This log file is used to log messages from the mock server which is important for debugging since - most of the work of the mock server is done within a separate process. + This must be called after Start-Server to ensure the listening port is closed, + otherwise subsequent tests may fail until the machine is rebooted. #> - $script:logFile = Join-Path -Path $PSScriptRoot -ChildPath 'PackageTestLogFile.txt' - $script:environmentInIncorrectStateErrorMessage = 'The current environment is not in the expected state for this test - either something was setup incorrectly on your machine or a previous test failed - results after this may be invalid.' - - $script:msiName = 'DSCSetupProject.msi' - $script:msiLocation = Join-Path -Path $TestDrive -ChildPath $script:msiName - - $script:packageId = '{deadbeef-80c6-41e6-a1b9-8bdb8a05027f}' - - $null = New-TestMsi -DestinationPath $script:msiLocation - - # Clear the log file - 'Beginning integration tests' > $script:logFile + Stop-Server -FileServerStarted $fileServerStarted -Job $job } - AfterAll { - # Remove the test MSI if it is still installed - if (Test-PackageInstalledById -ProductId $script:packageId) - { - $null = Start-Process -FilePath 'msiexec.exe' -ArgumentList @("/x$script:packageId", '/passive') -Wait - $null = Start-Sleep -Seconds 1 - } - - if (Test-PackageInstalledById -ProductId $script:packageId) - { - throw 'Test package could not be uninstalled after running all tests. It may cause errors in subsequent test runs.' - } - - Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment + It 'Should return True from Test-TargetResource with the same parameters after configuration' { + MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true } - Context 'Uninstall package that is already Absent' { - $configurationName = 'RemoveAbsentMsiPackage' + It 'Package should exist on the machine' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $true + } + } - $msiPackageParameters = @{ - ProductId = $script:packageId - Path = $script:msiLocation - Ensure = 'Absent' + Context 'Uninstall Msi package from HTTP Url' { + $configurationName = 'InstallMsiPackageFromHttp' + + $uriBuilder = [System.UriBuilder]::new('http', 'localhost', $script:testHttpPort) + $uriBuilder.Path = 'package.msi' + $msiUrl = $uriBuilder.Uri.AbsoluteUri + + $fileServerStarted = $null + $job = $null + + $msiPackageParameters = @{ + ProductId = $script:packageId + Path = $msiUrl + Ensure = 'Absent' + } + + It 'Should return False from Test-TargetResource with the same parameters before configuration' { + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult | Should Be $false + + if ($testTargetResourceInitialResult -ne $false) + { + <# + Not throwing an error here since the tests should still run correctly after this, + we just want to notify the user that the tests aren't necessarily testing what + they should be + #> + Write-Error -Message $script:environmentInIncorrectStateErrorMessage } + } - It 'Should return True from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters - $testTargetResourceInitialResult | Should Be $true + It 'Package should exist on the machine before configuration is run' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $true + } - if ($testTargetResourceInitialResult -ne $true) - { - <# - Not throwing an error here since the tests should still run correctly after this, - we just want to notify the user that the tests aren't necessarily testing what - they should be - #> - Write-Error -Message $script:environmentInIncorrectStateErrorMessage - } - } + try + { + # Make sure no existing HTTP(S) test servers are running + Stop-EveryTestServerInstance - It 'Package should not exist on the machine before configuration is run' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $false - } + $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false -HttpPort $script:testHttpPort -HttpsPort $script:testHttpsPort + $fileServerStarted = $serverResult.FileServerStarted + $job = $serverResult.Job + + $fileServerStarted.WaitOne(30000) It 'Should compile and run configuration' { - { + { . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName & $configurationName -OutputPath $TestDrive @msiPackageParameters Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force } | Should Not Throw } + } + finally + { + <# + This must be called after Start-Server to ensure the listening port is closed, + otherwise subsequent tests may fail until the machine is rebooted. + #> + Stop-Server -FileServerStarted $fileServerStarted -Job $job + } - It 'Should return True from Test-TargetResource with the same parameters after configuration' { - MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true - } + It 'Should return true from Test-TargetResource with the same parameters after configuration' { + MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true + } - It 'Package should not exist on the machine' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $false + It 'Package should not exist on the machine' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $false + } + } + + Context 'Install Msi package from HTTPS Url' { + $configurationName = 'InstallMsiPackageFromHttpS' + + $uriBuilder = [System.UriBuilder]::new('https', 'localhost', $script:testHttpsPort) + $uriBuilder.Path = 'package.msi' + $msiUrl = $uriBuilder.Uri.AbsoluteUri + + $fileServerStarted = $null + $job = $null + + $msiPackageParameters = @{ + ProductId = $script:packageId + Path = $msiUrl + Ensure = 'Present' + } + + It 'Should return False from Test-TargetResource with the same parameters before configuration' { + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult | Should Be $false + + if ($testTargetResourceInitialResult -ne $false) + { + <# + Not throwing an error here since the tests should still run correctly after this, + we just want to notify the user that the tests aren't necessarily testing what + they should be + #> + Write-Error -Message $script:environmentInIncorrectStateErrorMessage } } - Context 'Install package that is not installed yet' { - $configurationName = 'InstallMsiPackage' + It 'Package should not exist on the machine before configuration is run' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $false + } - $msiPackageParameters = @{ - ProductId = $script:packageId - Path = $script:msiLocation - Ensure = 'Present' - } + try + { + # Make sure no existing HTTP(S) test servers are running + Stop-EveryTestServerInstance - It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters - $testTargetResourceInitialResult | Should Be $false + $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $true -HttpPort $script:testHttpPort -HttpsPort $script:testHttpsPort + $fileServerStarted = $serverResult.FileServerStarted + $job = $serverResult.Job - if ($testTargetResourceInitialResult -ne $false) - { - <# - Not throwing an error here since the tests should still run correctly after this, - we just want to notify the user that the tests aren't necessarily testing what - they should be - #> - Write-Error -Message $script:environmentInIncorrectStateErrorMessage - } - } - - It 'Package should not exist on the machine before configuration is run' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $false - } + $fileServerStarted.WaitOne(30000) It 'Should compile and run configuration' { - { + { . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName & $configurationName -OutputPath $TestDrive @msiPackageParameters Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force } | Should Not Throw } + } + finally + { + <# + This must be called after Start-Server to ensure the listening port is closed, + otherwise subsequent tests may fail until the machine is rebooted. + #> + Stop-Server -FileServerStarted $fileServerStarted -Job $job + } - It 'Should return True from Test-TargetResource with the same parameters after configuration' { - MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true - } + It 'Should return true from Test-TargetResource with the same parameters after configuration' { + MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true + } - It 'Package should exist on the machine' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $true + It 'Package should exist on the machine' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $true + } + } + + Context 'Uninstall Msi package from HTTPS Url' { + $configurationName = 'UninstallMsiPackageFromHttps' + + $uriBuilder = [System.UriBuilder]::new('https', 'localhost', $script:testHttpsPort) + $uriBuilder.Path = 'package.msi' + $msiUrl = $uriBuilder.Uri.AbsoluteUri + + $fileServerStarted = $null + $job = $null + + $msiPackageParameters = @{ + ProductId = $script:packageId + Path = $msiUrl + Ensure = 'Absent' + } + + It 'Should return False from Test-TargetResource with the same parameters before configuration' { + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult | Should Be $false + + if ($testTargetResourceInitialResult -ne $false) + { + <# + Not throwing an error here since the tests should still run correctly after this, + we just want to notify the user that the tests aren't necessarily testing what + they should be + #> + Write-Error -Message $script:environmentInIncorrectStateErrorMessage } } - Context 'Install package that is already installed' { - $configurationName = 'InstallExistingMsiPackage' + It 'Package should exist on the machine before configuration is run' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $true + } - $msiPackageParameters = @{ - ProductId = $script:packageId - Path = $script:msiLocation - Ensure = 'Present' - } + try + { + # Make sure no existing HTTP(S) test servers are running + Stop-EveryTestServerInstance - It 'Should return True from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters - $testTargetResourceInitialResult | Should Be $true + $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $true -HttpPort $script:testHttpPort -HttpsPort $script:testHttpsPort + $fileServerStarted = $serverResult.FileServerStarted + $job = $serverResult.Job - if ($testTargetResourceInitialResult -ne $true) - { - <# - Not throwing an error here since the tests should still run correctly after this, - we just want to notify the user that the tests aren't necessarily testing what - they should be - #> - Write-Error -Message $script:environmentInIncorrectStateErrorMessage - } - } - - It 'Package should exist on the machine before configuration is run' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $true - } + $fileServerStarted.WaitOne(30000) It 'Should compile and run configuration' { - { + { . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName & $configurationName -OutputPath $TestDrive @msiPackageParameters Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force } | Should Not Throw } - - It 'Should return True from Test-TargetResource with the same parameters after configuration' { - MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true - } - - It 'Package should exist on the machine' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $true - } + } + finally + { + <# + This must be called after Start-Server to ensure the listening port is closed, + otherwise subsequent tests may fail until the machine is rebooted. + #> + Stop-Server -FileServerStarted $fileServerStarted -Job $job } - Context 'Uninstall package that is installed' { - $configurationName = 'UninstallExistingMsiPackage' - - $msiPackageParameters = @{ - ProductId = $script:packageId - Path = $script:msiLocation - Ensure = 'Absent' - } - - It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters - $testTargetResourceInitialResult | Should Be $false - - if ($testTargetResourceInitialResult -ne $false) - { - <# - Not throwing an error here since the tests should still run correctly after this, - we just want to notify the user that the tests aren't necessarily testing what - they should be - #> - Write-Error -Message $script:environmentInIncorrectStateErrorMessage - } - } - - It 'Package should exist on the machine before configuration is run' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $true - } - - It 'Should compile and run configuration' { - { - . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName - & $configurationName -OutputPath $TestDrive @msiPackageParameters - Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force - } | Should Not Throw - } - - It 'Should return True from Test-TargetResource with the same parameters after configuration' { - MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true - } - - It 'Package should not exist on the machine' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $false - } + It 'Should return true from Test-TargetResource with the same parameters after configuration' { + MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true } - Context 'Install package that is not installed and write to specified log file' { - $configurationName = 'InstallWithLogFile' - - $logPath = Join-Path -Path $TestDrive -ChildPath 'TestMsiLog.txt' - - if (Test-Path -Path $logPath) - { - Remove-Item -Path $logPath -Force - } - - $msiPackageParameters = @{ - ProductId = $script:packageId - Path = $script:msiLocation - Ensure = 'Present' - LogPath = $logPath - } - - It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters - $testTargetResourceInitialResult | Should Be $false - - if ($testTargetResourceInitialResult -ne $false) - { - <# - Not throwing an error here since the tests should still run correctly after this, - we just want to notify the user that the tests aren't necessarily testing what - they should be - #> - Write-Error -Message $script:environmentInIncorrectStateErrorMessage - } - } - - It 'Package should not exist on the machine before configuration is run' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $false - } - - It 'Should compile and run configuration' { - { - . $script:configurationFilePathLogPath -ConfigurationName $configurationName - & $configurationName -OutputPath $TestDrive @msiPackageParameters - Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force - } | Should Not Throw - } - - It 'Should return True from Test-TargetResource with the same parameters after configuration' { - MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true - } - - It 'Should have created the log file' { - Test-Path -Path $logPath | Should Be $true - } - - It 'Package should exist on the machine' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $true - } - } - - Context 'Uninstall package that is installed and write to specified log file' { - $configurationName = 'InstallWithLogFile' - - $logPath = Join-Path -Path $TestDrive -ChildPath 'TestMsiLog.txt' - - if (Test-Path -Path $logPath) - { - Remove-Item -Path $logPath -Force - } - - $msiPackageParameters = @{ - ProductId = $script:packageId - Path = $script:msiLocation - Ensure = 'Absent' - LogPath = $logPath - } - - It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters - $testTargetResourceInitialResult | Should Be $false - - if ($testTargetResourceInitialResult -ne $false) - { - <# - Not throwing an error here since the tests should still run correctly after this, - we just want to notify the user that the tests aren't necessarily testing what - they should be - #> - Write-Error -Message $script:environmentInIncorrectStateErrorMessage - } - } - - It 'Package should exist on the machine before configuration is run' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $true - } - - It 'Should compile and run configuration' { - { - . $script:configurationFilePathLogPath -ConfigurationName $configurationName - & $configurationName -OutputPath $TestDrive @msiPackageParameters - Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force - } | Should Not Throw - } - - It 'Should return True from Test-TargetResource with the same parameters after configuration' { - MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true - } - - It 'Should have created the log file' { - Test-Path -Path $logPath | Should Be $true - } - - It 'Package should not exist on the machine' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $false - } - } - - Context 'Install package from HTTP Url' { - $configurationName = 'UninstallExistingMsiPackageFromHttp' - - $baseUrl = 'http://localhost:1242/' - $msiUrl = "$baseUrl" + 'package.msi' - - $fileServerStarted = $null - $job = $null - - $msiPackageParameters = @{ - ProductId = $script:packageId - Path = $msiUrl - Ensure = 'Present' - } - - It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters - $testTargetResourceInitialResult | Should Be $false - - if ($testTargetResourceInitialResult -ne $false) - { - <# - Not throwing an error here since the tests should still run correctly after this, - we just want to notify the user that the tests aren't necessarily testing what - they should be - #> - Write-Error -Message $script:environmentInIncorrectStateErrorMessage - } - } - - It 'Package should not exist on the machine before configuration is run' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $false - } - - try - { - $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false - $fileServerStarted = $serverResult.FileServerStarted - $job = $serverResult.Job - - $fileServerStarted.WaitOne(30000) - - It 'Should compile and run configuration' { - { - . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName - & $configurationName -OutputPath $TestDrive @msiPackageParameters - Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force - } | Should Not Throw - } - } - finally - { - <# - This must be called after Start-Server to ensure the listening port is closed, - otherwise subsequent tests may fail until the machine is rebooted. - #> - Stop-Server -FileServerStarted $fileServerStarted -Job $job - } - - It 'Should return True from Test-TargetResource with the same parameters after configuration' { - MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true - } - - It 'Package should exist on the machine' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $true - } - } - - Context 'Uninstall Msi package from HTTP Url' { - $configurationName = 'InstallMsiPackageFromHttp' - - $baseUrl = 'http://localhost:1242/' - $msiUrl = "$baseUrl" + 'package.msi' - - $fileServerStarted = $null - $job = $null - - $msiPackageParameters = @{ - ProductId = $script:packageId - Path = $msiUrl - Ensure = 'Absent' - } - - It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters - $testTargetResourceInitialResult | Should Be $false - - if ($testTargetResourceInitialResult -ne $false) - { - <# - Not throwing an error here since the tests should still run correctly after this, - we just want to notify the user that the tests aren't necessarily testing what - they should be - #> - Write-Error -Message $script:environmentInIncorrectStateErrorMessage - } - } - - It 'Package should exist on the machine before configuration is run' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $true - } - - try - { - $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false - $fileServerStarted = $serverResult.FileServerStarted - $job = $serverResult.Job - - $fileServerStarted.WaitOne(30000) - - It 'Should compile and run configuration' { - { - . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName - & $configurationName -OutputPath $TestDrive @msiPackageParameters - Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force - } | Should Not Throw - } - } - finally - { - <# - This must be called after Start-Server to ensure the listening port is closed, - otherwise subsequent tests may fail until the machine is rebooted. - #> - Stop-Server -FileServerStarted $fileServerStarted -Job $job - } - - It 'Should return true from Test-TargetResource with the same parameters after configuration' { - MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true - } - - It 'Package should not exist on the machine' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $false - } - } - - Context 'Install Msi package from HTTPS Url' { - $configurationName = 'InstallMsiPackageFromHttpS' - - $baseUrl = 'https://localhost:1243/' - $msiUrl = "$baseUrl" + 'package.msi' - - $fileServerStarted = $null - $job = $null - - $msiPackageParameters = @{ - ProductId = $script:packageId - Path = $msiUrl - Ensure = 'Present' - } - - It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters - $testTargetResourceInitialResult | Should Be $false - - if ($testTargetResourceInitialResult -ne $false) - { - <# - Not throwing an error here since the tests should still run correctly after this, - we just want to notify the user that the tests aren't necessarily testing what - they should be - #> - Write-Error -Message $script:environmentInIncorrectStateErrorMessage - } - } - - It 'Package should not exist on the machine before configuration is run' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $false - } - - try - { - $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $true - $fileServerStarted = $serverResult.FileServerStarted - $job = $serverResult.Job - - $fileServerStarted.WaitOne(30000) - - It 'Should compile and run configuration' { - { - . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName - & $configurationName -OutputPath $TestDrive @msiPackageParameters - Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force - } | Should Not Throw - } - } - finally - { - <# - This must be called after Start-Server to ensure the listening port is closed, - otherwise subsequent tests may fail until the machine is rebooted. - #> - Stop-Server -FileServerStarted $fileServerStarted -Job $job - } - - It 'Should return true from Test-TargetResource with the same parameters after configuration' { - MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true - } - - It 'Package should exist on the machine' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $true - } - } - - Context 'Uninstall Msi package from HTTPS Url' { - $configurationName = 'UninstallMsiPackageFromHttps' - - $baseUrl = 'https://localhost:1243/' - $msiUrl = "$baseUrl" + 'package.msi' - - $fileServerStarted = $null - $job = $null - - $msiPackageParameters = @{ - ProductId = $script:packageId - Path = $msiUrl - Ensure = 'Absent' - } - - It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters - $testTargetResourceInitialResult | Should Be $false - - if ($testTargetResourceInitialResult -ne $false) - { - <# - Not throwing an error here since the tests should still run correctly after this, - we just want to notify the user that the tests aren't necessarily testing what - they should be - #> - Write-Error -Message $script:environmentInIncorrectStateErrorMessage - } - } - - It 'Package should exist on the machine before configuration is run' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $true - } - - try - { - $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $true - $fileServerStarted = $serverResult.FileServerStarted - $job = $serverResult.Job - - $fileServerStarted.WaitOne(30000) - - It 'Should compile and run configuration' { - { - . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName - & $configurationName -OutputPath $TestDrive @msiPackageParameters - Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force - } | Should Not Throw - } - } - finally - { - <# - This must be called after Start-Server to ensure the listening port is closed, - otherwise subsequent tests may fail until the machine is rebooted. - #> - Stop-Server -FileServerStarted $fileServerStarted -Job $job - } - - It 'Should return true from Test-TargetResource with the same parameters after configuration' { - MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should Be $true - } - - It 'Package should not exist on the machine' { - Test-PackageInstalledById -ProductId $script:packageId | Should Be $false - } + It 'Package should not exist on the machine' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $false } } } -finally -{ - Undo-ChangesToStrongCryptoForDotNetFour -OriginalSettings $originalStrongCryptoSettings -} diff --git a/Tests/Integration/MSFT_MsiPackage.Integration.Tests.ps1 b/Tests/Integration/MSFT_MsiPackage.Integration.Tests.ps1 index a6a5e45..f709f6b 100644 --- a/Tests/Integration/MSFT_MsiPackage.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_MsiPackage.Integration.Tests.ps1 @@ -19,9 +19,6 @@ $script:testEnvironment = Enter-DscResourceTestEnvironment ` try { - # Make sure strong crypto is enabled in .NET for HTTPS tests - $originalStrongCryptoSettings = Enable-StrongCryptoForDotNetFour - InModuleScope 'MSFT_MsiPackage' { Describe 'MSFT_MsiPackage Integration Tests' { BeforeAll { @@ -40,7 +37,7 @@ try <# This log file is used to log messages from the mock server which is important for debugging since - most of the work of the mock server is done within a separate process. + most of the work of the mock server is done within a separate process. #> $script:logFile = Join-Path -Path $PSScriptRoot -ChildPath 'PackageTestLogFile.txt' @@ -52,6 +49,9 @@ try $null = New-TestMsi -DestinationPath $script:msiLocation + $script:testHttpPort = Get-UnusedTcpPort + $script:testHttpsPort = Get-UnusedTcpPort -ExcludePorts @($script:testHttpPort) + # Clear the log file 'Beginning integration tests' > $script:logFile } @@ -174,8 +174,11 @@ try } It 'Should correctly install and remove a package from a HTTP URL' { - $baseUrl = 'http://localhost:1242/' - $msiUrl = "$baseUrl" + 'package.msi' + $uriBuilder = [System.UriBuilder]::new('http', 'localhost', $script:testHttpPort) + $baseUrl = $uriBuilder.Uri.AbsoluteUri + + $uriBuilder.Path = 'package.msi' + $msiUrl = $uriBuilder.Uri.AbsoluteUri $fileServerStarted = $null $job = $null @@ -184,7 +187,10 @@ try { 'Http tests:' >> $script:logFile - $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false + # Make sure no existing HTTP(S) test servers are running + Stop-EveryTestServerInstance + + $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false -HttpPort $script:testHttpPort -HttpsPort $script:testHttpsPort $fileServerStarted = $serverResult.FileServerStarted $job = $serverResult.Job @@ -199,6 +205,12 @@ try Set-TargetResource -Ensure 'Absent' -Path $msiUrl -ProductId $script:packageId Test-PackageInstalledById -ProductId $script:packageId | Should Be $false } + catch + { + Write-Warning -Message 'Caught exception performing HTTP server tests. Outputting HTTP server log.' -Verbose + Get-Content -Path $script:logFile | Write-Verbose -Verbose + throw $_ + } finally { <# @@ -210,9 +222,11 @@ try } It 'Should correctly install and remove a package from a HTTPS URL' -Skip:$script:skipHttpsTest { + $uriBuilder = [System.UriBuilder]::new('https', 'localhost', $script:testHttpsPort) + $baseUrl = $uriBuilder.Uri.AbsoluteUri - $baseUrl = 'https://localhost:1243/' - $msiUrl = "$baseUrl" + 'package.msi' + $uriBuilder.Path = 'package.msi' + $msiUrl = $uriBuilder.Uri.AbsoluteUri $fileServerStarted = $null $job = $null @@ -221,9 +235,12 @@ try { 'Https tests:' >> $script:logFile - $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $true + # Make sure no existing HTTP(S) test servers are running + Stop-EveryTestServerInstance + + $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $true -HttpPort $script:testHttpPort -HttpsPort $script:testHttpsPort $fileServerStarted = $serverResult.FileServerStarted - $job = $serverResult.Job + $job = $serverResult.Job # Wait for the file server to be ready to receive requests $fileServerStarted.WaitOne(30000) @@ -236,6 +253,12 @@ try Set-TargetResource -Ensure 'Absent' -Path $msiUrl -ProductId $script:packageId Test-PackageInstalledById -ProductId $script:packageId | Should Be $false } + catch + { + Write-Warning -Message 'Caught exception performing HTTPS server tests. Outputting HTTPS server log.' -Verbose + Get-Content -Path $script:logFile | Write-Verbose -Verbose + throw $_ + } finally { <# @@ -309,6 +332,4 @@ try finally { Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment - - Undo-ChangesToStrongCryptoForDotNetFour -OriginalSettings $originalStrongCryptoSettings } diff --git a/Tests/TestHelpers/CommonTestHelper.psm1 b/Tests/TestHelpers/CommonTestHelper.psm1 index 725b4ad..f6b3b32 100644 --- a/Tests/TestHelpers/CommonTestHelper.psm1 +++ b/Tests/TestHelpers/CommonTestHelper.psm1 @@ -157,7 +157,7 @@ function Invoke-ExpectedMocksAreCalledTest Each item in the array is a hashtable that contains the name of the command being mocked, the number of times it is called (can be 0) and, optionally, an extra custom string to make the test name more descriptive. The custom - string will only work if the command has a corresponding variable in the + string will only work if the command has a corresponding variable in the string data name. .PARAMETER ShouldThrow @@ -227,7 +227,7 @@ function Invoke-GenericUnitTest { Each item in the array is a hashtable that contains the name of the command being mocked, the number of times it is called (can be 0) and, optionally, an extra custom string to make the test name more descriptive. The custom - string will only work if the command has a corresponding variable in the + string will only work if the command has a corresponding variable in the string data name. .PARAMETER ExpectedReturnValue @@ -288,7 +288,7 @@ function Invoke-GetTargetResourceUnitTest Each item in the array is a hashtable that contains the name of the command being mocked, the number of times it is called (can be 0) and, optionally, an extra custom string to make the test name more descriptive. The custom - string will only work if the command has a corresponding variable in the + string will only work if the command has a corresponding variable in the string data name. .PARAMETER ShouldThrow @@ -354,7 +354,7 @@ function Invoke-SetTargetResourceUnitTest { Each item in the array is a hashtable that contains the name of the command being mocked, the number of times it is called (can be 0) and, optionally, an extra custom string to make the test name more descriptive. The custom - string will only work if the command has a corresponding variable in the + string will only work if the command has a corresponding variable in the string data name. .PARAMETER ExpectedReturnValue @@ -573,7 +573,7 @@ function Test-IsFileLocked .PARAMETER ExpectedOutput The output expected to be in the output from running WhatIf with the Set-TargetResource cmdlet. If this parameter is empty or null, this cmdlet will check that there was no output from - Set-TargetResource with WhatIf specified. + Set-TargetResource with WhatIf specified. #> function Test-SetTargetResourceWithWhatIf { @@ -584,7 +584,7 @@ function Test-SetTargetResourceWithWhatIf [Parameter(Mandatory = $true)] [Hashtable] $Parameters, - + [String[]] $ExpectedOutput ) @@ -792,101 +792,82 @@ function Exit-DscResourceTestEnvironment <# .SYNOPSIS - Enables strong crypto for .NET v4.0.30319. Returns a Hashtable - containing the relevant, original registry settings, prior to - modification. + Finds an unused TCP port in the specified port range. By default, + searches within ports 38473 - 38799, which at the time of writing, show + as unassigned in: + https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml + + .PARAMETER LowestPortNumber + The TCP port number at which to begin the unused port search. Must be + greater than 0. + + .PARAMETER HighestPortNumber + The highest TCP port number to search for unused ports within. Must be + greater than 0, and greater than LowestPortNumber. + + .PARAMETER ExcludePorts + TCP ports to exclude from the search, even if they fall within the + LowestPortNumber and HighestPortNumber range. #> -function Enable-StrongCryptoForDotNetFour -{ - [OutputType([System.Collections.Hashtable])] - [CmdletBinding()] - param () - - $originalValues = @{} - - $regBases = @( - 'HKLM:\SOFTWARE' - 'HKLM:\SOFTWARE\Wow6432Node' - ) - - $net4Suffix = 'Microsoft\.NetFramework\v4.0.30319' - - foreach ($regBase in $regBases) - { - $regPath = Join-Path -Path $regBase -ChildPath $net4Suffix - - if ($null -ne (Get-Item -Path $regPath -ErrorAction SilentlyContinue)) - { - $useStrongCryptoProp = Get-ItemProperty -Path $regPath -Name 'SchUseStrongCrypto' -ErrorAction SilentlyContinue - $needsUpdate = $false - - if ($null -eq $useStrongCryptoProp) - { - $originalValues.Add($regPath, $null) - $needsUpdate = $true - } - else - { - $originalValues.Add($regPath, $useStrongCryptoProp.SchUseStrongCrypto) - - if ($useStrongCryptoProp.SchUseStrongCrypto -ne 1) - { - $needsUpdate = $true - } - } - - if ($needsUpdate) - { - Write-Verbose -Message "Setting property SchUseStrongCrypto at path '$regPath' to 1" - - Set-ItemProperty -Path $regPath -Name 'SchUseStrongCrypto' -Value '1' -Type DWord -ErrorAction Stop - } - } - else - { - Write-Warning -Message "Failed to find registry key at path: $regPath. Skipping setting SchUseStrongCrypto to 1. This may cause Integration tests to fail that depend on this setting." - continue - } - } - - return $originalValues -} - -<# - .SYNOPSIS - Restores strong crypto for .NET v4.0.30319 settings to what they were - prior to calling Enable-StrongCryptoForDotNetFour. - - .PARAMETER OriginalSettings - A Hashtable containing the original registry settings for strong crypto - for .NET v4.0.30319. -#> -function Undo-ChangesToStrongCryptoForDotNetFour +function Get-UnusedTcpPort { + [OutputType([System.UInt16])] [CmdletBinding()] param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.Collections.Hashtable] - $OriginalSettings + [Parameter()] + [ValidateScript({$_ -gt 0})] + [System.UInt16] + $LowestPortNumber = 38473, + + [Parameter()] + [ValidateScript({$_ -gt $0})] + [System.UInt16] + $HighestPortNumber = 38799, + + [Parameter()] + [System.UInt16[]] + $ExcludePorts = @() ) - foreach ($regPath in $OriginalSettings.Keys) + if ($HighestPortNumber -lt $LowestPortNumber) { - $useStrongCryptoProp = Get-ItemProperty -Path $regPath -Name 'SchUseStrongCrypto' -ErrorAction SilentlyContinue + throw 'HighestPortNumber must be greater than or equal to LowestPortNumber' + } - # The SchUseStrongCrypto value previously didn't exist, but now does. Delete it. - if ($null -eq $OriginalSettings[$regPath] -and $null -ne $useStrongCryptoProp) + [System.UInt16] $unusedPort = 0 + + [System.Collections.ArrayList] $usedAndExcludedPorts = (Get-NetTCPConnection).LocalPort | Where-Object -FilterScript { + $_ -ge $LowestPortNumber -and $_ -le $HighestPortNumber + } + + if (!(Test-Path -Path variable:usedAndExcludedPorts) -or ($null -eq $usedAndExcludedPorts)) + { + [System.Collections.ArrayList] $usedAndExcludedPorts = @() + } + + if (!(Test-Path -Path variable:ExcludePorts) -or ($null -eq $ExcludePorts)) + { + $ExcludePorts = @() + } + + $null = $usedAndExcludedPorts.Add($ExcludePorts) + + foreach ($port in $LowestPortNumber..$HighestPortNumber) + { + if (!($usedAndExcludedPorts.Contains($port))) { - Remove-ItemProperty -Path $regPath -Name 'SchUseStrongCrypto' -ErrorAction Stop - } - # The SchUseStrongCrypto value previously existed but had a different value. - elseif($OriginalSettings[$regPath] -ne $useStrongCryptoProp.SchUseStrongCrypto) - { - Set-ItemProperty -Path $regPath -Name 'SchUseStrongCrypto' -Value $OriginalSettings[$regPath] -Type DWord -ErrorAction Stop + $unusedPort = $port + break } } + + if ($unusedPort -eq 0) + { + throw "Failed to find unused TCP port between ports $LowestPortNumber and $HighestPortNumber." + } + + return $unusedPort } Export-ModuleMember -Function @( @@ -901,7 +882,6 @@ Export-ModuleMember -Function @( 'Invoke-SetTargetResourceUnitTest', ` 'Invoke-TestTargetResourceUnitTest', ` 'Invoke-ExpectedMocksAreCalledTest', ` - 'Invoke-GenericUnitTest', - 'Enable-StrongCryptoForDotNetFour', - 'Undo-ChangesToStrongCryptoForDotNetFour' + 'Invoke-GenericUnitTest', ` + 'Get-UnusedTcpPort' ) diff --git a/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 b/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 index b96befe..5b3b8f2 100644 --- a/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 +++ b/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 @@ -1,6 +1,8 @@ $errorActionPreference = 'Stop' Set-StrictMode -Version 'Latest' +$testJobPrefix = 'MsiPackageTestJob' + <# .SYNOPSIS Tests if the package with the given Id is installed. @@ -55,9 +57,15 @@ function Test-PackageInstalledById .PARAMETER Https Indicates whether the server should use Https. If True then the file server will use Https - and listen on port 'https://localhost:1243'. Otherwise the file server will use Http and - listen on port 'http://localhost:1242' + and listen on port 'https://localhost:HttpsPort'. Otherwise the file server will use Http and + listen on port 'http://localhost:HttpPort' Default value is False (Http). + + .PARAMETER HttpPort + Specifies the TCP port to register an Http based HttpListener on. + + .PARAMETER HttspPort + Specifies the TCP port to register an Https based HttpListener on. #> function Start-Server { @@ -74,7 +82,17 @@ function Start-Server $LogPath = (Join-Path -Path $PSScriptRoot -ChildPath 'PackageTestLogFile.txt'), [System.Boolean] - $Https = $false + $Https = $false, + + [Parameter(Mandatory = $true)] + [ValidateScript({$_ -gt 0})] + [System.UInt16] + $HttpPort, + + [Parameter(Mandatory = $true)] + [ValidateScript({$_ -gt 0})] + [System.UInt16] + $HttpsPort ) # Create an event object to let the client know when the server is ready to begin receiving requests. @@ -82,7 +100,7 @@ function Start-Server 'HttpIntegrationTest.FileServerStarted') $null = $fileServerStarted.Reset() - <# + <# The server is run on a separate process so that it can receive requests while the tests continue to run. It takes in the same parameterss that are passed in to this function. All helper functions that the server uses have to be @@ -90,7 +108,7 @@ function Start-Server #> $server = { - param($FilePath, $LogPath, $Https) + param($FilePath, $LogPath, $Https, $HttpPort, $HttpsPort) <# .SYNOPSIS @@ -101,6 +119,9 @@ function Start-Server .PARAMETER Https Indicates whether https was used and if so, removes the SSL binding. + + .PARAMETER HttspPort + Specifies the TCP port to de-register an Https based HttpListener from. #> function Stop-Listener { @@ -109,16 +130,21 @@ function Start-Server ( [Parameter(Mandatory = $true)] [System.Net.HttpListener] - $HttpListener, + $HttpListener, [Parameter(Mandatory = $true)] [System.Boolean] - $Https + $Https, + + [Parameter(Mandatory = $true)] + [ValidateScript({$_ -gt 0})] + [System.UInt16] + $HttpsPort ) Write-Log -LogFile $LogPath -Message 'Finished listening for requests. Shutting down HTTP server.' - - $ipPort = '0.0.0.0:1243' + + $ipPort = "0.0.0.0:$HttpsPort" if ($null -eq $HttpListener) { @@ -159,16 +185,25 @@ function Start-Server <# .SYNOPSIS Creates and registers an SSL certificate for Https connections. + + .PARAMETER HttspPort + Specifies the TCP port to register an Https based HttpListener on. #> function Register-Ssl { [CmdletBinding()] - param() + param + ( + [Parameter(Mandatory = $true)] + [ValidateScript({$_ -gt 0})] + [System.UInt16] + $HttpsPort + ) # Create certificate $certificate = New-SelfSignedCertificate -CertStoreLocation 'Cert:\LocalMachine\My' -DnsName localhost Write-Log -LogFile $LogPath -Message 'Created certificate' - + $hash = $certificate.Thumbprint $certPassword = ConvertTo-SecureString -String 'password12345' -AsPlainText -Force $tempPath = 'C:\certForTesting' @@ -176,11 +211,11 @@ function Start-Server $null = Export-PfxCertificate -Cert $certificate -FilePath $tempPath -Password $certPassword $null = Import-PfxCertificate -CertStoreLocation 'Cert:\LocalMachine\Root' -FilePath 'C:\certForTesting' -Password $certPassword Remove-Item -Path $tempPath - + Write-Log -LogFile $LogPath -Message 'Finished importing certificate into root. About to bind it to port.' - + # Use net shell command to directly bind certificate to designated testing port - $null = netsh http add sslcert ipport=0.0.0.0:1243 certhash=$hash appid='{833f13c2-319a-4799-9d1a-5b267a0c3593}' clientcertnegotiation=enable + $null = netsh http add sslcert ipport=0.0.0.0:$HttpsPort certhash=$hash appid='{833f13c2-319a-4799-9d1a-5b267a0c3593}' clientcertnegotiation=enable } <# @@ -204,32 +239,32 @@ function Start-Server # Add the CallbackEventBridge type if it's not already defined if (-not ('CallbackEventBridge' -as [Type])) { - Add-Type @' - using System; - - public sealed class CallbackEventBridge { - public event AsyncCallback CallbackComplete = delegate { }; - - private CallbackEventBridge() {} - + Add-Type @' + using System; + + public sealed class CallbackEventBridge { + public event AsyncCallback CallbackComplete = delegate { }; + + private CallbackEventBridge() {} + private void CallbackInternal(IAsyncResult result) - { - CallbackComplete(result); - } - + { + CallbackComplete(result); + } + public AsyncCallback Callback - { - get { return new AsyncCallback(CallbackInternal); } - } - + { + get { return new AsyncCallback(CallbackInternal); } + } + public static CallbackEventBridge Create() - { - return new CallbackEventBridge(); - } - } + { + return new CallbackEventBridge(); + } + } '@ } - + $bridge = [CallbackEventBridge]::Create() Register-ObjectEvent -InputObject $bridge -EventName 'CallbackComplete' -Action $Callback -MessageData $args > $null $bridge.Callback @@ -249,7 +284,7 @@ function Start-Server .PARAMETER ScriptBlock The code to execute. - + #> function Invoke-ConsoleCommand { @@ -277,7 +312,7 @@ function Start-Server $message = ('Failed action ''{0}'' on target ''{1}'' (exit code {2}): {3}' -f $Action,$Target,$LASTEXITCODE,$output) Write-Error -Message $message Write-Log -LogFile $LogPath -Message "Error from Invoke-ConsoleCommand: $message" - } + } else { $nonNullOutput = $output | Where-Object { $_ -ne $null } @@ -309,7 +344,7 @@ function Start-Server [String] $Message ) - + $Message >> $LogFile } @@ -332,11 +367,11 @@ function Start-Server # Set up the listener if ($Https) { - $HttpListener.Prefixes.Add([Uri]'https://localhost:1243') - + $HttpListener.Prefixes.Add([Uri] "https://localhost:$HttpsPort") + try { - Register-SSL + Register-SSL -HttpsPort $HttpsPort } catch { @@ -349,7 +384,7 @@ function Start-Server } else { - $HttpListener.Prefixes.Add([Uri]'http://localhost:1242') + $HttpListener.Prefixes.Add([Uri] "http://localhost:$HttpPort") } Write-Log -LogFile $LogPath -Message 'Finished listener setup - about to start listener' @@ -374,7 +409,7 @@ function Start-Server .PARAMETER Result th IAsyncResult containing the listener object and path to the MSI file. - + #> $requestListener = { @@ -450,8 +485,22 @@ function Start-Server catch { $errorMessage = "There were problems setting up the HTTP(s) listener. Error: $_" + Write-Log -LogFile $LogPath -Message $errorMessage - throw $errorMessage + + 'Error Record Info' >> $LogPath + $_ | ConvertTo-Xml -As String >> $LogPath + + 'Exception Info' >> $LogPath + $_.Exception | ConvertTo-Xml -As String >> $LogPath + + 'Running Process Info' >> $LogPath + Get-Process | Format-List | Out-String >> $LogPath + + 'Open TCP Connections Info' >> $LogPath + Get-NetTCPConnection | Format-List | Out-String >> $LogPath + + throw $_ } finally { @@ -459,19 +508,39 @@ function Start-Server { $fileServerStarted.Dispose() } - + Write-Log -LogFile $LogPath -Message 'Stopping the Server' - Stop-Listener -HttpListener $HttpListener -Https $Https + Stop-Listener -HttpListener $HttpListener -Https $Https -HttpsPort $HttpsPort } } - $job = Start-Job -ScriptBlock $server -ArgumentList @( $FilePath, $LogPath, $Https ) + if ($Https) + { + $jobName = $testJobPrefix + 'Https' + } + else + { + $jobName = $testJobPrefix + 'Http' + } + + $job = Start-Job -ScriptBlock $server -Name $jobName -ArgumentList @( $FilePath, $LogPath, $Https, $HttpPort, $HttpsPort ) + + # Verify that the job is receivable and does not contain an exception. If it does, re-throw it. + try + { + $receivedJob = $job | Receive-Job + } + catch + { + Write-Error -Message 'Failed to setup HTTP(S) listener for MsiPackage Tests' + throw $_ + } <# - Return the event object so that client knows when it can start sending requests and + Return the event object so that client knows when it can start sending requests and the job object so that the client can stop the job once it is done sending requests. #> - return @{ + return @{ FileServerStarted = $fileServerStarted Job = $job } @@ -501,7 +570,7 @@ function Stop-Server $FileServerStarted, [System.Management.Automation.Job] - $Job + $Job ) if ($null -ne $FileServerStarted) @@ -516,6 +585,20 @@ function Stop-Server } } +<# + .SYNOPSIS + Removes any jobs associated with HTTP(S) servers that were created + for MsiPackage tests. +#> +function Stop-EveryTestServerInstance +{ + [CmdletBinding()] + param () + + Get-Job -Name "$($testJobPrefix)*" | Stop-Job + Get-Job -Name "$($testJobPrefix)*" | Remove-Job +} + <# .SYNOPSIS Creates a new MSI package for testing. @@ -526,7 +609,7 @@ function Stop-Server function New-TestMsi { [CmdletBinding()] - param + param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] @@ -1061,4 +1144,5 @@ Export-ModuleMember -Function ` New-TestMsi, ` Start-Server, ` Stop-Server, ` - Test-PackageInstalledById + Test-PackageInstalledById, ` + Stop-EveryTestServerInstance diff --git a/Tests/Unit/CommonResourceHelper.Tests.ps1 b/Tests/Unit/CommonResourceHelper.Tests.ps1 index 241c18f..09cc5b2 100644 --- a/Tests/Unit/CommonResourceHelper.Tests.ps1 +++ b/Tests/Unit/CommonResourceHelper.Tests.ps1 @@ -14,112 +14,38 @@ Describe 'CommonResourceHelper Unit Tests' { InModuleScope 'CommonResourceHelper' { Describe 'Test-IsNanoServer' { $testComputerInfoNanoServer = @{ - OsProductType = 'Server' - OsServerLevel = 'NanoServer' + NanoServer = 1 } $testComputerInfoServerNotNano = @{ - OsProductType = 'Server' - OsServerLevel = 'NotNano' } - $testComputerInfoNotServer = @{ - OsProductType = 'NotServer' - OsServerLevel = 'NotNano' - } - - Mock -CommandName 'Test-CommandExists' -MockWith { return $true } - Mock -CommandName 'Get-ComputerInfo' -MockWith { return $testComputerInfoNanoServer } - - Context 'Get-ComputerInfo command exists and succeeds' { - Context 'Computer OS type is Server and OS server level is NanoServer' { - It 'Should not throw' { - { $null = Test-IsNanoServer } | Should -Not -Throw - } - - It 'Should test if the Get-ComputerInfo command exists' { - $testCommandExistsParameterFilter = { - return $Name -eq 'Get-ComputerInfo' - } - - Assert-MockCalled -CommandName 'Test-CommandExists' -ParameterFilter $testCommandExistsParameterFilter -Exactly 1 -Scope 'Context' - } - - It 'Should retrieve the computer info' { - Assert-MockCalled -CommandName 'Get-ComputerInfo' -Exactly 1 -Scope 'Context' - } - - It 'Should return true' { - Test-IsNanoServer | Should -Be $true - } + Context 'Computer OS type is Server and OS server level is NanoServer' { + Mock -CommandName 'Test-Path' -MockWith { return $true } + Mock -CommandName 'Get-ItemProperty' -MockWith { return $testComputerInfoNanoServer } + It 'Should not throw' { + { $null = Test-IsNanoServer } | Should -Not -Throw } - Context 'Computer OS type is Server and OS server level is not NanoServer' { - Mock -CommandName 'Get-ComputerInfo' -MockWith { return $testComputerInfoServerNotNano } - - It 'Should not throw' { - { $null = Test-IsNanoServer } | Should -Not -Throw - } - - It 'Should test if the Get-ComputerInfo command exists' { - $testCommandExistsParameterFilter = { - return $Name -eq 'Get-ComputerInfo' - } - - Assert-MockCalled -CommandName 'Test-CommandExists' -ParameterFilter $testCommandExistsParameterFilter -Exactly 1 -Scope 'Context' - } - - It 'Should retrieve the computer info' { - Assert-MockCalled -CommandName 'Get-ComputerInfo' -Exactly 1 -Scope 'Context' - } - - It 'Should return false' { - Test-IsNanoServer | Should -Be $false - } + It 'Should check the ServerLevels registry path' { + Assert-MockCalled -CommandName 'Get-ItemProperty' -Exactly 1 -Scope 'Context' } - Context 'Computer OS type is not Server' { - Mock -CommandName 'Get-ComputerInfo' -MockWith { return $testComputerInfoNotServer } - - It 'Should not throw' { - { $null = Test-IsNanoServer } | Should -Not -Throw - } - - It 'Should test if the Get-ComputerInfo command exists' { - $testCommandExistsParameterFilter = { - return $Name -eq 'Get-ComputerInfo' - } - - Assert-MockCalled -CommandName 'Test-CommandExists' -ParameterFilter $testCommandExistsParameterFilter -Exactly 1 -Scope 'Context' - } - - It 'Should retrieve the computer info' { - Assert-MockCalled -CommandName 'Get-ComputerInfo' -Exactly 1 -Scope 'Context' - } - - It 'Should return false' { - Test-IsNanoServer | Should -Be $false - } + It 'Should return true' { + Test-IsNanoServer | Should -Be $true } } - Context 'Get-ComputerInfo command exists but throws an error and returns null' { - Mock -CommandName 'Get-ComputerInfo' -MockWith { return $null } + Context 'Computer OS type is Server and OS server level is not NanoServer' { + Mock -CommandName 'Test-Path' -MockWith { return $true } + Mock -CommandName 'Get-ItemProperty' -MockWith { return $testComputerInfoServerNotNano } It 'Should not throw' { { $null = Test-IsNanoServer } | Should -Not -Throw } - It 'Should test if the Get-ComputerInfo command exists' { - $testCommandExistsParameterFilter = { - return $Name -eq 'Get-ComputerInfo' - } - - Assert-MockCalled -CommandName 'Test-CommandExists' -ParameterFilter $testCommandExistsParameterFilter -Exactly 1 -Scope 'Context' - } - - It 'Should retrieve the computer info' { - Assert-MockCalled -CommandName 'Get-ComputerInfo' -Exactly 1 -Scope 'Context' + It 'Should check the ServerLevels registry path' { + Assert-MockCalled -CommandName 'Get-ItemProperty' -Exactly 1 -Scope 'Context' } It 'Should return false' { @@ -127,23 +53,15 @@ Describe 'CommonResourceHelper Unit Tests' { } } - Context 'Get-ComputerInfo command does not exist' { - Mock -CommandName 'Test-CommandExists' -MockWith { return $false } + Context 'Computer OS type is not Server' { + Mock -CommandName 'Test-Path' -MockWith { return $false } It 'Should not throw' { { $null = Test-IsNanoServer } | Should -Not -Throw } - It 'Should test if the Get-ComputerInfo command exists' { - $testCommandExistsParameterFilter = { - return $Name -eq 'Get-ComputerInfo' - } - - Assert-MockCalled -CommandName 'Test-CommandExists' -ParameterFilter $testCommandExistsParameterFilter -Exactly 1 -Scope 'Context' - } - - It 'Should not attempt to retrieve the computer info' { - Assert-MockCalled -CommandName 'Get-ComputerInfo' -Exactly 0 -Scope 'Context' + It 'Should check the ServerLevels registry path' { + Assert-MockCalled -CommandName 'Test-Path' -Exactly 1 -Scope 'Context' } It 'Should return false' { diff --git a/Tests/Unit/MSFT_MsiPackage.Tests.ps1 b/Tests/Unit/MSFT_MsiPackage.Tests.ps1 index 8516b3c..13e3eb8 100644 --- a/Tests/Unit/MSFT_MsiPackage.Tests.ps1 +++ b/Tests/Unit/MSFT_MsiPackage.Tests.ps1 @@ -123,7 +123,7 @@ Describe 'MsiPackage Unit Tests' { -ExpectedReturnValue $expectedReturnValue } } - + Describe 'Set-TargetResource' { $setTargetResourceParameters = @{ ProductId = 'TestProductId' @@ -138,7 +138,7 @@ Describe 'MsiPackage Unit Tests' { ServerCertificateValidationCallback = 'TestValidationCallback' RunAsCredential = $script:testCredential } - + Mock -CommandName 'Convert-PathToUri' -MockWith { return $script:testUriNonUnc } Mock -CommandName 'Convert-ProductIdToIdentifyingNumber' -MockWith { return $script:testIdentifyingNumber } Mock -CommandName 'Assert-PathExtensionValid' -MockWith {} @@ -158,7 +158,7 @@ Describe 'MsiPackage Unit Tests' { Mock -CommandName 'Invoke-CimMethod' -MockWith {} Mock -CommandName 'Get-ItemProperty' -MockWith { return $null } Mock -CommandName 'Get-ProductEntry' -MockWith { return $script:mockProductEntry } - + Context 'Uri scheme is non-UNC file and installation succeeds' { $mocksCalled = @( @{ Command = 'Convert-PathToUri'; Times = 1 } @@ -219,13 +219,13 @@ Describe 'MsiPackage Unit Tests' { @{ Command = 'Assert-PathExtensionValid'; Times = 1 } @{ Command = 'New-LogFile'; Times = 1 } @{ Command = 'New-PSDrive'; Times = 0 } - @{ Command = 'Test-Path'; Times = 2; Custom = 'to the package cache' } + @{ Command = 'Test-Path'; Times = 3; Custom = 'to the package cache' } @{ Command = 'New-Item'; Times = 0; Custom = 'directory for the package cache' } @{ Command = 'New-Object'; Times = 1; Custom = 'file stream to copy the response to' } @{ Command = 'Get-WebRequestResponse'; Times = 1 } @{ Command = 'Copy-ResponseStreamToFileStream'; Times = 1 } @{ Command = 'Close-Stream'; Times = 2 } - @{ Command = 'Test-Path'; Times = 2; Custom = 'to the MSI file' } + @{ Command = 'Test-Path'; Times = 3; Custom = 'to the MSI file' } @{ Command = 'Assert-FileValid'; Times = 1 } @{ Command = 'Get-MsiProductCode'; Times = 1 } @{ Command = 'Start-MsiProcess'; Times = 1 } @@ -250,13 +250,13 @@ Describe 'MsiPackage Unit Tests' { @{ Command = 'Assert-PathExtensionValid'; Times = 1 } @{ Command = 'New-LogFile'; Times = 1 } @{ Command = 'New-PSDrive'; Times = 0 } - @{ Command = 'Test-Path'; Times = 2; Custom = 'to the package cache' } + @{ Command = 'Test-Path'; Times = 3; Custom = 'to the package cache' } @{ Command = 'New-Item'; Times = 0; Custom = 'directory for the package cache' } @{ Command = 'New-Object'; Times = 1; Custom = 'file stream to copy the response to' } @{ Command = 'Get-WebRequestResponse'; Times = 1 } @{ Command = 'Copy-ResponseStreamToFileStream'; Times = 1 } @{ Command = 'Close-Stream'; Times = 2 } - @{ Command = 'Test-Path'; Times = 2; Custom = 'to the MSI file' } + @{ Command = 'Test-Path'; Times = 3; Custom = 'to the MSI file' } @{ Command = 'Assert-FileValid'; Times = 1 } @{ Command = 'Get-MsiProductCode'; Times = 1 } @{ Command = 'Start-MsiProcess'; Times = 1 } @@ -349,7 +349,7 @@ Describe 'MsiPackage Unit Tests' { -ErrorMessage ($script:localizedData.PostValidationError -f $setTargetResourceParameters.Path) ` -ErrorTestName $script:errorMessageTitles.PostValidationError } - + Mock -CommandName 'Get-MsiProductCode' -MockWith { return $script:testWrongProductId } Context 'Product code from downloaded MSI package does not match specified ID' { @@ -491,25 +491,27 @@ Describe 'MsiPackage Unit Tests' { Convert-PathToUri -Path $filePath | Should Be $expectedReturnValue } - - It 'Should return the expected URI when scheme is http' { - $filePath = 'http://localhost:1242/testMsi.msi' - $expectedReturnValue = [Uri] $filePath - Convert-PathToUri -Path $filePath | Should Be $expectedReturnValue + It 'Should return the expected URI when scheme is http' { + $uriBuilder = [System.UriBuilder]::new('http', 'localhost') + $uriBuilder.Path = 'testMsi.msi' + $filePath = $uriBuilder.Uri.AbsoluteUri + + Convert-PathToUri -Path $filePath | Should Be $uriBuilder.Uri } It 'Should return the expected URI when scheme is https' { - $filePath = 'https://localhost:1243/testMsi.msi' - $expectedReturnValue = [Uri] $filePath + $uriBuilder = [System.UriBuilder]::new('https', 'localhost') + $uriBuilder.Path = 'testMsi.msi' + $filePath = $uriBuilder.Uri.AbsoluteUri - Convert-PathToUri -Path $filePath | Should Be $expectedReturnValue + Convert-PathToUri -Path $filePath | Should Be $uriBuilder.Uri } } Context 'Invalid path passed in' { It 'Should throw an error when uri scheme is invalid' { - $filePath = 'ht://localhost:1243/testMsi.msi' + $filePath = 'ht://localhost/testMsi.msi' $expectedErrorMessage = ($script:localizedData.InvalidPath -f $filePath) { Convert-PathToUri -Path $filePath } | Should Throw $expectedErrorMessage @@ -686,7 +688,7 @@ Describe 'MsiPackage Unit Tests' { -ErrorTestName $script:errorMessageTitles.CouldNotOpenLog } } - + Describe 'Get-WebRequestResponse' { Mock -CommandName 'Get-WebRequest' -MockWith { return $script:mockWebRequest } Mock -CommandName 'Get-ScriptBlock' -MockWith { return { Write-Verbose 'Hello World' } } @@ -702,7 +704,7 @@ Describe 'MsiPackage Unit Tests' { It 'Should return the expected response stream' { Get-WebRequestResponse -Uri $script:testUriHttp | Should Be $script:mockStream } - + Invoke-ExpectedMocksAreCalledTest -MocksCalled $mocksCalled } @@ -716,7 +718,7 @@ Describe 'MsiPackage Unit Tests' { It 'Should return the expected response stream' { Get-WebRequestResponse -Uri $script:testUriHttps | Should Be $script:mockStream } - + Invoke-ExpectedMocksAreCalledTest -MocksCalled $mocksCalled } @@ -859,7 +861,7 @@ Describe 'MsiPackage Unit Tests' { Describe 'Assert-FileSignatureValid' { $mockThumbprint = 'mockThumbprint' $mockSubject = 'mockSubject' - $mockSignature = @{ + $mockSignature = @{ Status = [System.Management.Automation.SignatureStatus]::Valid SignerCertificate = @{ Thumbprint = $mockThumbprint; Subject = $mockSubject } }