From 61285842ccdd8025970da3a5eb73f69526cac8a9 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 21 May 2017 23:10:42 +0200 Subject: [PATCH] Changes to xSQLServerSetup BREAKING CHANGE: Replaced StartWin32Process helper function with the cmdlet Start-Process (issue #41, #93 and #126). --- CHANGELOG.md | 2 + .../MSFT_xSQLServerSetup.psm1 | 25 +- Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 | 308 ++++---- en-US/xPDT.strings.psd1 | 11 - xPDT.psm1 | 708 ------------------ 5 files changed, 187 insertions(+), 867 deletions(-) delete mode 100644 en-US/xPDT.strings.psd1 delete mode 100644 xPDT.psm1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 22c7602b37..079a7ecf1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,8 @@ - Removed helper function Grant-ServerPerms because the deprecated resource that was using it was removed. - Removed helper function Grant-CNOPerms because the deprecated resource that was using it was removed. - Removed helper function New-ListenerADObject because the deprecated resource that was using it was removed. +- Changes to xSQLServerSetup + - BREAKING CHANGE: Replaced StartWin32Process helper function with the cmdlet Start-Process (issue #41, #93 and #126). ## 7.0.0.0 diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 index fc7baed760..0bf6b59023 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 @@ -1,6 +1,5 @@ $script:currentPath = Split-Path -Path $MyInvocation.MyCommand.Path -Parent Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $script:currentPath -Parent) -Parent) -ChildPath 'xSQLServerHelper.psm1') -Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $script:currentPath -Parent) -Parent) -ChildPath 'xPDT.psm1') <# .SYNOPSIS @@ -851,7 +850,7 @@ function Set-TargetResource $mediaDestinationPath = Join-Path -Path (Get-TemporaryFolder) -ChildPath $mediaDestinationFolder New-VerboseMessage -Message "Robocopy is copying media from source '$SourcePath' to destination '$mediaDestinationPath'" - Copy-ItemWithRoboCopy -Path $SourcePath -DestinationPath $mediaDestinationPath + Copy-ItemWithRobocopy -Path $SourcePath -DestinationPath $mediaDestinationPath Remove-SmbMapping -RemotePath $SourcePath -Force @@ -1323,8 +1322,8 @@ function Set-TargetResource $arguments = $arguments.Trim() $processArguments = @{ - Path = $pathToSetupExecutable - Arguments = $arguments + FilePath = $pathToSetupExecutable + ArgumentList = $arguments } if ($Action -in @('InstallFailoverCluster','AddNode')) @@ -1332,11 +1331,21 @@ function Set-TargetResource $processArguments.Add('Credential',$SetupCredential) } - $process = StartWin32Process @processArguments + $sqlSetupProcess = Start-Process @processArguments -PassThru -Wait -NoNewWindow + Wait-Process -InputObject $sqlSetupProcess -Timeout 120 - New-VerboseMessage -Message $process + $processExitCode = $sqlSetupProcess.ExitCode + $setupExitMessage = "Setup exited with code '$processExitCode'." - WaitForWin32ProcessEnd -Path $pathToSetupExecutable -Arguments $arguments + if ($processExitCode -ne 0) { + $setupExitMessage += ' Please see the ''Summary.txt'' log file in the ''Setup Bootstrap\Log'' folder.' + + throw $setupExitMessage + } + else + { + Write-Verbose $setupExitMessage + } if ($ForceReboot -or ($null -ne (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name 'PendingFileRenameOperations' -ErrorAction SilentlyContinue))) { @@ -1796,7 +1805,7 @@ function Get-FirstItemPropertyValue .PARAMETER DestinationPath The path to the destination. #> -function Copy-ItemWithRoboCopy +function Copy-ItemWithRobocopy { [CmdletBinding()] param diff --git a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 index 4043fc0ed1..bd36f789e0 100644 --- a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 @@ -584,9 +584,9 @@ try } $mockRobocopyExecutableName = 'Robocopy.exe' - $mockRobocopyExectuableVersionWithoutUnbufferedIO = '6.2.9200.00000' - $mockRobocopyExectuableVersionWithUnbufferedIO = '6.3.9600.16384' - $mockRobocopyExectuableVersion = '' # Set dynamically during runtime + $mockRobocopyExecutableVersionWithoutUnbufferedIO = '6.2.9200.00000' + $mockRobocopyExecutableVersionWithUnbufferedIO = '6.3.9600.16384' + $mockRobocopyExecutableVersion = '' # Set dynamically during runtime $mockRobocopyArgumentSilent = '/njh /njs /ndl /nc /ns /nfl' $mockRobocopyArgumentCopySubDirectoriesIncludingEmpty = '/e' $mockRobocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource = '/purge' @@ -601,7 +601,7 @@ try Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockRobocopyExecutableName -PassThru | Add-Member ScriptProperty FileVersionInfo { return @( ( New-Object Object | - Add-Member -MemberType NoteProperty -Name 'ProductVersion' -Value $mockRobocopyExectuableVersion -PassThru -Force + Add-Member -MemberType NoteProperty -Name 'ProductVersion' -Value $mockRobocopyExecutableVersion -PassThru -Force ) ) } -PassThru -Force ) @@ -611,7 +611,7 @@ try $mockStartProcessExpectedArgument = '' # Set dynamically during runtime $mockStartProcessExitCode = 0 # Set dynamically during runtime - $mockStartProcess = { + $mockStartProcess_Robocopy = { if ( $ArgumentList -cne $mockStartProcessExpectedArgument ) { throw "Expected arguments was not the same as the arguments in the function call.`nExpected: '$mockStartProcessExpectedArgument' `n But was: '$ArgumentList'" @@ -621,7 +621,7 @@ try Add-Member -MemberType NoteProperty -Name 'ExitCode' -Value 0 -PassThru -Force } - $mockStartProcess_WithExitCode = { + $mockStartProcess_Robocopy_WithExitCode = { return New-Object Object | Add-Member -MemberType NoteProperty -Name 'ExitCode' -Value $mockStartProcessExitCode -PassThru -Force } @@ -825,11 +825,11 @@ try Needed a way to see into the Set-method for the arguments the Set-method is building and sending to 'setup.exe', and fail the test if the arguments is different from the expected arguments. Solved this by dynamically set the expected arguments before each It-block. If the arguments differs the mock of - StartWin32Process throws an error message, similiar to what Pester would have reported (expected -> but was). + Start-Process throws an error message, similar to what Pester would have reported (expected -> but was). #> - $mockStartWin32ProcessExpectedArgument = @{} + $mockStartProcessExpectedArgument = @{} - $mockStartWin32ProcessExpectedArgumentClusterDefault = @{ + $mockStartProcessExpectedArgumentClusterDefault = @{ IAcceptSQLServerLicenseTerms = 'True' Quiet = 'True' InstanceName = 'MSSQLSERVER' @@ -838,11 +838,19 @@ try FailoverClusterGroup = 'SQL Server (MSSQLSERVER)' } - $mockStartWin32Process = { + $mockStartProcess_ParameterFilter = { + <# + The mock for Start-Process fakes a System.Diagnostics.Process using Start-Process. + We don't want to mock that. + #> + $FilePath -ne 'whoami' + } + + $mockStartProcess = { $argumentHashTable = @{} # Break the argument string into a hash table - ($Arguments -split ' ?/') | ForEach-Object { + ($ArgumentList -split ' ?/') | ForEach-Object { if ($_ -imatch '(\w+)="?([^/]+)"?') { $key = $Matches[1] @@ -859,27 +867,56 @@ try } } + $actualValues = $argumentHashTable.Clone() + # Start by checking whether we have the same number of parameters Write-Verbose 'Verifying setup argument count (expected vs actual)' -Verbose - Write-Verbose -Message ('Expected: {0}' -f ($mockStartWin32ProcessExpectedArgument.Keys -join ',') ) -Verbose - Write-Verbose -Message ('Actual: {0}' -f ($argumentHashTable.Keys -join ',')) -Verbose + Write-Verbose -Message ('Expected: {0}' -f ($mockStartProcessExpectedArgument.Keys -join ',') ) -Verbose + Write-Verbose -Message ('Actual: {0}' -f ($actualValues.Keys -join ',')) -Verbose - $argumentHashTable.Keys.Count | Should BeExactly $mockStartWin32ProcessExpectedArgument.Keys.Count + $numberOfActualValues = $actualValues.Count + $numberOfExpectedValues = $mockStartProcessExpectedArgument.Count + + $numberOfActualValues | Should Be $numberOfExpectedValues Write-Verbose 'Verifying actual setup arguments against expected setup arguments' -Verbose - foreach ($argumentKey in $mockStartWin32ProcessExpectedArgument.Keys) + foreach ($argumentKey in $mockStartProcessExpectedArgument.Keys) { $argumentKeyName = $argumentHashTable.GetEnumerator() | Where-Object -FilterScript { $_.Name -eq $argumentKey } | Select-Object -ExpandProperty Name $argumentKeyName | Should Be $argumentKey $argumentValue = $argumentHashTable.$argumentKey - $argumentValue | Should Be $mockStartWin32ProcessExpectedArgument.$argumentKey + $argumentValue | Should Be $mockStartProcessExpectedArgument.$argumentKey } - return 'Process Started' + <# + Because Wait-Process expects the type System.Diagnostics.Process[], and the + type cannot be easily mocked. A try was made using New-Object to create an + dummy object, but since the property ExitCode is read only it was not + possible. So a workaround is to start a real process with a command that is + harmless ('whoami') which will populate ExitCode correctly. + Redirecting output to a temporary file so that the output (name) from whoami + is not visible in the console. + #> + $temporaryFileForOutput = New-TemporaryFile + $setupProcess = Start-Process -FilePath 'whoami' -PassThru -Wait -NoNewWindow -RedirectStandardOutput $temporaryFileForOutput.FullName + Remove-Item $temporaryFileForOutput.FullName -Force + + return $setupProcess } #endregion Function mocks + $mockStartProcess_SetupCredential = { + $Credential | Should Not BeNullOrEmpty + + # See comment in script block for $mockStartProcess (didn't want to duplicate comment) + $temporaryFileForOutput = New-TemporaryFile + $setupProcess = Start-Process -FilePath 'whoami' -PassThru -Wait -NoNewWindow -RedirectStandardOutput $temporaryFileForOutput.FullName + Remove-Item $temporaryFileForOutput.FullName -Force + + return $setupProcess + } + # Default parameters that are used for the It-blocks $mockDefaultParameters = @{ SetupCredential = $mockSetupCredential @@ -2864,8 +2901,8 @@ try $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\Tools\Setup\Client_Components_Full" } -MockWith $mockGetItemProperty_ClientComponentsFull_FeatureList -Verifiable - Mock -CommandName StartWin32Process -MockWith $mockStartWin32Process -Verifiable - Mock -CommandName WaitForWin32ProcessEnd -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartProcess -ParameterFilter $mockStartProcess_ParameterFilter -Verifiable + Mock -CommandName Wait-Process -Verifiable Mock -CommandName Test-TargetResource -MockWith { return $true } -Verifiable @@ -2887,7 +2924,7 @@ try BeforeEach { Mock -CommandName New-SmbMapping -Verifiable Mock -CommandName Remove-SmbMapping -Verifiable - Mock -CommandName Start-Process -Verifiable + Mock -CommandName Copy-ItemWithRobocopy -Verifiable Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable Mock -CommandName New-Guid -MockWith $mockNewGuid -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable @@ -2924,7 +2961,7 @@ try $testParameters.Features = $testParameters.Features -replace ',SSMS,ADV_SSMS','' } - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' Action = 'Install' @@ -2946,7 +2983,7 @@ try Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-TemporaryFolder -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName New-Guid -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Start-Process -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Copy-ItemWithRobocopy -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -2965,8 +3002,8 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -2981,7 +3018,7 @@ try } $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = @{} + $mockStartProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } @@ -2995,7 +3032,7 @@ try } $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = @{} + $mockStartProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } @@ -3012,7 +3049,7 @@ try $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' Action = 'Install' @@ -3040,8 +3077,8 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -3055,7 +3092,7 @@ try $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' Action = 'Install' @@ -3084,8 +3121,8 @@ try } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } } @@ -3120,7 +3157,7 @@ try Mock -CommandName New-SmbMapping -Verifiable Mock -CommandName Remove-SmbMapping -Verifiable - Mock -CommandName Start-Process -Verifiable + Mock -CommandName Copy-ItemWithRobocopy -Verifiable Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable Mock -CommandName New-Guid -MockWith $mockNewGuid -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable @@ -3138,7 +3175,7 @@ try It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' Action = 'Install' @@ -3155,7 +3192,7 @@ try Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 2 -Scope It Assert-MockCalled -CommandName Get-TemporaryFolder -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName New-Guid -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Copy-ItemWithRobocopy -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -3175,8 +3212,8 @@ try } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -3184,14 +3221,14 @@ try { It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = '' + $mockStartProcessExpectedArgument = '' { Set-TargetResource @testParameters } | Should Throw "'SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } It 'Should throw when feature parameter contains ''ADV_SSMS'' when installing SQL Server 2016' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = '' + $mockStartProcessExpectedArgument = '' { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } @@ -3201,7 +3238,7 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' Action = 'Install' @@ -3230,15 +3267,15 @@ try } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' Action = 'Install' @@ -3265,8 +3302,8 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } } @@ -3290,7 +3327,7 @@ try Mock -CommandName New-SmbMapping -Verifiable Mock -CommandName Remove-SmbMapping -Verifiable - Mock -CommandName Start-Process -Verifiable + Mock -CommandName Copy-ItemWithRobocopy -Verifiable Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable Mock -CommandName New-Guid -MockWith $mockNewGuid -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable @@ -3307,7 +3344,7 @@ try } It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' Action = 'Install' @@ -3324,7 +3361,7 @@ try Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 2 -Scope It Assert-MockCalled -CommandName Get-TemporaryFolder -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName New-Guid -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Copy-ItemWithRobocopy -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -3344,8 +3381,8 @@ try } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -3353,14 +3390,14 @@ try { It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = @{} + $mockStartProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } It 'Should throw when feature parameter contains ''ADV_SSMS'' when installing SQL Server 2016' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = @{} + $mockStartProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } @@ -3370,7 +3407,7 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' Action = 'Install' @@ -3399,15 +3436,15 @@ try } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' Action = 'Install' @@ -3434,8 +3471,8 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } } @@ -3479,7 +3516,7 @@ try } It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' Action = 'Install' @@ -3511,8 +3548,8 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -3520,14 +3557,14 @@ try { It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { $testParameters.Features = $($testParameters.Features), 'SSMS' -join ',' - $mockStartWin32ProcessExpectedArgument = @{} + $mockStartProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } It 'Should throw when feature parameter contains ''ADV_SSMS'' when installing SQL Server 2016' { $testParameters.Features = $($testParameters.Features), 'ADV_SSMS' -join ',' - $mockStartWin32ProcessExpectedArgument = @{} + $mockStartProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } @@ -3537,7 +3574,7 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' Action = 'Install' @@ -3565,15 +3602,15 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' Action = 'Install' @@ -3600,8 +3637,8 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } } @@ -3655,7 +3692,7 @@ try } It 'Should pass proper parameters to setup' { - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ IAcceptSQLServerLicenseTerms = 'True' Quiet = 'True' Action = 'AddNode' @@ -3672,13 +3709,8 @@ try { Set-TargetResource @testParameters } | Should Not Throw } - It 'Should pass the SetupCredential object to the StartWin32Process function' { - $mockStartWin32Process_SetupCredential = { - $Credential | Should Not BeNullOrEmpty - return 'Process started.' - } - - Mock -CommandName StartWin32Process -MockWith $mockStartWin32Process_SetupCredential + It 'Should pass the SetupCredential object to the Start-Process function' { + Mock -CommandName Start-Process -MockWith $mockStartProcess_SetupCredential -ParameterFilter $mockStartProcess_ParameterFilter -Verifiable { Set-TargetResource @testParameters } | Should Not Throw } @@ -3761,8 +3793,8 @@ try } It 'Should pass proper parameters to setup' { - $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() - $mockStartWin32ProcessExpectedArgument += @{ + $mockStartProcessExpectedArgument = $mockStartProcessExpectedArgumentClusterDefault.Clone() + $mockStartProcessExpectedArgument += @{ Action = 'InstallFailoverCluster' FailoverClusterDisks = 'Backup; SysData; TempDbData; TempDbLogs; UserData; UserLogs' FailoverClusterIPAddresses = $mockDefaultInstance_FailoverClusterIPAddressParameter_SingleSite @@ -3780,8 +3812,8 @@ try } It 'Should pass proper parameters to setup when only InstallSQLDataDir is assigned a path' { - $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() - $mockStartWin32ProcessExpectedArgument += @{ + $mockStartProcessExpectedArgument = $mockStartProcessExpectedArgumentClusterDefault.Clone() + $mockStartProcessExpectedArgument += @{ Action = 'InstallFailoverCluster' FailoverClusterDisks = 'SysData' FailoverClusterIPAddresses = $mockDefaultInstance_FailoverClusterIPAddressParameter_SingleSite @@ -3801,8 +3833,8 @@ try } It 'Should pass proper parameters to setup when three variables are assigned the same drive, but different paths' { - $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() - $mockStartWin32ProcessExpectedArgument += @{ + $mockStartProcessExpectedArgument = $mockStartProcessExpectedArgumentClusterDefault.Clone() + $mockStartProcessExpectedArgument += @{ Action = 'InstallFailoverCluster' FailoverClusterDisks = 'SysData' FailoverClusterIPAddresses = $mockDefaultInstance_FailoverClusterIPAddressParameter_SingleSite @@ -3825,13 +3857,8 @@ try { Set-TargetResource @setTargetResourceParameters } | Should Not Throw } - It 'Should pass the SetupCredential object to the StartWin32Process function' { - $mockStartWin32Process_SetupCredential = { - $Credential | Should Not BeNullOrEmpty - return 'Process started.' - } - - Mock -CommandName StartWin32Process -MockWith $mockStartWin32Process_SetupCredential + It 'Should pass the SetupCredential object to the Start-Process function' { + Mock -CommandName Start-Process -MockWith $mockStartProcess_SetupCredential -ParameterFilter $mockStartProcess_ParameterFilter -Verifiable { Set-TargetResource @testParameters } | Should Not Throw } @@ -3846,8 +3873,8 @@ try } It 'Should properly map paths to clustered disk resources' { - $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() - $mockStartWin32ProcessExpectedArgument += @{ + $mockStartProcessExpectedArgument = $mockStartProcessExpectedArgumentClusterDefault.Clone() + $mockStartProcessExpectedArgument += @{ Action = 'InstallFailoverCluster' FailoverClusterIPAddresses = $mockDefaultInstance_FailoverClusterIPAddressParameter_SingleSite InstallSQLDataDir = $mockDynamicSqlDataDirectoryPath @@ -3868,8 +3895,8 @@ try $missingNetworkParams = $testParameters.Clone() $missingNetworkParams.Remove('FailoverClusterIPAddress') - $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() - $mockStartWin32ProcessExpectedArgument += @{ + $mockStartProcessExpectedArgument = $mockStartProcessExpectedArgumentClusterDefault.Clone() + $mockStartProcessExpectedArgument += @{ Action = 'InstallFailoverCluster' FailoverClusterIPAddresses = 'DEFAULT' FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName @@ -3910,8 +3937,8 @@ try It 'Should build a valid IP address string for a single address' { - $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() - $mockStartWin32ProcessExpectedArgument += @{ + $mockStartProcessExpectedArgument = $mockStartProcessExpectedArgumentClusterDefault.Clone() + $mockStartProcessExpectedArgument += @{ FailoverClusterIPAddresses = $mockDefaultInstance_FailoverClusterIPAddressParameter_SingleSite FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName InstallSQLDataDir = $mockDynamicSqlDataDirectoryPath @@ -3935,8 +3962,8 @@ try FailoverClusterIPAddress = ($mockClusterSites | ForEach-Object { $_.Address }) } - $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() - $mockStartWin32ProcessExpectedArgument += @{ + $mockStartProcessExpectedArgument = $mockStartProcessExpectedArgumentClusterDefault.Clone() + $mockStartProcessExpectedArgument += @{ FailoverClusterIPAddresses = $mockDefaultInstance_FailoverClusterIPAddressParameter_MultiSite FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName InstallSQLDataDir = $mockDynamicSqlDataDirectoryPath @@ -3963,7 +3990,7 @@ try $csvTestParameters['SQLTempDBLogDir'] = $mockCSVClusterDiskMap['TempDBLogs'].Path $csvTestParameters['SQLBackupDir'] = $mockCSVClusterDiskMap['Backup'].Path - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ IAcceptSQLServerLicenseTerms = 'True' SkipRules = 'Cluster_VerifyForErrors' Quiet = 'True' @@ -3996,7 +4023,7 @@ try $csvTestParameters['SQLTempDBLogDir'] = $mockCSVClusterDiskMap['UserData'].Path + '\TEMPDBLOG' $csvTestParameters['SQLBackupDir'] = $mockCSVClusterDiskMap['Backup'].Path + '\Backup' - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ IAcceptSQLServerLicenseTerms = 'True' SkipRules = 'Cluster_VerifyForErrors' Quiet = 'True' @@ -4034,8 +4061,9 @@ try Action = 'PrepareFailoverCluster' } - Mock -CommandName NetUse -Verifiable - Mock -CommandName Copy-ItemWithRoboCopy -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Copy-ItemWithRobocopy -Verifiable Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable @@ -4055,7 +4083,7 @@ try $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' } -MockWith $mockGetItemProperty_Setup -Verifiable - Mock -CommandName StartWin32Process -MockWith $mockStartWin32Process -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartProcess -ParameterFilter $mockStartProcess_ParameterFilter -Verifiable Mock -CommandName Get-CimInstance -MockWith {} -ParameterFilter { ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_ResourceGroup') -and ($Filter -eq "Name = 'Available Storage'") @@ -4080,13 +4108,13 @@ try It 'Should pass correct arguments to the setup process' { - $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() - $mockStartWin32ProcessExpectedArgument += @{ + $mockStartProcessExpectedArgument = $mockStartProcessExpectedArgumentClusterDefault.Clone() + $mockStartProcessExpectedArgument += @{ Action = 'PrepareFailoverCluster' SkipRules = 'Cluster_VerifyForErrors' } - $mockStartWin32ProcessExpectedArgument.Remove('FailoverClusterGroup') - $mockStartWin32ProcessExpectedArgument.Remove('SQLSysAdminAccounts') + $mockStartProcessExpectedArgument.Remove('FailoverClusterGroup') + $mockStartProcessExpectedArgument.Remove('SQLSysAdminAccounts') { Set-TargetResource @testParameters } | Should Not throw @@ -4097,8 +4125,8 @@ try $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Wait-Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { @@ -4194,8 +4222,8 @@ try It 'Should properly map paths to clustered disk resources' { - $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() - $mockStartWin32ProcessExpectedArgument += @{ + $mockStartProcessExpectedArgument = $mockStartProcessExpectedArgumentClusterDefault.Clone() + $mockStartProcessExpectedArgument += @{ Action = 'CompleteFailoverCluster' FailoverClusterIPAddresses = $mockDefaultInstance_FailoverClusterIPAddressParameter_SingleSite InstallSQLDataDir = $mockDynamicSqlDataDirectoryPath @@ -4216,8 +4244,8 @@ try $missingNetworkParams = $testParameters.Clone() $missingNetworkParams.Remove('FailoverClusterIPAddress') - $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() - $mockStartWin32ProcessExpectedArgument += @{ + $mockStartProcessExpectedArgument = $mockStartProcessExpectedArgumentClusterDefault.Clone() + $mockStartProcessExpectedArgument += @{ Action = 'CompleteFailoverCluster' FailoverClusterIPAddresses = 'DEFAULT' FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName @@ -4258,8 +4286,8 @@ try It 'Should build a valid IP address string for a single address' { - $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() - $mockStartWin32ProcessExpectedArgument += @{ + $mockStartProcessExpectedArgument = $mockStartProcessExpectedArgumentClusterDefault.Clone() + $mockStartProcessExpectedArgument += @{ FailoverClusterIPAddresses = $mockDefaultInstance_FailoverClusterIPAddressParameter_SingleSite FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName InstallSQLDataDir = $mockDynamicSqlDataDirectoryPath @@ -4283,8 +4311,8 @@ try FailoverClusterIPAddress = ($mockClusterSites | ForEach-Object { $_.Address }) } - $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() - $mockStartWin32ProcessExpectedArgument += @{ + $mockStartProcessExpectedArgument = $mockStartProcessExpectedArgumentClusterDefault.Clone() + $mockStartProcessExpectedArgument += @{ FailoverClusterIPAddresses = $mockDefaultInstance_FailoverClusterIPAddressParameter_MultiSite FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName InstallSQLDataDir = $mockDynamicSqlDataDirectoryPath @@ -4302,7 +4330,7 @@ try } It 'Should pass proper parameters to setup' { - $mockStartWin32ProcessExpectedArgument = @{ + $mockStartProcessExpectedArgument = @{ IAcceptSQLServerLicenseTerms = 'True' SkipRules = 'Cluster_VerifyForErrors' Quiet = 'True' @@ -4333,16 +4361,16 @@ try } # Tests only the parts of the code that does not already get tested thru the other tests. - Describe 'Copy-ItemWithRoboCopy' -Tag 'Helper' { - Context 'When Copy-ItemWithRoboCopy is called it should return the correct arguments' { + Describe 'Copy-ItemWithRobocopy' -Tag 'Helper' { + Context 'When Copy-ItemWithRobocopy is called it should return the correct arguments' { BeforeEach { Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable - Mock -CommandName Start-Process -MockWith $mockStartProcess -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartProcess_Robocopy -Verifiable } It 'Should use Unbuffered IO when copying' { - $mockRobocopyExectuableVersion = $mockRobocopyExectuableVersionWithUnbufferedIO + $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithUnbufferedIO $mockStartProcessExpectedArgument = $mockRobocopyArgumentSourcePath, @@ -4357,14 +4385,14 @@ try DestinationPath = $mockRobocopyArgumentDestinationPath } - { Copy-ItemWithRoboCopy @copyItemWithRoboCopyParameter } | Should Not Throw + { Copy-ItemWithRobocopy @copyItemWithRoboCopyParameter } | Should Not Throw Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It } It 'Should not use Unbuffered IO when copying' { - $mockRobocopyExectuableVersion = $mockRobocopyExectuableVersionWithoutUnbufferedIO + $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithoutUnbufferedIO $mockStartProcessExpectedArgument = $mockRobocopyArgumentSourcePath, @@ -4379,19 +4407,19 @@ try DestinationPath = $mockRobocopyArgumentDestinationPath } - { Copy-ItemWithRoboCopy @copyItemWithRoboCopyParameter } | Should Not Throw + { Copy-ItemWithRobocopy @copyItemWithRoboCopyParameter } | Should Not Throw Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It } } - Context 'When Copy-ItemWithRoboCopy throws an exception it should return the correct error messages' { + Context 'When Copy-ItemWithRobocopy throws an exception it should return the correct error messages' { BeforeEach { - $mockRobocopyExectuableVersion = $mockRobocopyExectuableVersionWithUnbufferedIO + $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithUnbufferedIO Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable - Mock -CommandName Start-Process -MockWith $mockStartProcess_WithExitCode -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartProcess_Robocopy_WithExitCode -Verifiable } It 'Should throw the correct error message when error code is 8' { @@ -4402,7 +4430,7 @@ try DestinationPath = $mockRobocopyArgumentDestinationPath } - { Copy-ItemWithRoboCopy @copyItemWithRoboCopyParameter } | Should Throw "Robocopy reported errors when copying files. Error code: $mockStartProcessExitCode." + { Copy-ItemWithRobocopy @copyItemWithRoboCopyParameter } | Should Throw "Robocopy reported errors when copying files. Error code: $mockStartProcessExitCode." Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It @@ -4416,7 +4444,7 @@ try DestinationPath = $mockRobocopyArgumentDestinationPath } - { Copy-ItemWithRoboCopy @copyItemWithRoboCopyParameter } | Should Throw "Robocopy reported errors when copying files. Error code: $mockStartProcessExitCode." + { Copy-ItemWithRobocopy @copyItemWithRoboCopyParameter } | Should Throw "Robocopy reported errors when copying files. Error code: $mockStartProcessExitCode." Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It @@ -4430,22 +4458,22 @@ try DestinationPath = $mockRobocopyArgumentDestinationPath } - { Copy-ItemWithRoboCopy @copyItemWithRoboCopyParameter } | Should Throw "Robocopy reported that failures occured when copying files. Error code: $mockStartProcessExitCode." + { Copy-ItemWithRobocopy @copyItemWithRoboCopyParameter } | Should Throw "Robocopy reported that failures occured when copying files. Error code: $mockStartProcessExitCode." Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It } } - Context 'When Copy-ItemWithRoboCopy is called and finishes succesfully it should return the correct exit code' { + Context 'When Copy-ItemWithRobocopy is called and finishes successfully it should return the correct exit code' { BeforeEach { - $mockRobocopyExectuableVersion = $mockRobocopyExectuableVersionWithUnbufferedIO + $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithUnbufferedIO Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable - Mock -CommandName Start-Process -MockWith $mockStartProcess_WithExitCode -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartProcess_Robocopy_WithExitCode -Verifiable } - It 'Should finish succesfully with exit code 1' { + It 'Should finish successfully with exit code 1' { $mockStartProcessExitCode = 1 $copyItemWithRoboCopyParameter = @{ @@ -4453,13 +4481,13 @@ try DestinationPath = $mockRobocopyArgumentDestinationPath } - { Copy-ItemWithRoboCopy @copyItemWithRoboCopyParameter } | Should Not Throw + { Copy-ItemWithRobocopy @copyItemWithRoboCopyParameter } | Should Not Throw Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It } - It 'Should finish succesfully with exit code 2' { + It 'Should finish successfully with exit code 2' { $mockStartProcessExitCode = 2 $copyItemWithRoboCopyParameter = @{ @@ -4467,13 +4495,13 @@ try DestinationPath = $mockRobocopyArgumentDestinationPath } - { Copy-ItemWithRoboCopy @copyItemWithRoboCopyParameter } | Should Not Throw + { Copy-ItemWithRobocopy @copyItemWithRoboCopyParameter } | Should Not Throw Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It } - It 'Should finish succesfully with exit code 3' { + It 'Should finish successfully with exit code 3' { $mockStartProcessExitCode = 3 $copyItemWithRoboCopyParameter = @{ @@ -4481,7 +4509,7 @@ try DestinationPath = $mockRobocopyArgumentDestinationPath } - { Copy-ItemWithRoboCopy @copyItemWithRoboCopyParameter } | Should Not Throw + { Copy-ItemWithRobocopy @copyItemWithRoboCopyParameter } | Should Not Throw Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It diff --git a/en-US/xPDT.strings.psd1 b/en-US/xPDT.strings.psd1 deleted file mode 100644 index 0411cbba1a..0000000000 --- a/en-US/xPDT.strings.psd1 +++ /dev/null @@ -1,11 +0,0 @@ -ConvertFrom-StringData @' -###PSLOC -FileNotFound=File not found in the environment path -AbsolutePathOrFileName=Absolute path or file name expected -InvalidArgument=Invalid argument: '{0}' with value: '{1}' -InvalidArgumentAndMessage={0} {1} -ErrorStarting=Failure starting process matching path '{0}'. Message: {1} -FailureWaitingForProcessesToStart=Failed to wait for processes to start -ProcessStarted=Process matching path '{0}' started in process ID {1} -ProcessAlreadyStarted=Process matching path '{0}' already started in process ID {1} -'@ diff --git a/xPDT.psm1 b/xPDT.psm1 deleted file mode 100644 index 6334ef1a4a..0000000000 --- a/xPDT.psm1 +++ /dev/null @@ -1,708 +0,0 @@ -Import-LocalizedData LocalizedData -filename xPDT.strings.psd1 - -function ThrowInvalidArgumentError -{ - [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $errorId, - - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $errorMessage - ) - - $errorCategory=[System.Management.Automation.ErrorCategory]::InvalidArgument - $exception = New-Object System.ArgumentException $errorMessage; - $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null - throw $errorRecord -} - -function ResolvePath -{ - [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $Path - ) - - $Path = [Environment]::ExpandEnvironmentVariables($Path) - if(IsRootedPath $Path) - { - if(!(Test-Path $Path -PathType Leaf)) - { - ThrowInvalidArgumentError "CannotFindRootedPath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound) - } - return $Path - } - else - { - $Path = (Get-Item -Path $Path -ErrorAction SilentlyContinue).FullName - if(!(Test-Path $Path -PathType Leaf)) - { - ThrowInvalidArgumentError "CannotFindRootedPath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound) - } - return $Path - } - if([string]::IsNullOrEmpty($env:Path)) - { - ThrowInvalidArgumentError "EmptyEnvironmentPath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound) - } - if((Split-Path $Path -Leaf) -ne $Path) - { - ThrowInvalidArgumentError "NotAbsolutePathOrFileName" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.AbsolutePathOrFileName) - } - foreach($rawSegment in $env:Path.Split(";")) - { - $segment = [Environment]::ExpandEnvironmentVariables($rawSegment) - $segmentRooted = $false - try - { - $segmentRooted=[IO.Path]::IsPathRooted($segment) - } - catch {} - if(!$segmentRooted) - { - continue - } - $candidate = join-path $segment $Path - if(Test-Path $candidate -PathType Leaf) - { - return $candidate - } - } - ThrowInvalidArgumentError "CannotFindRelativePath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound) -} - -function IsRootedPath -{ - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $Path - ) - - try - { - return [IO.Path]::IsPathRooted($Path) - } - catch - { - ThrowInvalidArgumentError "CannotGetIsPathRooted" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $_.Exception.Message) - } -} - -function ExtractArguments -{ - [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - $functionBoundParameters, - - [parameter(Mandatory = $true)] - [string[]] - $argumentNames, - - [string[]] - $newArgumentNames - ) - - $returnValue=@{} - - for($i=0;$i -lt $argumentNames.Count;$i++) - { - $argumentName = $argumentNames[$i] - - if($newArgumentNames -eq $null) - { - $newArgumentName = $argumentName - } - else - { - $newArgumentName = $newArgumentNames[$i] - } - - if($functionBoundParameters.ContainsKey($argumentName)) - { - $null = $returnValue.Add($newArgumentName,$functionBoundParameters[$argumentName]) - } - } - - return $returnValue -} - -function CallPInvoke -{ - $script:ProgramSource = @" -using System; -using System.Collections.Generic; -using System.Text; -using System.Security; -using System.Runtime.InteropServices; -using System.Diagnostics; -using System.Security.Principal; -using System.ComponentModel; -using System.IO; - -namespace Source -{ - [SuppressUnmanagedCodeSecurity] - public static class NativeMethods - { - //The following structs and enums are used by the various Win32 API's that are used in the code below - - [StructLayout(LayoutKind.Sequential)] - public struct STARTUPINFO - { - public Int32 cb; - public string lpReserved; - public string lpDesktop; - public string lpTitle; - public Int32 dwX; - public Int32 dwY; - public Int32 dwXSize; - public Int32 dwXCountChars; - public Int32 dwYCountChars; - public Int32 dwFillAttribute; - public Int32 dwFlags; - public Int16 wShowWindow; - public Int16 cbReserved2; - public IntPtr lpReserved2; - public IntPtr hStdInput; - public IntPtr hStdOutput; - public IntPtr hStdError; - } - - [StructLayout(LayoutKind.Sequential)] - public struct PROCESS_INFORMATION - { - public IntPtr hProcess; - public IntPtr hThread; - public Int32 dwProcessID; - public Int32 dwThreadID; - } - - [Flags] - public enum LogonType - { - LOGON32_LOGON_INTERACTIVE = 2, - LOGON32_LOGON_NETWORK = 3, - LOGON32_LOGON_BATCH = 4, - LOGON32_LOGON_SERVICE = 5, - LOGON32_LOGON_UNLOCK = 7, - LOGON32_LOGON_NETWORK_CLEARTEXT = 8, - LOGON32_LOGON_NEW_CREDENTIALS = 9 - } - - [Flags] - public enum LogonProvider - { - LOGON32_PROVIDER_DEFAULT = 0, - LOGON32_PROVIDER_WINNT35, - LOGON32_PROVIDER_WINNT40, - LOGON32_PROVIDER_WINNT50 - } - - [StructLayout(LayoutKind.Sequential)] - public struct SECURITY_ATTRIBUTES - { - public Int32 Length; - public IntPtr lpSecurityDescriptor; - public bool bInheritHandle; - } - - public enum SECURITY_IMPERSONATION_LEVEL - { - SecurityAnonymous, - SecurityIdentification, - SecurityImpersonation, - SecurityDelegation - } - - public enum TOKEN_TYPE - { - TokenPrimary = 1, - TokenImpersonation - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct TokPriv1Luid - { - public int Count; - public long Luid; - public int Attr; - } - - public const int GENERIC_ALL_ACCESS = 0x10000000; - public const int CREATE_NO_WINDOW = 0x08000000; - internal const int SE_PRIVILEGE_ENABLED = 0x00000002; - internal const int TOKEN_QUERY = 0x00000008; - internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; - internal const string SE_INCRASE_QUOTA = "SeIncreaseQuotaPrivilege"; - - [DllImport("kernel32.dll", - EntryPoint = "CloseHandle", SetLastError = true, - CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] - public static extern bool CloseHandle(IntPtr handle); - - [DllImport("advapi32.dll", - EntryPoint = "CreateProcessAsUser", SetLastError = true, - CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] - public static extern bool CreateProcessAsUser( - IntPtr hToken, - string lpApplicationName, - string lpCommandLine, - ref SECURITY_ATTRIBUTES lpProcessAttributes, - ref SECURITY_ATTRIBUTES lpThreadAttributes, - bool bInheritHandle, - Int32 dwCreationFlags, - IntPtr lpEnvrionment, - string lpCurrentDirectory, - ref STARTUPINFO lpStartupInfo, - ref PROCESS_INFORMATION lpProcessInformation - ); - - [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] - public static extern bool DuplicateTokenEx( - IntPtr hExistingToken, - Int32 dwDesiredAccess, - ref SECURITY_ATTRIBUTES lpThreadAttributes, - Int32 ImpersonationLevel, - Int32 dwTokenType, - ref IntPtr phNewToken - ); - - [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - public static extern Boolean LogonUser( - String lpszUserName, - String lpszDomain, - String lpszPassword, - LogonType dwLogonType, - LogonProvider dwLogonProvider, - out IntPtr phToken - ); - - [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] - internal static extern bool AdjustTokenPrivileges( - IntPtr htok, - bool disall, - ref TokPriv1Luid newst, - int len, - IntPtr prev, - IntPtr relen - ); - - [DllImport("kernel32.dll", ExactSpelling = true)] - internal static extern IntPtr GetCurrentProcess(); - - [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] - internal static extern bool OpenProcessToken( - IntPtr h, - int acc, - ref IntPtr phtok - ); - - [DllImport("advapi32.dll", SetLastError = true)] - internal static extern bool LookupPrivilegeValue( - string host, - string name, - ref long pluid - ); - - public static void CreateProcessAsUser(string strCommand, string strDomain, string strName, string strPassword) - { - var hToken = IntPtr.Zero; - var hDupedToken = IntPtr.Zero; - TokPriv1Luid tp; - var pi = new PROCESS_INFORMATION(); - var sa = new SECURITY_ATTRIBUTES(); - sa.Length = Marshal.SizeOf(sa); - Boolean bResult = false; - try - { - bResult = LogonUser( - strName, - strDomain, - strPassword, - LogonType.LOGON32_LOGON_BATCH, - LogonProvider.LOGON32_PROVIDER_DEFAULT, - out hToken - ); - if (!bResult) - { - throw new Win32Exception("The user could not be logged on. Ensure that the user has an existing profile on the machine and that correct credentials are provided. Logon error #" + Marshal.GetLastWin32Error().ToString()); - } - IntPtr hproc = GetCurrentProcess(); - IntPtr htok = IntPtr.Zero; - bResult = OpenProcessToken( - hproc, - TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, - ref htok - ); - if(!bResult) - { - throw new Win32Exception("Open process token error #" + Marshal.GetLastWin32Error().ToString()); - } - tp.Count = 1; - tp.Luid = 0; - tp.Attr = SE_PRIVILEGE_ENABLED; - bResult = LookupPrivilegeValue( - null, - SE_INCRASE_QUOTA, - ref tp.Luid - ); - if(!bResult) - { - throw new Win32Exception("Error in looking up privilege of the process. This should not happen if DSC is running as LocalSystem Lookup privilege error #" + Marshal.GetLastWin32Error().ToString()); - } - bResult = AdjustTokenPrivileges( - htok, - false, - ref tp, - 0, - IntPtr.Zero, - IntPtr.Zero - ); - if(!bResult) - { - throw new Win32Exception("Token elevation error #" + Marshal.GetLastWin32Error().ToString()); - } - - bResult = DuplicateTokenEx( - hToken, - GENERIC_ALL_ACCESS, - ref sa, - (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, - (int)TOKEN_TYPE.TokenPrimary, - ref hDupedToken - ); - if(!bResult) - { - throw new Win32Exception("Duplicate Token error #" + Marshal.GetLastWin32Error().ToString()); - } - var si = new STARTUPINFO(); - si.cb = Marshal.SizeOf(si); - si.lpDesktop = ""; - bResult = CreateProcessAsUser( - hDupedToken, - null, - strCommand, - ref sa, - ref sa, - false, - 0, - IntPtr.Zero, - null, - ref si, - ref pi - ); - if(!bResult) - { - throw new Win32Exception("The process could not be created. Create process as user error #" + Marshal.GetLastWin32Error().ToString()); - } - } - finally - { - if (pi.hThread != IntPtr.Zero) - { - CloseHandle(pi.hThread); - } - if (pi.hProcess != IntPtr.Zero) - { - CloseHandle(pi.hProcess); - } - if (hDupedToken != IntPtr.Zero) - { - CloseHandle(hDupedToken); - } - } - } - } -} - -"@ - Add-Type -TypeDefinition $ProgramSource -ReferencedAssemblies "System.ServiceProcess" -} - -function GetWin32Process -{ - [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $Path, - - [String] - $Arguments, - - [PSCredential] - $Credential - ) - - $fileName = [io.path]::GetFileNameWithoutExtension($Path) - $GetProcesses = @(Get-Process -Name $fileName -ErrorAction SilentlyContinue) - $Processes = foreach($process in $GetProcesses) - { - if($Process.Path -ieq $Path) - { - try - { - [wmi]"Win32_Process.Handle='$($Process.Id)'" - } - catch - { - } - } - } - if($PSBoundParameters.ContainsKey('Credential')) - { - $Processes = $Processes | Where-Object {(GetWin32ProcessOwner $_) -eq $Credential.UserName} - } - if($Arguments -eq $null) {$Arguments = ""} - $Processes = $Processes | Where-Object {(GetWin32ProcessArgumentsFromCommandLine $_.CommandLine) -eq $Arguments} - - return $Processes -} - -function GetWin32ProcessOwner -{ - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNull()] - $Process - ) - - try - { - $Owner = $Process.GetOwner() - } - catch - {} - if(($owner.Domain -ne $null) -and ($owner.Domain -ne $env:COMPUTERNAME)) - { - return $Owner.Domain + "\" + $Owner.User - } - else - { - return $Owner.User - } -} - -function GetWin32ProcessArgumentsFromCommandLine -{ - param - ( - [String] - $commandLine - ) - - if($commandLine -eq $null) - { - return "" - } - $commandLine=$commandLine.Trim() - if($commandLine.Length -eq 0) - { - return "" - } - if($commandLine[0] -eq '"') - { - $charToLookfor=[char]'"' - } - else - { - $charToLookfor=[char]' ' - } - $endOfCommand=$commandLine.IndexOf($charToLookfor ,1) - if($endOfCommand -eq -1) - { - return "" - } - return $commandLine.Substring($endOfCommand+1).Trim() -} - -function StartWin32Process -{ - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $Path, - - [String] - $Arguments, - - [PSCredential] - $Credential, - - [Switch] - $AsTask - ) - - $GetArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential") - $Processes = @(GetWin32Process @getArguments) - if ($processes.Count -eq 0) - { - if($PSBoundParameters.ContainsKey("Credential")) - { - if($AsTask) - { - $ActionArguments = ExtractArguments $PSBoundParameters ` - ("Path", "Arguments") ` - ("Execute", "Argument") - if([string]::IsNullOrEmpty($Arguments)) - { - $null = $ActionArguments.Remove("Argument") - } - $TaskGuid = [guid]::NewGuid().ToString() - $Action = New-ScheduledTaskAction @ActionArguments - $null = Register-ScheduledTask -TaskName "xPDT $TaskGuid" -Action $Action -User $Credential.UserName -Password $Credential.GetNetworkCredential().Password -RunLevel Highest - $err = Start-ScheduledTask -TaskName "xPDT $TaskGuid" - } - else - { - try - { - CallPInvoke - [Source.NativeMethods]::CreateProcessAsUser(("$Path " + $Arguments),$Credential.GetNetworkCredential().Domain,$Credential.GetNetworkCredential().UserName,$Credential.GetNetworkCredential().Password) - } - catch - { - $exception = New-Object System.ArgumentException $_ - $errorCategory = [System.Management.Automation.ErrorCategory]::OperationStopped - $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "Win32Exception", $errorCategory, $null - $err = $errorRecord - } - } - } - else - { - $StartArguments = ExtractArguments $PSBoundParameters ` - ("Path", "Arguments", "Credential") ` - ("FilePath", "ArgumentList", "Credential") - if([string]::IsNullOrEmpty($Arguments)) - { - $null = $StartArguments.Remove("ArgumentList") - } - $err = Start-Process @StartArguments - } - if($err -ne $null) - { - throw $err - } - if (!(WaitForWin32ProcessStart @GetArguments)) - { -# ThrowInvalidArgumentError "FailureWaitingForProcessesToStart" ($LocalizedData.ErrorStarting -f $Path,$LocalizedData.FailureWaitingForProcessesToStart) - } - } - else - { - return ($LocalizedData.ProcessAlreadyStarted -f $Path,$Processes.ProcessId) - } - $Processes = @(GetWin32Process @getArguments) - return ($LocalizedData.ProcessStarted -f $Path,$Processes.ProcessId) -} - -function WaitForWin32ProcessStart -{ - [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $Path, - - [String] - $Arguments, - - [PSCredential] - $Credential, - - [Int] - $Delay = 60000 - ) - - $start = [DateTime]::Now - $GetArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential") - do - { - $value = @(GetWin32Process @GetArguments).Count -ge 1 - } while(!$value -and ([DateTime]::Now - $start).TotalMilliseconds -lt $Delay) - - return $value -} - -function WaitForWin32ProcessEnd -{ - [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $Path, - - [String] - $Arguments, - - [PSCredential] - $Credential - ) - - $GetArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential") - While (WaitForWin32ProcessStart @GetArguments -Delay 1000) - { - Start-Sleep 1 - } - Get-ScheduledTask | Where-Object {($_.TaskName.StartsWith("xPDT")) -and ($_.Actions.Execute -eq $Path) -and ($_.Actions.Arguments -eq $Arguments)} | Where-Object {$_ -ne $null} | Unregister-ScheduledTask -Confirm:$false -} - -function NetUse -{ - param - ( - [parameter(Mandatory)] - [string] - $SourcePath, - - [parameter(Mandatory)] - [PSCredential] - $Credential, - - [string] - [ValidateSet("Present","Absent")] - $Ensure = "Present" - ) - - if(($SourcePath.Length -ge 2) -and ($SourcePath.Substring(0,2) -eq "\\")) - { - $args = @() - if ($Ensure -eq "Absent") - { - $args += "use", $SourcePath, "/del" - } - else - { - $args += "use", $SourcePath, $($Credential.GetNetworkCredential().Password), "/user:$($Credential.GetNetworkCredential().Domain)\$($Credential.GetNetworkCredential().UserName)" - } - - &"net" $args - } -} - -Export-ModuleMember ResolvePath,StartWin32Process,WaitForWin32ProcessEnd,NetUse