From 33d408aee446ba0cd13a5a16b467ced3f90ffefa Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Thu, 18 Apr 2019 12:39:33 +0100 Subject: [PATCH 01/13] Test-Is NanoServer speed improvement Replace Get-ComputerInfo with Registry Value Test --- DscResources/CommonResourceHelper.psm1 | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/DscResources/CommonResourceHelper.psm1 b/DscResources/CommonResourceHelper.psm1 index f1f9d3a..77018f2 100644 --- a/DscResources/CommonResourceHelper.psm1 +++ b/DscResources/CommonResourceHelper.psm1 @@ -8,23 +8,16 @@ function Test-IsNanoServer [CmdletBinding()] param () - $isNanoServer = $false - - if (Test-CommandExists -Name 'Get-ComputerInfo') + $ServerLevels = Get-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels' + If ($ServerLevels.NanoServer -eq 1) { - $computerInfo = Get-ComputerInfo -ErrorAction 'SilentlyContinue' - - if ($null -ne $computerInfo) - { - $computerIsServer = 'Server' -ieq $computerInfo.OsProductType - - if ($computerIsServer) - { - $isNanoServer = 'NanoServer' -ieq $computerInfo.OsServerLevel - } - } + $isNanoServer = $True } - + Else + { + $isNanoServer = $false + } + return $isNanoServer } From 0d478f955e3ff91f78f7b65ff0259c4c2b999f3c Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Thu, 18 Apr 2019 12:58:44 +0100 Subject: [PATCH 02/13] update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 807a2cb..82abc70 100644 --- a/README.md +++ b/README.md @@ -621,6 +621,7 @@ The following parameters will be the same for each process in the set: to Describe fixtures. * GroupSet * Update tests to meet Pester 4.0.0 standards ([issue #129](https://github.com/PowerShell/PSDscResources/issues/129)). +* Improved speed of Test-IsNanoServer function ### 2.9.0.0 From 9dff32a4dfd26332b33b37bf435baaffba1edc62 Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Thu, 18 Apr 2019 14:16:47 +0100 Subject: [PATCH 03/13] Add check for ServerLevels Reg Key for Non-Server --- DscResources/CommonResourceHelper.psm1 | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/DscResources/CommonResourceHelper.psm1 b/DscResources/CommonResourceHelper.psm1 index 77018f2..eae7c7f 100644 --- a/DscResources/CommonResourceHelper.psm1 +++ b/DscResources/CommonResourceHelper.psm1 @@ -8,16 +8,22 @@ function Test-IsNanoServer [CmdletBinding()] param () - $ServerLevels = Get-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels' - If ($ServerLevels.NanoServer -eq 1) + $ServerLevelsRegKey = 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels' + If (Test-Path $ServerLevelsRegKey) { - $isNanoServer = $True + $ServerLevels = Get-ItemProperty -Path $ServerLevelsRegKey + If ($ServerLevels.NanoServer -eq 1) + { + $isNanoServer = $true + } + Else + { + $isNanoServer = $false + } } - Else - { + Else { $isNanoServer = $false } - return $isNanoServer } From 2a06d5cb283658c2129e794cd3eb80cbdab90aea Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Thu, 18 Apr 2019 14:18:19 +0100 Subject: [PATCH 04/13] Update CommonResourceHelper.Tests --- Tests/Unit/CommonResourceHelper.Tests.ps1 | 132 ++++------------------ 1 file changed, 25 insertions(+), 107 deletions(-) diff --git a/Tests/Unit/CommonResourceHelper.Tests.ps1 b/Tests/Unit/CommonResourceHelper.Tests.ps1 index 241c18f..15fb907 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 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 - } - } - - 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 - } - } - } - - 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 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 } - 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 check the ServerLevels registry path' { + Assert-MockCalled -CommandName 'Get-ItemProperty' -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 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 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' { From 642dfe60acf620be948877a0a4ab2adb5a8b79a1 Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Tue, 23 Apr 2019 07:41:54 +0100 Subject: [PATCH 05/13] Moved comment to unreleased --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 82abc70..390d8b2 100644 --- a/README.md +++ b/README.md @@ -577,6 +577,8 @@ The following parameters will be the same for each process in the set: ### Unreleased +* Improved speed of Test-IsNanoServer function + ### 2.10.0.0 * Fixed CompanyName typo - Fixes [Issue #100](https://github.com/PowerShell/PSDscResources/issues/100) @@ -621,7 +623,6 @@ The following parameters will be the same for each process in the set: to Describe fixtures. * GroupSet * Update tests to meet Pester 4.0.0 standards ([issue #129](https://github.com/PowerShell/PSDscResources/issues/129)). -* Improved speed of Test-IsNanoServer function ### 2.9.0.0 From 01a7cace8d311f53a81839b08fe5f297f5432788 Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Tue, 23 Apr 2019 07:45:25 +0100 Subject: [PATCH 06/13] Make requested changes --- DscResources/CommonResourceHelper.psm1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/DscResources/CommonResourceHelper.psm1 b/DscResources/CommonResourceHelper.psm1 index eae7c7f..95600ba 100644 --- a/DscResources/CommonResourceHelper.psm1 +++ b/DscResources/CommonResourceHelper.psm1 @@ -8,20 +8,20 @@ function Test-IsNanoServer [CmdletBinding()] param () - $ServerLevelsRegKey = 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels' - If (Test-Path $ServerLevelsRegKey) + $serverLevelsRegKey = 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels' + if (Test-Path -Path $serverLevelsRegKey) { - $ServerLevels = Get-ItemProperty -Path $ServerLevelsRegKey - If ($ServerLevels.NanoServer -eq 1) + $serverLevels = Get-ItemProperty -Path $serverLevelsRegKey + if ($serverLevels.NanoServer -eq 1) { $isNanoServer = $true } - Else + else { $isNanoServer = $false } } - Else { + else { $isNanoServer = $false } return $isNanoServer From b99c0592cd7a8bee05982b3a3c11f074976c473a Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Tue, 23 Apr 2019 07:48:06 +0100 Subject: [PATCH 07/13] Remove whitespace --- Tests/Unit/CommonResourceHelper.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Unit/CommonResourceHelper.Tests.ps1 b/Tests/Unit/CommonResourceHelper.Tests.ps1 index 15fb907..09cc5b2 100644 --- a/Tests/Unit/CommonResourceHelper.Tests.ps1 +++ b/Tests/Unit/CommonResourceHelper.Tests.ps1 @@ -39,7 +39,7 @@ Describe 'CommonResourceHelper Unit Tests' { 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 } From 5b69220937a1af4c53039960b9e90baa68c6fdbc Mon Sep 17 00:00:00 2001 From: Mike Hendrickson Date: Thu, 30 May 2019 14:32:51 -0700 Subject: [PATCH 08/13] Revert "Fix msi package tests" --- README.md | 4 - .../MSFT_MsiPackage.EndToEnd.Tests.ps1 | 1158 ++++++++--------- .../MSFT_MsiPackage.Integration.Tests.ps1 | 5 - Tests/TestHelpers/CommonTestHelper.psm1 | 103 +- 4 files changed, 572 insertions(+), 698 deletions(-) diff --git a/README.md b/README.md index 7633f08..3be6947 100644 --- a/README.md +++ b/README.md @@ -577,10 +577,6 @@ 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 - [Issue #142](https://github.com/PowerShell/PSDscResources/issues/142) - ### 2.11.0.0 * Fix Custom DSC Resource Kit PSSA Rule Failures diff --git a/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 b/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 index 78bc29e..5abf0a9 100644 --- a/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 +++ b/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 @@ -13,647 +13,631 @@ 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 + + # 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' + + $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 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' - It 'Should return True from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters - $testTargetResourceInitialResult | Should Be $true + $baseUrl = 'http://localhost:1242/' + $msiUrl = "$baseUrl" + 'package.msi' - 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 - } - } + $fileServerStarted = $null + $job = $null - 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 - } + $msiPackageParameters = @{ + ProductId = $script:packageId + Path = $msiUrl + Ensure = 'Absent' } - Context 'Install package that is not installed yet' { - $configurationName = 'InstallMsiPackage' + It 'Should return False from Test-TargetResource with the same parameters before configuration' { + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult | Should Be $false - $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' - - $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 + if ($testTargetResourceInitialResult -ne $false) { <# - This must be called after Start-Server to ensure the listening port is closed, - otherwise subsequent tests may fail until the machine is rebooted. + 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 #> - 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 + Write-Error -Message $script:environmentInIncorrectStateErrorMessage } } - 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 - } + 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 + try + { + $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false + $fileServerStarted = $serverResult.FileServerStarted + $job = $serverResult.Job - $fileServerStarted.WaitOne(30000) + $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 compile and run configuration' { + { + . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @msiPackageParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw } - finally + } + 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) { <# - This must be called after Start-Server to ensure the listening port is closed, - otherwise subsequent tests may fail until the machine is rebooted. + 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 #> - 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 + Write-Error -Message $script:environmentInIncorrectStateErrorMessage } } - 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 - } + 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 + try + { + $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $true + $fileServerStarted = $serverResult.FileServerStarted + $job = $serverResult.Job - $fileServerStarted.WaitOne(30000) + $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 + 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' + Context 'Uninstall Msi package from HTTPS Url' { + $configurationName = 'UninstallMsiPackageFromHttps' - $baseUrl = 'https://localhost:1243/' - $msiUrl = "$baseUrl" + 'package.msi' + $baseUrl = 'https://localhost:1243/' + $msiUrl = "$baseUrl" + 'package.msi' - $fileServerStarted = $null - $job = $null + $fileServerStarted = $null + $job = $null - $msiPackageParameters = @{ - ProductId = $script:packageId - Path = $msiUrl - Ensure = 'Absent' - } + $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 + 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 + if ($testTargetResourceInitialResult -ne $false) { <# - This must be called after Start-Server to ensure the listening port is closed, - otherwise subsequent tests may fail until the machine is rebooted. + 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 #> - Stop-Server -FileServerStarted $fileServerStarted -Job $job + Write-Error -Message $script:environmentInIncorrectStateErrorMessage } + } - 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 before configuration is run' { + Test-PackageInstalledById -ProductId $script:packageId | Should Be $true + } - It 'Package should not exist on the machine' { - 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 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..740f9bf 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 { @@ -309,6 +306,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..c5671e9 100644 --- a/Tests/TestHelpers/CommonTestHelper.psm1 +++ b/Tests/TestHelpers/CommonTestHelper.psm1 @@ -790,105 +790,6 @@ function Exit-DscResourceTestEnvironment Restore-TestEnvironment -TestEnvironment $TestEnvironment } -<# - .SYNOPSIS - Enables strong crypto for .NET v4.0.30319. Returns a Hashtable - containing the relevant, original registry settings, prior to - modification. -#> -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 -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.Collections.Hashtable] - $OriginalSettings - ) - - foreach ($regPath in $OriginalSettings.Keys) - { - $useStrongCryptoProp = Get-ItemProperty -Path $regPath -Name 'SchUseStrongCrypto' -ErrorAction SilentlyContinue - - # The SchUseStrongCrypto value previously didn't exist, but now does. Delete it. - if ($null -eq $OriginalSettings[$regPath] -and $null -ne $useStrongCryptoProp) - { - 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 - } - } -} - Export-ModuleMember -Function @( 'Test-GetTargetResourceResult', ` 'Wait-ScriptBlockReturnTrue', ` @@ -901,7 +802,5 @@ Export-ModuleMember -Function @( 'Invoke-SetTargetResourceUnitTest', ` 'Invoke-TestTargetResourceUnitTest', ` 'Invoke-ExpectedMocksAreCalledTest', ` - 'Invoke-GenericUnitTest', - 'Enable-StrongCryptoForDotNetFour', - 'Undo-ChangesToStrongCryptoForDotNetFour' + 'Invoke-GenericUnitTest' ) From 7b99a2e0080de6447bc8afadcf4212b4e95f5b64 Mon Sep 17 00:00:00 2001 From: Mike Hendrickson Date: Fri, 31 May 2019 12:56:16 -0700 Subject: [PATCH 09/13] Fix MsiPackage tests... for real this time --- .../MSFT_MsiPackage/MSFT_MsiPackage.psm1 | 2 +- README.md | 10 ++ .../MSFT_MsiPackage.EndToEnd.Tests.ps1 | 31 +++-- .../MSFT_MsiPackage.Integration.Tests.ps1 | 29 ++++- Tests/TestHelpers/CommonTestHelper.psm1 | 79 ++++++++++++- .../MSFT_MsiPackageResource.TestHelper.psm1 | 108 +++++++++++++++--- Tests/Unit/MSFT_MsiPackage.Tests.ps1 | 14 +-- 7 files changed, 235 insertions(+), 38 deletions(-) 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 3be6947..4b3172f 100644 --- a/README.md +++ b/README.md @@ -577,6 +577,16 @@ The following parameters will be the same for each process in the set: ### Unreleased +* 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) + ### 2.11.0.0 * Fix Custom DSC Resource Kit PSSA Rule Failures diff --git a/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 b/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 index 5abf0a9..d50c7c8 100644 --- a/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 +++ b/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 @@ -55,6 +55,9 @@ Describe 'MsiPackage End to End Tests' { $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 } @@ -372,7 +375,7 @@ Describe 'MsiPackage End to End Tests' { Context 'Install package from HTTP Url' { $configurationName = 'UninstallExistingMsiPackageFromHttp' - $baseUrl = 'http://localhost:1242/' + $baseUrl = "http://localhost:$script:testHttpPort/" $msiUrl = "$baseUrl" + 'package.msi' $fileServerStarted = $null @@ -405,7 +408,10 @@ Describe 'MsiPackage End to End Tests' { try { - $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 @@ -440,7 +446,7 @@ Describe 'MsiPackage End to End Tests' { Context 'Uninstall Msi package from HTTP Url' { $configurationName = 'InstallMsiPackageFromHttp' - $baseUrl = 'http://localhost:1242/' + $baseUrl = "http://localhost:$script:testHttpPort/" $msiUrl = "$baseUrl" + 'package.msi' $fileServerStarted = $null @@ -473,7 +479,10 @@ Describe 'MsiPackage End to End Tests' { try { - $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 @@ -508,7 +517,7 @@ Describe 'MsiPackage End to End Tests' { Context 'Install Msi package from HTTPS Url' { $configurationName = 'InstallMsiPackageFromHttpS' - $baseUrl = 'https://localhost:1243/' + $baseUrl = "https://localhost:$script:testHttpsPort/" $msiUrl = "$baseUrl" + 'package.msi' $fileServerStarted = $null @@ -541,7 +550,10 @@ Describe 'MsiPackage End to End Tests' { try { - $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 @@ -576,7 +588,7 @@ Describe 'MsiPackage End to End Tests' { Context 'Uninstall Msi package from HTTPS Url' { $configurationName = 'UninstallMsiPackageFromHttps' - $baseUrl = 'https://localhost:1243/' + $baseUrl = "https://localhost:$script:testHttpsPort/" $msiUrl = "$baseUrl" + 'package.msi' $fileServerStarted = $null @@ -609,7 +621,10 @@ Describe 'MsiPackage End to End Tests' { try { - $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 diff --git a/Tests/Integration/MSFT_MsiPackage.Integration.Tests.ps1 b/Tests/Integration/MSFT_MsiPackage.Integration.Tests.ps1 index 740f9bf..7d68670 100644 --- a/Tests/Integration/MSFT_MsiPackage.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_MsiPackage.Integration.Tests.ps1 @@ -49,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 } @@ -171,7 +174,7 @@ try } It 'Should correctly install and remove a package from a HTTP URL' { - $baseUrl = 'http://localhost:1242/' + $baseUrl = "http://localhost:$script:testHttpPort/" $msiUrl = "$baseUrl" + 'package.msi' $fileServerStarted = $null @@ -181,7 +184,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 @@ -196,6 +202,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 { <# @@ -208,7 +220,7 @@ try It 'Should correctly install and remove a package from a HTTPS URL' -Skip:$script:skipHttpsTest { - $baseUrl = 'https://localhost:1243/' + $baseUrl = "https://localhost:$script:testHttpsPort/" $msiUrl = "$baseUrl" + 'package.msi' $fileServerStarted = $null @@ -218,7 +230,10 @@ 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 @@ -233,6 +248,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 { <# diff --git a/Tests/TestHelpers/CommonTestHelper.psm1 b/Tests/TestHelpers/CommonTestHelper.psm1 index c5671e9..98659f8 100644 --- a/Tests/TestHelpers/CommonTestHelper.psm1 +++ b/Tests/TestHelpers/CommonTestHelper.psm1 @@ -790,6 +790,82 @@ function Exit-DscResourceTestEnvironment Restore-TestEnvironment -TestEnvironment $TestEnvironment } +<# + .SYNOPSIS + 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 Get-UnusedTcpPort +{ + [OutputType([System.UInt16])] + [CmdletBinding()] + param + ( + [Parameter()] + [ValidateScript({$_ -gt 0})] + [System.UInt16] + $LowestPortNumber = 38473, + + [Parameter()] + [ValidateScript({$_ -gt $0})] + [System.UInt16] + $HighestPortNumber = 38799, + + [Parameter()] + [System.UInt16[]] + $ExcludePorts = @() + ) + + if ($HighestPortNumber -lt $LowestPortNumber) + { + throw "HighestPortNumber must be greater than or equal to LowestPortNumber" + } + + [System.UInt16] $unusedPort = 0 + + $usedPorts = (Get-NetTCPConnection).LocalPort | Where-Object -FilterScript {$_ -ge $LowestPortNumber -and $_ -le $HighestPortNumber} + + if (!(Test-Path -Path variable:usedPorts) -or ($usedPorts -eq $null)) + { + $usedPorts = @() + } + + if (!(Test-Path -Path variable:ExcludePorts) -or ($ExcludePorts -eq $null)) + { + $ExcludePorts = @() + } + + for ($i = $LowestPortNumber; $i -le $HighestPortNumber; $i++) + { + if (!($usedPorts.Contains($i)) -and !($ExcludePorts.Contains($i))) + { + $unusedPort = $i + break + } + } + + if ($unusedPort -eq 0) + { + throw "Failed to find unused TCP port between ports $LowestPortNumber and $HighestPortNumber." + } + + return $unusedPort +} + Export-ModuleMember -Function @( 'Test-GetTargetResourceResult', ` 'Wait-ScriptBlockReturnTrue', ` @@ -802,5 +878,6 @@ Export-ModuleMember -Function @( 'Invoke-SetTargetResourceUnitTest', ` 'Invoke-TestTargetResourceUnitTest', ` 'Invoke-ExpectedMocksAreCalledTest', ` - 'Invoke-GenericUnitTest' + 'Invoke-GenericUnitTest', ` + 'Get-UnusedTcpPort' ) diff --git a/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 b/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 index b96befe..8a0b418 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,8 +57,8 @@ 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). #> function Start-Server @@ -74,7 +76,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. @@ -90,7 +102,7 @@ function Start-Server #> $server = { - param($FilePath, $LogPath, $Https) + param($FilePath, $LogPath, $Https, $HttpPort, $HttpsPort) <# .SYNOPSIS @@ -113,12 +125,17 @@ function Start-Server [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) { @@ -163,7 +180,13 @@ function Start-Server 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 @@ -180,7 +203,7 @@ function Start-Server 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 } <# @@ -332,11 +355,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 +372,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' @@ -450,8 +473,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 | fl | Out-String >> $LogPath + + 'Open TCP Connections Info' >> $LogPath + Get-NetTCPConnection | fl | Out-String >> $LogPath + + throw $_ } finally { @@ -461,11 +498,31 @@ function Start-Server } 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 @@ -516,6 +573,22 @@ 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. @@ -1061,4 +1134,5 @@ Export-ModuleMember -Function ` New-TestMsi, ` Start-Server, ` Stop-Server, ` - Test-PackageInstalledById + Test-PackageInstalledById, ` + Stop-EveryTestServerInstance diff --git a/Tests/Unit/MSFT_MsiPackage.Tests.ps1 b/Tests/Unit/MSFT_MsiPackage.Tests.ps1 index 8516b3c..8327faf 100644 --- a/Tests/Unit/MSFT_MsiPackage.Tests.ps1 +++ b/Tests/Unit/MSFT_MsiPackage.Tests.ps1 @@ -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 } @@ -493,14 +493,14 @@ Describe 'MsiPackage Unit Tests' { } It 'Should return the expected URI when scheme is http' { - $filePath = 'http://localhost:1242/testMsi.msi' + $filePath = 'http://localhost/testMsi.msi' $expectedReturnValue = [Uri] $filePath Convert-PathToUri -Path $filePath | Should Be $expectedReturnValue } It 'Should return the expected URI when scheme is https' { - $filePath = 'https://localhost:1243/testMsi.msi' + $filePath = 'https://localhost/testMsi.msi' $expectedReturnValue = [Uri] $filePath Convert-PathToUri -Path $filePath | Should Be $expectedReturnValue @@ -509,7 +509,7 @@ Describe 'MsiPackage Unit Tests' { 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 From c9af459a9f60861309af23ae2537f18d516404fe Mon Sep 17 00:00:00 2001 From: Mike Hendrickson Date: Fri, 31 May 2019 13:03:31 -0700 Subject: [PATCH 10/13] Fix MsiPackage tests... for real this time --- Tests/TestHelpers/CommonTestHelper.psm1 | 2 +- .../MSFT_MsiPackageResource.TestHelper.psm1 | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Tests/TestHelpers/CommonTestHelper.psm1 b/Tests/TestHelpers/CommonTestHelper.psm1 index 98659f8..f110c49 100644 --- a/Tests/TestHelpers/CommonTestHelper.psm1 +++ b/Tests/TestHelpers/CommonTestHelper.psm1 @@ -832,7 +832,7 @@ function Get-UnusedTcpPort if ($HighestPortNumber -lt $LowestPortNumber) { - throw "HighestPortNumber must be greater than or equal to LowestPortNumber" + throw 'HighestPortNumber must be greater than or equal to LowestPortNumber' } [System.UInt16] $unusedPort = 0 diff --git a/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 b/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 index 8a0b418..406ecb7 100644 --- a/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 +++ b/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 @@ -60,6 +60,12 @@ function Test-PackageInstalledById 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 { @@ -113,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 { @@ -176,6 +185,9 @@ 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 { From d60426a648e40f2e1b743cd2f23d15bad3919268 Mon Sep 17 00:00:00 2001 From: Mike Hendrickson Date: Sat, 1 Jun 2019 16:07:38 -0700 Subject: [PATCH 11/13] Fix MsiPackage tests... for real this time - Post Review #1 --- .../MSFT_MsiPackage.EndToEnd.Tests.ps1 | 72 +++++++++-------- .../MSFT_MsiPackage.Integration.Tests.ps1 | 17 ++-- Tests/TestHelpers/CommonTestHelper.psm1 | 34 ++++---- .../MSFT_MsiPackageResource.TestHelper.psm1 | 78 +++++++++---------- Tests/Unit/MSFT_MsiPackage.Tests.ps1 | 32 ++++---- 5 files changed, 122 insertions(+), 111 deletions(-) diff --git a/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 b/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 index d50c7c8..6916d9b 100644 --- a/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 +++ b/Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1 @@ -43,7 +43,7 @@ Describe 'MsiPackage End to End Tests' { <# 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' $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.' @@ -88,7 +88,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should return True from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters $testTargetResourceInitialResult | Should Be $true if ($testTargetResourceInitialResult -ne $true) @@ -107,7 +107,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should compile and run configuration' { - { + { . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName & $configurationName -OutputPath $TestDrive @msiPackageParameters Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force @@ -133,7 +133,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters $testTargetResourceInitialResult | Should Be $false if ($testTargetResourceInitialResult -ne $false) @@ -152,7 +152,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should compile and run configuration' { - { + { . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName & $configurationName -OutputPath $TestDrive @msiPackageParameters Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force @@ -178,7 +178,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should return True from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters $testTargetResourceInitialResult | Should Be $true if ($testTargetResourceInitialResult -ne $true) @@ -197,7 +197,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should compile and run configuration' { - { + { . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName & $configurationName -OutputPath $TestDrive @msiPackageParameters Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force @@ -223,7 +223,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters $testTargetResourceInitialResult | Should Be $false if ($testTargetResourceInitialResult -ne $false) @@ -242,7 +242,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should compile and run configuration' { - { + { . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName & $configurationName -OutputPath $TestDrive @msiPackageParameters Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force @@ -276,7 +276,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters $testTargetResourceInitialResult | Should Be $false if ($testTargetResourceInitialResult -ne $false) @@ -295,7 +295,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should compile and run configuration' { - { + { . $script:configurationFilePathLogPath -ConfigurationName $configurationName & $configurationName -OutputPath $TestDrive @msiPackageParameters Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force @@ -333,7 +333,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters $testTargetResourceInitialResult | Should Be $false if ($testTargetResourceInitialResult -ne $false) @@ -352,7 +352,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should compile and run configuration' { - { + { . $script:configurationFilePathLogPath -ConfigurationName $configurationName & $configurationName -OutputPath $TestDrive @msiPackageParameters Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force @@ -369,14 +369,15 @@ Describe 'MsiPackage End to End Tests' { 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:$script:testHttpPort/" - $msiUrl = "$baseUrl" + 'package.msi' + $uriBuilder = [System.UriBuilder]::new('http', 'localhost', $script:testHttpPort) + $uriBuilder.Path = 'package.msi' + $msiUrl = $uriBuilder.Uri.AbsoluteUri $fileServerStarted = $null $job = $null @@ -388,7 +389,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters $testTargetResourceInitialResult | Should Be $false if ($testTargetResourceInitialResult -ne $false) @@ -413,12 +414,12 @@ Describe 'MsiPackage End to End Tests' { $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false -HttpPort $script:testHttpPort -HttpsPort $script:testHttpsPort $fileServerStarted = $serverResult.FileServerStarted - $job = $serverResult.Job + $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 @@ -446,8 +447,9 @@ Describe 'MsiPackage End to End Tests' { Context 'Uninstall Msi package from HTTP Url' { $configurationName = 'InstallMsiPackageFromHttp' - $baseUrl = "http://localhost:$script:testHttpPort/" - $msiUrl = "$baseUrl" + 'package.msi' + $uriBuilder = [System.UriBuilder]::new('http', 'localhost', $script:testHttpPort) + $uriBuilder.Path = 'package.msi' + $msiUrl = $uriBuilder.Uri.AbsoluteUri $fileServerStarted = $null $job = $null @@ -459,7 +461,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters $testTargetResourceInitialResult | Should Be $false if ($testTargetResourceInitialResult -ne $false) @@ -476,7 +478,7 @@ Describe 'MsiPackage End to End Tests' { It 'Package should exist on the machine before configuration is run' { Test-PackageInstalledById -ProductId $script:packageId | Should Be $true } - + try { # Make sure no existing HTTP(S) test servers are running @@ -489,7 +491,7 @@ Describe 'MsiPackage End to End Tests' { $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 @@ -517,8 +519,9 @@ Describe 'MsiPackage End to End Tests' { Context 'Install Msi package from HTTPS Url' { $configurationName = 'InstallMsiPackageFromHttpS' - $baseUrl = "https://localhost:$script:testHttpsPort/" - $msiUrl = "$baseUrl" + 'package.msi' + $uriBuilder = [System.UriBuilder]::new('https', 'localhost', $script:testHttpsPort) + $uriBuilder.Path = 'package.msi' + $msiUrl = $uriBuilder.Uri.AbsoluteUri $fileServerStarted = $null $job = $null @@ -530,7 +533,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters $testTargetResourceInitialResult | Should Be $false if ($testTargetResourceInitialResult -ne $false) @@ -547,7 +550,7 @@ Describe 'MsiPackage End to End Tests' { 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 @@ -560,7 +563,7 @@ Describe 'MsiPackage End to End Tests' { $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 @@ -584,12 +587,13 @@ Describe 'MsiPackage End to End Tests' { Test-PackageInstalledById -ProductId $script:packageId | Should Be $true } } - + Context 'Uninstall Msi package from HTTPS Url' { $configurationName = 'UninstallMsiPackageFromHttps' - $baseUrl = "https://localhost:$script:testHttpsPort/" - $msiUrl = "$baseUrl" + 'package.msi' + $uriBuilder = [System.UriBuilder]::new('https', 'localhost', $script:testHttpsPort) + $uriBuilder.Path = 'package.msi' + $msiUrl = $uriBuilder.Uri.AbsoluteUri $fileServerStarted = $null $job = $null @@ -601,7 +605,7 @@ Describe 'MsiPackage End to End Tests' { } It 'Should return False from Test-TargetResource with the same parameters before configuration' { - $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters + $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters $testTargetResourceInitialResult | Should Be $false if ($testTargetResourceInitialResult -ne $false) @@ -631,7 +635,7 @@ Describe 'MsiPackage End to End Tests' { $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 diff --git a/Tests/Integration/MSFT_MsiPackage.Integration.Tests.ps1 b/Tests/Integration/MSFT_MsiPackage.Integration.Tests.ps1 index 7d68670..f709f6b 100644 --- a/Tests/Integration/MSFT_MsiPackage.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_MsiPackage.Integration.Tests.ps1 @@ -37,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' @@ -174,8 +174,11 @@ try } It 'Should correctly install and remove a package from a HTTP URL' { - $baseUrl = "http://localhost:$script:testHttpPort/" - $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 @@ -219,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:$script:testHttpsPort/" - $msiUrl = "$baseUrl" + 'package.msi' + $uriBuilder.Path = 'package.msi' + $msiUrl = $uriBuilder.Uri.AbsoluteUri $fileServerStarted = $null $job = $null @@ -235,7 +240,7 @@ try $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) diff --git a/Tests/TestHelpers/CommonTestHelper.psm1 b/Tests/TestHelpers/CommonTestHelper.psm1 index f110c49..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 ) @@ -837,23 +837,27 @@ function Get-UnusedTcpPort [System.UInt16] $unusedPort = 0 - $usedPorts = (Get-NetTCPConnection).LocalPort | Where-Object -FilterScript {$_ -ge $LowestPortNumber -and $_ -le $HighestPortNumber} - - if (!(Test-Path -Path variable:usedPorts) -or ($usedPorts -eq $null)) - { - $usedPorts = @() + [System.Collections.ArrayList] $usedAndExcludedPorts = (Get-NetTCPConnection).LocalPort | Where-Object -FilterScript { + $_ -ge $LowestPortNumber -and $_ -le $HighestPortNumber } - if (!(Test-Path -Path variable:ExcludePorts) -or ($ExcludePorts -eq $null)) + 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 = @() } - for ($i = $LowestPortNumber; $i -le $HighestPortNumber; $i++) + $null = $usedAndExcludedPorts.Add($ExcludePorts) + + foreach ($port in $LowestPortNumber..$HighestPortNumber) { - if (!($usedPorts.Contains($i)) -and !($ExcludePorts.Contains($i))) + if (!($usedAndExcludedPorts.Contains($port))) { - $unusedPort = $i + $unusedPort = $port break } } diff --git a/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 b/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 index 406ecb7..5b3b8f2 100644 --- a/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 +++ b/Tests/TestHelpers/MSFT_MsiPackageResource.TestHelper.psm1 @@ -100,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 @@ -130,7 +130,7 @@ function Start-Server ( [Parameter(Mandatory = $true)] [System.Net.HttpListener] - $HttpListener, + $HttpListener, [Parameter(Mandatory = $true)] [System.Boolean] @@ -203,7 +203,7 @@ function Start-Server # 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' @@ -211,9 +211,9 @@ 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:$HttpsPort certhash=$hash appid='{833f13c2-319a-4799-9d1a-5b267a0c3593}' clientcertnegotiation=enable } @@ -239,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 @@ -284,7 +284,7 @@ function Start-Server .PARAMETER ScriptBlock The code to execute. - + #> function Invoke-ConsoleCommand { @@ -312,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 } @@ -344,7 +344,7 @@ function Start-Server [String] $Message ) - + $Message >> $LogFile } @@ -409,7 +409,7 @@ function Start-Server .PARAMETER Result th IAsyncResult containing the listener object and path to the MSI file. - + #> $requestListener = { @@ -495,10 +495,10 @@ function Start-Server $_.Exception | ConvertTo-Xml -As String >> $LogPath 'Running Process Info' >> $LogPath - Get-Process | fl | Out-String >> $LogPath + Get-Process | Format-List | Out-String >> $LogPath 'Open TCP Connections Info' >> $LogPath - Get-NetTCPConnection | fl | Out-String >> $LogPath + Get-NetTCPConnection | Format-List | Out-String >> $LogPath throw $_ } @@ -508,7 +508,7 @@ function Start-Server { $fileServerStarted.Dispose() } - + Write-Log -LogFile $LogPath -Message 'Stopping the Server' Stop-Listener -HttpListener $HttpListener -Https $Https -HttpsPort $HttpsPort } @@ -537,10 +537,10 @@ function Start-Server } <# - 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 } @@ -570,7 +570,7 @@ function Stop-Server $FileServerStarted, [System.Management.Automation.Job] - $Job + $Job ) if ($null -ne $FileServerStarted) @@ -593,9 +593,7 @@ function Stop-Server function Stop-EveryTestServerInstance { [CmdletBinding()] - param - ( - ) + param () Get-Job -Name "$($testJobPrefix)*" | Stop-Job Get-Job -Name "$($testJobPrefix)*" | Remove-Job @@ -611,7 +609,7 @@ function Stop-EveryTestServerInstance function New-TestMsi { [CmdletBinding()] - param + param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] diff --git a/Tests/Unit/MSFT_MsiPackage.Tests.ps1 b/Tests/Unit/MSFT_MsiPackage.Tests.ps1 index 8327faf..86cf862 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 } @@ -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,19 +491,19 @@ Describe 'MsiPackage Unit Tests' { Convert-PathToUri -Path $filePath | Should Be $expectedReturnValue } - - It 'Should return the expected URI when scheme is http' { - $filePath = 'http://localhost/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' + + Convert-PathToUri -Path $filePath | Should Be $uriBuilder.Uri } It 'Should return the expected URI when scheme is https' { - $filePath = 'https://localhost/testMsi.msi' - $expectedReturnValue = [Uri] $filePath + $uriBuilder = [System.UriBuilder]::new('https', 'localhost') + $uriBuilder.Path = 'testMsi.msi' - Convert-PathToUri -Path $filePath | Should Be $expectedReturnValue + Convert-PathToUri -Path $filePath | Should Be $uriBuilder.Uri } } @@ -686,7 +686,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 +702,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 +716,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 +859,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 } } From deaa8687f931cc6596bd20cd51769bdfa6fbf509 Mon Sep 17 00:00:00 2001 From: Mike Hendrickson Date: Sat, 1 Jun 2019 17:09:45 -0700 Subject: [PATCH 12/13] Fix MsiPackage tests... for real this time - Post Review #1 --- Tests/Unit/MSFT_MsiPackage.Tests.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/Unit/MSFT_MsiPackage.Tests.ps1 b/Tests/Unit/MSFT_MsiPackage.Tests.ps1 index 86cf862..13e3eb8 100644 --- a/Tests/Unit/MSFT_MsiPackage.Tests.ps1 +++ b/Tests/Unit/MSFT_MsiPackage.Tests.ps1 @@ -495,6 +495,7 @@ Describe 'MsiPackage Unit Tests' { 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 } @@ -502,6 +503,7 @@ Describe 'MsiPackage Unit Tests' { It 'Should return the expected URI when scheme is https' { $uriBuilder = [System.UriBuilder]::new('https', 'localhost') $uriBuilder.Path = 'testMsi.msi' + $filePath = $uriBuilder.Uri.AbsoluteUri Convert-PathToUri -Path $filePath | Should Be $uriBuilder.Uri } From 4db36d52e8a3292ccae879ad0f5ebf4ae2081e04 Mon Sep 17 00:00:00 2001 From: Mike Hendrickson Date: Sun, 2 Jun 2019 18:15:24 -0700 Subject: [PATCH 13/13] Update CommonResourceHelper.psm1 --- DscResources/CommonResourceHelper.psm1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DscResources/CommonResourceHelper.psm1 b/DscResources/CommonResourceHelper.psm1 index 95600ba..6854e31 100644 --- a/DscResources/CommonResourceHelper.psm1 +++ b/DscResources/CommonResourceHelper.psm1 @@ -9,9 +9,11 @@ function Test-IsNanoServer param () $serverLevelsRegKey = 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels' + if (Test-Path -Path $serverLevelsRegKey) { $serverLevels = Get-ItemProperty -Path $serverLevelsRegKey + if ($serverLevels.NanoServer -eq 1) { $isNanoServer = $true @@ -21,9 +23,11 @@ function Test-IsNanoServer $isNanoServer = $false } } - else { + else + { $isNanoServer = $false } + return $isNanoServer }