From b7b68f938b7371a048294f3c55bdbc817b31bf5a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 19 Dec 2016 16:05:33 +0100 Subject: [PATCH 01/31] Improvements how tests are initiated in AppVeyor - Removed workaround (issue #201) from tests - Remove present SQL modules before common test run - Added AppVeyor changes to CHANGELOG.md - Changes to CONTRIBUTING.md - Removed the text about the need to do the workaround in tests (issue #201). - Added a short descriptive text how tests works with AppVeyor. - Added text explaining that a review only starts when all tests are passing. --- CHANGELOG.md | 4 + CONTRIBUTING.md | 23 +- Tests/Unit/MSFT_xSQLAOGroupEnsure.Tests.ps1 | 564 ++++---- ...LServerAvailabilityGroupListener.Tests.ps1 | 1224 ++++++++--------- .../MSFT_xSQLServerConfiguration.Tests.ps1 | 419 +++--- Tests/Unit/MSFT_xSQLServerDatabase.Tests.ps1 | 343 +++-- .../MSFT_xSQLServerDatabaseOwner.Tests.ps1 | 417 +++--- ...SFT_xSQLServerEndpointPermission.Tests.ps1 | 667 +++++---- Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 | 713 +++++----- .../Unit/MSFT_xSQLServerPermission.Tests.ps1 | 538 ++++---- Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 | 408 +++--- appveyor.yml | 6 + 12 files changed, 2604 insertions(+), 2722 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e6f5815..188538dc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- Improvements how tests are initiated in AppVeyor + - Removed previous workaround (issue #201) from unit tests. + - Changes in appveyor.yml so that SQL modules are removed before common test is run. + ## 4.0.0.0 - Fixes in xSQLServerConfiguration diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad94268c0..0663892c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,28 +73,13 @@ one resource, then the functions can also be placed in the common [xSQLServerHel ### Tests +For a review of a Pull Request (PR) to start, all tests must pass without error. If you need help to figure why some test don't pass, just write a comment in the Pull Request (PR), or submit an issue, and somebody will come along and assist. + #### Using SMO stub classes There are [stub classes](https://github.com/PowerShell/xSQLServer/blob/dev/Tests/Unit/Stubs/SMO.cs) for the SMO classes which can be used and improved on when creating tests where SMO classes are used in the code being tested. #### AppVeyor -AppVeyor is the platform where the tests is run when sending in a Pull Request (PR). Due to a change in the build worker that AppVeyor provides it has already have the SMO assemblies loaded, which make our stub SMO classes unable to be initiated. -To get around this we need to get a clean PowerShell environment to run our tests in. One way is to use `Start-Job`. So this change needs to be done to the unit test template before sending in a Pull Request (PR). - -```powershell -# - AppVeyor build worker loads the SMO assembly which makes the SMO stub classes unable to be initiated. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - - # Unit test template goes here -} - -$testJob | Receive-Job -Wait -``` +AppVeyor is the platform where the tests is run when sending in a Pull Request (PR). All tests are run on a clean AppVeyor build worker for each push to the Pull Request (PR). +The tests that are run on the build worker are common tests, unit tests and integration tests (with some limitations). diff --git a/Tests/Unit/MSFT_xSQLAOGroupEnsure.Tests.ps1 b/Tests/Unit/MSFT_xSQLAOGroupEnsure.Tests.ps1 index 8f483465b..816a493d2 100644 --- a/Tests/Unit/MSFT_xSQLAOGroupEnsure.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLAOGroupEnsure.Tests.ps1 @@ -2,339 +2,327 @@ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param () -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLAOGroupEnsure' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) - if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) - { - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) - } +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLAOGroupEnsure' - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +#region HEADER - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit +# Unit Test Template Version: 1.1.0 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} - #endregion HEADER +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force - try - { +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit - #region Pester Test Initialization +#endregion HEADER - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +try +{ - $mockpassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force - $mockusername = "dba" - $mockcredential = New-Object System.Management.Automation.PSCredential($mockusername,$mockpassword) + #region Pester Test Initialization - #endregion Pester Test Initialization - - #region Get-TargetResource - Describe 'Get-TargetResource' { - Mock -CommandName Connect-SQL -MockWith { - # build a custom object to return which is close to the real SMO object - $smoObj = [PSCustomObject] @{ - SQLServer = 'Node01' - SQLInstanceName = 'Prd01' - ClusterName = 'Clust01' - } + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') - # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later - $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{ - 'AG01' = @{ - AvailabilityGroupListeners = @{ - name = 'AgList01' - availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'}) - portnumber = 5022 - } - - AvailabilityDatabases = @( - @{ - name='AdventureWorks' - } - ) + $mockpassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force + $mockusername = "dba" + $mockcredential = New-Object System.Management.Automation.PSCredential($mockusername,$mockpassword) + + #endregion Pester Test Initialization + + #region Get-TargetResource + Describe 'Get-TargetResource' { + Mock -CommandName Connect-SQL -MockWith { + # build a custom object to return which is close to the real SMO object + $smoObj = [PSCustomObject] @{ + SQLServer = 'Node01' + SQLInstanceName = 'Prd01' + ClusterName = 'Clust01' + } + + # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later + $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{ + 'AG01' = @{ + AvailabilityGroupListeners = @{ + name = 'AgList01' + availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'}) + portnumber = 5022 } + + AvailabilityDatabases = @( + @{ + name='AdventureWorks' + } + ) } + } - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force - $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value { - return $true - } -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force + $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value { + return $true + } -Force - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value { - return 'AG01' - } -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value { + return 'AG01' + } -Force - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value { - return $true - } -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value { + return $true + } -Force - return $smoObj - } -ModuleName $script:DSCResourceName - - Context "When the system is in the desired state" { - $SqlAOGroup = Get-TargetResource -Ensure 'Present' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential + return $smoObj + } -ModuleName $script:DSCResourceName - It 'Should return hashtable with Ensure = $true'{ - $SqlAOGroup.Ensure | Should Be $true - } + Context "When the system is in the desired state" { + $SqlAOGroup = Get-TargetResource -Ensure 'Present' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential + + It 'Should return hashtable with Ensure = $true'{ + $SqlAOGroup.Ensure | Should Be $true } - - Context "When the system is not in the desired state" { - $SqlAOGroup = Get-TargetResource -Ensure 'Absent' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential - - It 'Should return hashtable with Ensure = $false' { - $SqlAOGroup.Ensure | Should Be $false - } + } + + Context "When the system is not in the desired state" { + $SqlAOGroup = Get-TargetResource -Ensure 'Absent' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential + + It 'Should return hashtable with Ensure = $false' { + $SqlAOGroup.Ensure | Should Be $false } } - #endregion Get-TargetResource - - #region Test-TargetResource - Describe 'Test-TargetResource' { - Mock -CommandName Connect-SQL -MockWith { - # build a custom object to return which is close to the real SMO object - $smoObj = [PSCustomObject] @{ - SQLServer = 'Node01' - SQLInstanceName = 'Prd01' - ClusterName = 'Clust01' - } + } + #endregion Get-TargetResource + + #region Test-TargetResource + Describe 'Test-TargetResource' { + Mock -CommandName Connect-SQL -MockWith { + # build a custom object to return which is close to the real SMO object + $smoObj = [PSCustomObject] @{ + SQLServer = 'Node01' + SQLInstanceName = 'Prd01' + ClusterName = 'Clust01' + } - # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later - $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{ - 'AG01' = @{ - AvailabilityGroupListeners = @{ - name = 'AgList01' - availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'}) - portnumber = 5022 - } - - AvailabilityDatabases = @( - @{ - name='AdventureWorks' - } - ) + # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later + $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{ + 'AG01' = @{ + AvailabilityGroupListeners = @{ + name = 'AgList01' + availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'}) + portnumber = 5022 } + + AvailabilityDatabases = @( + @{ + name='AdventureWorks' + } + ) } + } - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force - $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value { - return $true - } -Force - - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value { - return 'AG01' - } -Force - - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value { - return $true - } -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force + $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value { + return $true + } -Force + + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value { + return 'AG01' + } -Force + + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value { + return $true + } -Force + + return $smoObj + } -ModuleName $script:DSCResourceName + + Context "When the system is in the desired state" { + $SqlAOGroupTest = Test-TargetResource -Ensure 'Present' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential + + It 'Should return $true'{ + $SqlAOGroupTest | Should Be $true + } + } + + Context "When the system is not in the desired state" { + $SqlAOGroupTest = Test-TargetResource -Ensure 'Absent' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential + + It 'Should return $false' { + $SqlAOGroupTest | Should Be $false + } + } + } + #endregion Test-TargetResource + + Describe 'Set-TargetResource' { + # Mocking the module FailoverCluster, to be able to mock the function Get-ClusterNode + Get-Module -Name FailoverClusters | Remove-Module + New-Module -Name FailoverClusters -ScriptBlock { + # This was generated by Write-ModuleStubFile function from the folder Tests\Unit\Stubs + function Get-ClusterNode { + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216215')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} + ) - return $smoObj - } -ModuleName $script:DSCResourceName - - Context "When the system is in the desired state" { - $SqlAOGroupTest = Test-TargetResource -Ensure 'Present' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential - - It 'Should return $true'{ - $SqlAOGroupTest | Should Be $true - } + throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } - Context "When the system is not in the desired state" { - $SqlAOGroupTest = Test-TargetResource -Ensure 'Absent' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential - - It 'Should return $false' { - $SqlAOGroupTest | Should Be $false + Export-ModuleMember -Function *-Cluster* + } | Import-Module -Force + + Mock Grant-ServerPerms -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Mock New-ListenerADObject -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Mock Get-ClusterNode -MockWith { + $clusterNode = @( + [PSCustomObject] @{ + Name = 'Node01' + }, + [PSCustomObject] @{ + Name = 'Node02' + }, + [PSCustomObject] @{ + Name = 'Node03' + }, + [PSCustomObject] @{ + Name = 'Node04' } + ) + return $clusterNode + } -ModuleName $script:DSCResourceName -Verifiable + + Mock Connect-SQL -MockWith { + # build a custom object to return which is close to the real SMO object + $smoObj = [PSCustomObject] @{ + SQLServer = 'Node01' + SQLInstanceName = 'Prd01' + ClusterName = 'Clust01' } - } - #endregion Test-TargetResource - - Describe 'Set-TargetResource' { - # Mocking the module FailoverCluster, to be able to mock the function Get-ClusterNode - Get-Module -Name FailoverClusters | Remove-Module - New-Module -Name FailoverClusters -ScriptBlock { - # This was generated by Write-ModuleStubFile function from the folder Tests\Unit\Stubs - function Get-ClusterNode { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216215')] - param( - [Parameter(Position=0)] - [ValidateNotNullOrEmpty()] - [System.Collections.Specialized.StringCollection] - ${Name}, - - [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] - [ValidateNotNull()] - [psobject] - ${InputObject}, - - [ValidateNotNullOrEmpty()] - [string] - ${Cluster} - ) - throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand - } - - Export-ModuleMember -Function *-Cluster* - } | Import-Module -Force - - Mock Grant-ServerPerms -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock New-ListenerADObject -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock Get-ClusterNode -MockWith { - $clusterNode = @( - [PSCustomObject] @{ - Name = 'Node01' - }, - [PSCustomObject] @{ - Name = 'Node02' - }, - [PSCustomObject] @{ - Name = 'Node03' - }, - [PSCustomObject] @{ - Name = 'Node04' + # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later + $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{ + 'AG01' = @{ + AvailabilityGroupListeners = @{ + name = 'AgList01' + availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'}) + portnumber = 5022 } - ) - return $clusterNode - } -ModuleName $script:DSCResourceName -Verifiable - - Mock Connect-SQL -MockWith { - # build a custom object to return which is close to the real SMO object - $smoObj = [PSCustomObject] @{ - SQLServer = 'Node01' - SQLInstanceName = 'Prd01' - ClusterName = 'Clust01' - } - # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later - $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{ - 'AG01' = @{ - AvailabilityGroupListeners = @{ - name = 'AgList01' - availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'}) - portnumber = 5022 + AvailabilityDatabases = @( + @{ + name='AdventureWorks' } + ) + } + } - AvailabilityDatabases = @( - @{ - name='AdventureWorks' + $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value {return $true} -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value {return 'AG01'} -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value {return $true} -Force + + return $smoObj + } -ModuleName $script:DSCResourceName -Verifiable + + Mock New-Object -MockWith { + Param($TypeName) + Switch ($TypeName) + { + 'Microsoft.SqlServer.Management.Smo.AvailabilityGroup' { + $object = [PSCustomObject] @{ + Name = "MockedObject" + AutomatedBackupPreference = '' + FailureConditionLevel = '' + HealthCheckTimeout = '' + AvailabilityReplicas = [System.Collections.ArrayList] @() + AvailabilityGroupListeners = [System.Collections.ArrayList] @() } - ) - } + $object | Add-Member -MemberType ScriptMethod -Name Create -Value {return $true} } - $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value {return $true} -Force - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value {return 'AG01'} -Force - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value {return $true} -Force - - return $smoObj - } -ModuleName $script:DSCResourceName -Verifiable - - Mock New-Object -MockWith { - Param($TypeName) - Switch ($TypeName) - { - 'Microsoft.SqlServer.Management.Smo.AvailabilityGroup' { - $object = [PSCustomObject] @{ - Name = "MockedObject" - AutomatedBackupPreference = '' - FailureConditionLevel = '' - HealthCheckTimeout = '' - AvailabilityReplicas = [System.Collections.ArrayList] @() - AvailabilityGroupListeners = [System.Collections.ArrayList] @() - } - $object | Add-Member -MemberType ScriptMethod -Name Create -Value {return $true} - } - - 'Microsoft.SqlServer.Management.Smo.AvailabilityReplica' { - $object = [PSCustomObject] @{ - Name = "MockedObject" - EndpointUrl = '' - FailoverMode = '' - AvailabilityMode = '' - BackupPriority = 0 - ConnectionModeInPrimaryRole = '' - ConnectionModeInSecondaryRole = '' - } - } - - 'Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener' { - $object = [PSCustomObject] @{ - Name = "MockedObject" - PortNumber = '' - AvailabilityGroupListenerIPAddresses = [System.Collections.ArrayList] @() - } - } - - 'Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress' { - $object = [PSCustomObject] @{ - Name = "MockedObject" - IsDHCP = '' - IPAddress = '' - SubnetMask = '' - } - } + 'Microsoft.SqlServer.Management.Smo.AvailabilityReplica' { + $object = [PSCustomObject] @{ + Name = "MockedObject" + EndpointUrl = '' + FailoverMode = '' + AvailabilityMode = '' + BackupPriority = 0 + ConnectionModeInPrimaryRole = '' + ConnectionModeInSecondaryRole = '' + } + } - Default { - $object = [PSCustomObject] @{ - Name = "MockedObject" - } - } + 'Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener' { + $object = [PSCustomObject] @{ + Name = "MockedObject" + PortNumber = '' + AvailabilityGroupListenerIPAddresses = [System.Collections.ArrayList] @() + } } - return $object - } -ModuleName $script:DSCResourceName -Verifiable - - Context "When the system is not in the desired state" { - $params = @{ - Ensure = 'Present' - AvailabilityGroupName = 'AG01' - AvailabilityGroupNameListener = 'AgList01' - AvailabilityGroupNameIP = '192.168.0.1' - AvailabilityGroupSubMask = '255.255.255.0' - AvailabilityGroupPort = 1433 - ReadableSecondary = 'ReadOnly' - AutoBackupPreference = 'Primary' - SQLServer = 'localhost' - SQLInstanceName = 'MSSQLSERVER' - SetupCredential = $mockcredential + 'Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress' { + $object = [PSCustomObject] @{ + Name = "MockedObject" + IsDHCP = '' + IPAddress = '' + SubnetMask = '' + } } - It 'Should not throw when calling Set-method' { - { Set-TargetResource @Params } | Should Not Throw + Default { + $object = [PSCustomObject] @{ + Name = "MockedObject" + } } } - } - } - finally - { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + return $object + } -ModuleName $script:DSCResourceName -Verifiable + + Context "When the system is not in the desired state" { + $params = @{ + Ensure = 'Present' + AvailabilityGroupName = 'AG01' + AvailabilityGroupNameListener = 'AgList01' + AvailabilityGroupNameIP = '192.168.0.1' + AvailabilityGroupSubMask = '255.255.255.0' + AvailabilityGroupPort = 1433 + ReadableSecondary = 'ReadOnly' + AutoBackupPreference = 'Primary' + SQLServer = 'localhost' + SQLInstanceName = 'MSSQLSERVER' + SetupCredential = $mockcredential + } - #endregion + It 'Should not throw when calling Set-method' { + { Set-TargetResource @Params } | Should Not Throw + } + } } } +finally +{ + #region FOOTER -$testJob | Receive-Job -Wait + Restore-TestEnvironment -TestEnvironment $TestEnvironment + + #endregion +} diff --git a/Tests/Unit/MSFT_xSQLServerAvailabilityGroupListener.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerAvailabilityGroupListener.Tests.ps1 index 361e43be7..ef96a7cf3 100644 --- a/Tests/Unit/MSFT_xSQLServerAvailabilityGroupListener.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerAvailabilityGroupListener.Tests.ps1 @@ -1,633 +1,527 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerAvailabilityGroupListener' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) - if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) - { - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerAvailabilityGroupListener' + +#region HEADER + +# Unit Test Template Version: 1.1.0 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Test Initialization + + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + + # Loading stub cmdlets + Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SQLPSStub.psm1') -Force + + # Static parameter values + $nodeName = 'localhost' + $instanceName = 'DEFAULT' + $availabilityGroup = 'AG01' + $listnerName = 'AGListner' + + $defaultParameters = @{ + InstanceName = $instanceName + NodeName = $nodeName + Name = $listnerName + AvailabilityGroup = $availabilityGroup } - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force - - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit - - #endregion HEADER - - # Begin Testing - try - { - #region Pester Test Initialization - - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') - - # Loading stub cmdlets - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SQLPSStub.psm1') -Force - - # Static parameter values - $nodeName = 'localhost' - $instanceName = 'DEFAULT' - $availabilityGroup = 'AG01' - $listnerName = 'AGListner' - - $defaultParameters = @{ - InstanceName = $instanceName - NodeName = $nodeName - Name = $listnerName - AvailabilityGroup = $availabilityGroup + #endregion Pester Test Initialization + + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the desired state as absent' { + $result.Ensure | Should Be 'Absent' + } + + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + $result.AvailabilityGroup | Should Be $testParameters.AvailabilityGroup + } + + It 'Should not return any IP addresses' { + $result.IpAddress | Should Be $null + } + + It 'Should not return port' { + $result.Port | Should Be 0 + } + + It 'Should return that DHCP is not used' { + $result.DHCP | Should Be $false + } + + It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state, without DHCP' { + $testParameters = $defaultParameters + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5030 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $false -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the desired state as present' { + $result.Ensure | Should Be 'Present' + } + + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + $result.AvailabilityGroup | Should Be $testParameters.AvailabilityGroup + } + + It 'Should return correct IP address' { + $result.IpAddress | Should Be '192.168.0.1/255.255.255.0' + } + + It 'Should return correct port' { + $result.Port | Should Be 5030 + } + + It 'Should return that DHCP is not used' { + $result.DHCP | Should Be $false + } + + It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } } - #endregion Pester Test Initialization + Context 'When the system is in the desired state, with DHCP' { + $testParameters = $defaultParameters + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5031 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $true -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the desired state as present' { + $result.Ensure | Should Be 'Present' + } - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Context 'When the system is not in the desired state' { + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + $result.AvailabilityGroup | Should Be $testParameters.AvailabilityGroup + } + + It 'Should return correct IP address' { + $result.IpAddress | Should Be '192.168.0.1/255.255.255.0' + } + + It 'Should return correct port' { + $result.Port | Should Be 5031 + } + + It 'Should return that DHCP is used' { + $result.DHCP | Should Be $true + } + + It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Context 'When the system is not in the desired state (for static IP)' { + It 'Should return that desired state is absent when wanted desired state is to be Present' { $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.10.45/255.255.252.0' + Port = 5030 + DHCP = $false + } Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters - It 'Should return the desired state as absent' { - $result.Ensure | Should Be 'Absent' - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - $result.AvailabilityGroup | Should Be $testParameters.AvailabilityGroup - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should not return any IP addresses' { - $result.IpAddress | Should Be $null - } + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5030 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $false -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should not return port' { - $result.Port | Should Be 0 - } + It 'Should return that desired state is absent when wanted desired state is to be Absent' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' + } - It 'Should return that DHCP is not used' { - $result.DHCP | Should Be $false - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Context 'When the system is in the desired state, without DHCP' { + It 'Should return that desired state is absent when IP address is different' { $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.10.45/255.255.252.0' + Port = 5030 + DHCP = $false + } - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5030 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $false -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return the desired state as present' { - $result.Ensure | Should Be 'Present' - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - $result.AvailabilityGroup | Should Be $testParameters.AvailabilityGroup - } + It 'Should return that desired state is absent when DHCP is absent but should be present' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return correct IP address' { - $result.IpAddress | Should Be '192.168.0.1/255.255.255.0' - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should return correct port' { - $result.Port | Should Be 5030 - } + It 'Should return that desired state is absent when DHCP is the only set parameter' { + $testParameters = $defaultParameters + $testParameters += @{ + DHCP = $true + } - It 'Should return that DHCP is not used' { - $result.DHCP | Should Be $false - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Context 'When the system is in the desired state, with DHCP' { + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5555 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $false -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + It 'Should return that desired state is absent when port is different' { $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $false + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5031 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $true -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) + Context 'When the system is not in the desired state (for DHCP)' { + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5030 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $true -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should return the desired state as present' { - $result.Ensure | Should Be 'Present' - } + It 'Should return that desired state is absent when DHCP is present but should be absent' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.100/255.255.255.0' + Port = 5030 + DHCP = $false + } - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - $result.AvailabilityGroup | Should Be $testParameters.AvailabilityGroup - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return correct IP address' { - $result.IpAddress | Should Be '192.168.0.1/255.255.255.0' - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should return correct port' { - $result.Port | Should Be 5031 - } + It 'Should return that desired state is absent when IP address is the only set parameter' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = '192.168.10.45/255.255.252.0' + } - It 'Should return that DHCP is used' { - $result.DHCP | Should Be $true - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Assert-VerifiableMocks + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5555 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $true -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + It 'Should return that desired state is absent when port is the only set parameter' { + $testParameters = $defaultParameters + $testParameters += @{ + Port = 5030 + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Context 'When the system is not in the desired state (for static IP)' { - It 'Should return that desired state is absent when wanted desired state is to be Present' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.10.45/255.255.252.0' - Port = 5030 - DHCP = $false - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5030 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $false -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should return that desired state is absent when wanted desired state is to be Absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should return that desired state is absent when IP address is different' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.10.45/255.255.252.0' - Port = 5030 - DHCP = $false - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should return that desired state is absent when DHCP is absent but should be present' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - DHCP = $true - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should return that desired state is absent when DHCP is the only set parameter' { - $testParameters = $defaultParameters - $testParameters += @{ - DHCP = $true - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5555 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $false -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should return that desired state is absent when port is different' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - DHCP = $false - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } - - Context 'When the system is not in the desired state (for DHCP)' { - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5030 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $true -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should return that desired state is absent when DHCP is present but should be absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.0.100/255.255.255.0' - Port = 5030 - DHCP = $false - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should return that desired state is absent when IP address is the only set parameter' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = '192.168.10.45/255.255.252.0' - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5555 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $true -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should return that desired state is absent when port is the only set parameter' { - $testParameters = $defaultParameters - $testParameters += @{ - Port = 5030 - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } - - Context 'When the system is in the desired state (for static IP)' { - It 'Should return that desired state is present when wanted desired state is to be Absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - IpAddress = '192.168.10.45/255.255.252.0' - Port = 5030 - DHCP = $false - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5030 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $false -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should return that desired state is present when wanted desired state is to be Present, without DHCP' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - DHCP = $false - } - - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should return that desired state is present when IP address is the only set parameter' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = '192.168.0.1/255.255.255.0' - } - - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should return that desired state is present when port is the only set parameter' { - $testParameters = $defaultParameters - $testParameters += @{ - Port = 5030 - } - - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } - - Context 'When the system is in the desired state (for DHCP)' { - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5030 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $true -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) + Context 'When the system is in the desired state (for static IP)' { + It 'Should return that desired state is present when wanted desired state is to be Absent' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' + IpAddress = '192.168.10.45/255.255.252.0' + Port = 5030 + DHCP = $false + } + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5030 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $false -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should return that desired state is present when wanted desired state is to be Present, with DHCP' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - DHCP = $true - } + It 'Should return that desired state is present when wanted desired state is to be Present, without DHCP' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $false + } - $result = Test-TargetResource @testParameters - $result | Should Be $true + $result = Test-TargetResource @testParameters + $result | Should Be $true - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should return that desired state is present when DHCP is the only set parameter' { - $testParameters = $defaultParameters - $testParameters += @{ - DHCP = $true - } + It 'Should return that desired state is present when IP address is the only set parameter' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = '192.168.0.1/255.255.255.0' + } - $result = Test-TargetResource @testParameters - $result | Should Be $true + $result = Test-TargetResource @testParameters + $result | Should Be $true - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Assert-VerifiableMocks + It 'Should return that desired state is present when port is the only set parameter' { + $testParameters = $defaultParameters + $testParameters += @{ + Port = 5030 + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName New-SqlAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Set-SqlAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Add-SqlAvailabilityGroupListenerStaticIp -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - It 'Should call the cmdlet New-SqlAvailabilityGroupListener when system is not in desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.10.45/255.255.252.0' - Port = 5030 - DHCP = $false - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - Set-TargetResource @testParameters | Out-Null - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5030 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $false -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should throw when trying to change an existing IP address' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = '10.0.0.1/255.255.252.0' - Port = 5030 - DHCP = $false - } - - { Set-TargetResource @testParameters } | Should Throw - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should throw when trying to change from static IP to DHCP' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - DHCP = $true - } - - { Set-TargetResource @testParameters } | Should Throw - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should call the cmdlet Add-SqlAvailabilityGroupListenerStaticIp, when adding another IP address, and system is not in desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = @('192.168.0.1/255.255.255.0','10.0.0.1/255.255.252.0') - Port = 5030 - DHCP = $false - } - - Set-TargetResource @testParameters | Out-Null - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5555 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $false -PassThru | - Add-Member NoteProperty IPAddress '192.168.10.45' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.252.0' -PassThru - ) + Context 'When the system is in the desired state (for DHCP)' { + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5030 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $true -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + It 'Should return that desired state is present when wanted desired state is to be Present, with DHCP' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should return that desired state is present when DHCP is the only set parameter' { + $testParameters = $defaultParameters + $testParameters += @{ + DHCP = $true + } - It 'Should call the cmdlet Set-SqlAvailabilityGroupListener when port is not in desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = '192.168.10.45/255.255.252.0' - Port = 5030 - DHCP = $false - } + $result = Test-TargetResource @testParameters + $result | Should Be $true - Set-TargetResource @testParameters | Out-Null + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName New-SqlAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Mock -CommandName Set-SqlAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Mock -CommandName Add-SqlAvailabilityGroupListenerStaticIp -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + It 'Should call the cmdlet New-SqlAvailabilityGroupListener when system is not in desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.10.45/255.255.252.0' + Port = 5030 + DHCP = $false + } + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters | Out-Null - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It } Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { @@ -646,51 +540,145 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { } -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable - Context 'When the system is in the desired state' { - It 'Should not call the any cmdlet *-SqlAvailability* when system is in desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - DHCP = $false - } - - Set-TargetResource @testParameters | Out-Null - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should not call the any cmdlet *-SqlAvailability* when system is in desired state (without ensure parameter)' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - } - - Set-TargetResource @testParameters | Out-Null - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } - } - - Assert-VerifiableMocks + It 'Should throw when trying to change an existing IP address' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = '10.0.0.1/255.255.252.0' + Port = 5030 + DHCP = $false + } + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should throw when trying to change from static IP to DHCP' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $true + } + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should call the cmdlet Add-SqlAvailabilityGroupListenerStaticIp, when adding another IP address, and system is not in desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = @('192.168.0.1/255.255.255.0','10.0.0.1/255.255.252.0') + Port = 5030 + DHCP = $false + } + + Set-TargetResource @testParameters | Out-Null + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5555 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $false -PassThru | + Add-Member NoteProperty IPAddress '192.168.10.45' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.252.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + It 'Should call the cmdlet Set-SqlAvailabilityGroupListener when port is not in desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = '192.168.10.45/255.255.252.0' + Port = 5030 + DHCP = $false + } + + Set-TargetResource @testParameters | Out-Null + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } } - } - finally - { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5030 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $false -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is in the desired state' { + It 'Should not call the any cmdlet *-SqlAvailability* when system is in desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $false + } + + Set-TargetResource @testParameters | Out-Null + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } - #endregion + It 'Should not call the any cmdlet *-SqlAvailability* when system is in desired state (without ensure parameter)' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + } + + Set-TargetResource @testParameters | Out-Null + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + } + + Assert-VerifiableMocks } } +finally +{ + #region FOOTER + + Restore-TestEnvironment -TestEnvironment $TestEnvironment -$testJob | Receive-Job -Wait + #endregion +} diff --git a/Tests/Unit/MSFT_xSQLServerConfiguration.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerConfiguration.Tests.ps1 index 1ab06da4d..557538aba 100644 --- a/Tests/Unit/MSFT_xSQLServerConfiguration.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerConfiguration.Tests.ps1 @@ -1,191 +1,114 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerConfiguration' - - # Unit Test Template Version: 1.1.0 - [String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) - if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) - { - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) - } +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerConfiguration' + +# Unit Test Template Version: 1.1.0 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit - - $defaultState = @{ - SQLServer = 'CLU01' - SQLInstanceName = 'ClusteredInstance' - OptionName = 'user connections' - OptionValue = 0 - RestartService = $false - RestartTimeout = 120 - } +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +$defaultState = @{ + SQLServer = 'CLU01' + SQLInstanceName = 'ClusteredInstance' + OptionName = 'user connections' + OptionValue = 0 + RestartService = $false + RestartTimeout = 120 +} - $desiredState = @{ - SQLServer = 'CLU01' - SQLInstanceName = 'ClusteredInstance' - OptionName = 'user connections' - OptionValue = 500 - RestartService = $false - RestartTimeout = 120 - } +$desiredState = @{ + SQLServer = 'CLU01' + SQLInstanceName = 'ClusteredInstance' + OptionName = 'user connections' + OptionValue = 500 + RestartService = $false + RestartTimeout = 120 +} - $desiredStateRestart = @{ - SQLServer = 'CLU01' - SQLInstanceName = 'ClusteredInstance' - OptionName = 'user connections' - OptionValue = 5000 - RestartService = $true - RestartTimeout = 120 - } +$desiredStateRestart = @{ + SQLServer = 'CLU01' + SQLInstanceName = 'ClusteredInstance' + OptionName = 'user connections' + OptionValue = 5000 + RestartService = $true + RestartTimeout = 120 +} - $dynamicOption = @{ - SQLServer = 'CLU02' - SQLInstanceName = 'ClusteredInstance' - OptionName = 'show advanced options' - OptionValue = 0 - RestartService = $false - RestartTimeout = 120 - } +$dynamicOption = @{ + SQLServer = 'CLU02' + SQLInstanceName = 'ClusteredInstance' + OptionName = 'show advanced options' + OptionValue = 0 + RestartService = $false + RestartTimeout = 120 +} - $invalidOption = @{ - SQLServer = 'CLU01' - SQLInstanceName = 'MSSQLSERVER' - OptionName = 'Does Not Exist' - OptionValue = 1 - RestartService = $false - RestartTimeout = 120 - } +$invalidOption = @{ + SQLServer = 'CLU01' + SQLInstanceName = 'MSSQLSERVER' + OptionName = 'Does Not Exist' + OptionValue = 1 + RestartService = $false + RestartTimeout = 120 +} - ## compile the SMO stub - #Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +## compile the SMO stub +#Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') - try - { - Describe "$($script:DSCResourceName)\Get-TargetResource" { +try +{ + Describe "$($script:DSCResourceName)\Get-TargetResource" { - Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName + Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName - Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName + Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName - Context 'The system is not in the desired state' { + Context 'The system is not in the desired state' { - Mock -CommandName Connect-SQL -MockWith { - $mock = New-Object PSObject -Property @{ - Configuration = @{ - Properties = @( - @{ - DisplayName = 'user connections' - ConfigValue = 0 - } - ) - } + Mock -CommandName Connect-SQL -MockWith { + $mock = New-Object PSObject -Property @{ + Configuration = @{ + Properties = @( + @{ + DisplayName = 'user connections' + ConfigValue = 0 + } + ) } - - ## add the Alter method - $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} - - return $mock - } -ModuleName $script:DSCResourceName -Verifiable - - ## Get the current state - $result = Get-TargetResource @desiredState - - It 'Should return the same values as passed' { - $result.SQLServer | Should Be $desiredState.SQLServer - $result.SQLInstanceName | Should Be $desiredState.SQLInstanceName - $result.OptionName | Should Be $desiredState.OptionName - $result.OptionValue | Should Not Be $desiredState.OptionValue - $result.RestartService | Should Be $desiredState.RestartService - $result.RestartTimeout | Should Be $desiredState.RestartTimeout } - It 'Should call Connect-SQL mock when getting the current state' { - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 1 - } - } - - Context 'The system is in the desired state' { - - Mock -CommandName Connect-SQL -MockWith { - $mock = New-Object PSObject -Property @{ - Configuration = @{ - Properties = @( - @{ - DisplayName = 'user connections' - ConfigValue = 500 - } - ) - } - } - - ## add the Alter method - $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} - - return $mock - } -ModuleName $script:DSCResourceName -Verifiable - - ## Get the current state - $result = Get-TargetResource @desiredState + ## add the Alter method + $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} - It 'Should return the same values as passed' { - $result.SQLServer | Should Be $desiredState.SQLServer - $result.SQLInstanceName | Should Be $desiredState.SQLInstanceName - $result.OptionName | Should Be $desiredState.OptionName - $result.OptionValue | Should Be $desiredState.OptionValue - $result.RestartService | Should Be $desiredState.RestartService - $result.RestartTimeout | Should Be $desiredState.RestartTimeout - } + return $mock + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should call Connect-SQL mock when getting the current state' { - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 1 - } + ## Get the current state + $result = Get-TargetResource @desiredState + + It 'Should return the same values as passed' { + $result.SQLServer | Should Be $desiredState.SQLServer + $result.SQLInstanceName | Should Be $desiredState.SQLInstanceName + $result.OptionName | Should Be $desiredState.OptionName + $result.OptionValue | Should Not Be $desiredState.OptionValue + $result.RestartService | Should Be $desiredState.RestartService + $result.RestartTimeout | Should Be $desiredState.RestartTimeout } - Context 'Invalid data is supplied' { - - Mock -CommandName Connect-SQL -MockWith { - $mock = New-Object PSObject -Property @{ - Configuration = @{ - Properties = @( - @{ - DisplayName = 'user connections' - ConfigValue = 0 - } - ) - } - } - - ## add the Alter method - $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} - - return $mock - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should call New-TerminatingError mock when a bad option name is specified' { - { Get-TargetResource @invalidOption } | Should Throw 'ConfigurationOptionNotFound' - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-TerminatingError -Scope It -Times 1 - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 1 - } + It 'Should call Connect-SQL mock when getting the current state' { + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 1 } } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - - Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName + Context 'The system is in the desired state' { Mock -CommandName Connect-SQL -MockWith { $mock = New-Object PSObject -Property @{ @@ -205,19 +128,24 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { return $mock } -ModuleName $script:DSCResourceName -Verifiable - It 'Should cause Test-TargetResource to return false when not in the desired state' { - Test-TargetResource @defaultState | Should be $false + ## Get the current state + $result = Get-TargetResource @desiredState + + It 'Should return the same values as passed' { + $result.SQLServer | Should Be $desiredState.SQLServer + $result.SQLInstanceName | Should Be $desiredState.SQLInstanceName + $result.OptionName | Should Be $desiredState.OptionName + $result.OptionValue | Should Be $desiredState.OptionValue + $result.RestartService | Should Be $desiredState.RestartService + $result.RestartTimeout | Should Be $desiredState.RestartTimeout } - It 'Should cause Test-TargetResource method to return true' { - Test-TargetResource @desiredState | Should be $true + It 'Should call Connect-SQL mock when getting the current state' { + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 1 } } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName - - Mock -CommandName New-TerminatingError -MockWith {} -ModuleName $script:DSCResourceName + Context 'Invalid data is supplied' { Mock -CommandName Connect-SQL -MockWith { $mock = New-Object PSObject -Property @{ @@ -226,7 +154,6 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { @{ DisplayName = 'user connections' ConfigValue = 0 - IsDynamic = $false } ) } @@ -236,59 +163,119 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} return $mock - } -ModuleName $script:DSCResourceName -Verifiable -ParameterFilter { $SQLServer -eq 'CLU01' } + } -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Connect-SQL -MockWith { - $mock = New-Object PSObject -Property @{ - Configuration = @{ - Properties = @( - @{ - DisplayName = 'show advanced options' - ConfigValue = 1 - IsDynamic = $true - } - ) - } + It 'Should call New-TerminatingError mock when a bad option name is specified' { + { Get-TargetResource @invalidOption } | Should Throw 'ConfigurationOptionNotFound' + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-TerminatingError -Scope It -Times 1 + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 1 + } + } + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + + Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName + + Mock -CommandName Connect-SQL -MockWith { + $mock = New-Object PSObject -Property @{ + Configuration = @{ + Properties = @( + @{ + DisplayName = 'user connections' + ConfigValue = 500 + } + ) } + } - ## add the Alter method - $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} + ## add the Alter method + $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} - return $mock - } -ModuleName $script:DSCResourceName -Verifiable -ParameterFilter { $SQLServer -eq 'CLU02' } + return $mock + } -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Restart-SqlService -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + It 'Should cause Test-TargetResource to return false when not in the desired state' { + Test-TargetResource @defaultState | Should be $false + } - Mock -CommandName New-WarningMessage -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + It 'Should cause Test-TargetResource method to return true' { + Test-TargetResource @desiredState | Should be $true + } + } - Context 'Change the system to the desired state' { - It 'Should not restart SQL for a dynamic option' { - Set-TargetResource @dynamicOption - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Restart-SqlService -Scope It -Times 0 -Exactly - } + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName - It 'Should restart SQL for a non-dynamic option' { - Set-TargetResource @desiredStateRestart - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Restart-SqlService -Scope It -Times 1 -Exactly - } - - It 'Should warn about restart when required, but not requested' { - Set-TargetResource @desiredState + Mock -CommandName New-TerminatingError -MockWith {} -ModuleName $script:DSCResourceName - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-WarningMessage -Scope It -Times 1 -Exactly - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Restart-SqlService -Scope It -Times 0 -Exactly + Mock -CommandName Connect-SQL -MockWith { + $mock = New-Object PSObject -Property @{ + Configuration = @{ + Properties = @( + @{ + DisplayName = 'user connections' + ConfigValue = 0 + IsDynamic = $false + } + ) } + } - It 'Should call Connect-SQL to get option values' { - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 3 + ## add the Alter method + $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} + + return $mock + } -ModuleName $script:DSCResourceName -Verifiable -ParameterFilter { $SQLServer -eq 'CLU01' } + + Mock -CommandName Connect-SQL -MockWith { + $mock = New-Object PSObject -Property @{ + Configuration = @{ + Properties = @( + @{ + DisplayName = 'show advanced options' + ConfigValue = 1 + IsDynamic = $true + } + ) } } + + ## add the Alter method + $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} + + return $mock + } -ModuleName $script:DSCResourceName -Verifiable -ParameterFilter { $SQLServer -eq 'CLU02' } + + Mock -CommandName Restart-SqlService -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + Mock -CommandName New-WarningMessage -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + Context 'Change the system to the desired state' { + It 'Should not restart SQL for a dynamic option' { + Set-TargetResource @dynamicOption + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Restart-SqlService -Scope It -Times 0 -Exactly + } + + It 'Should restart SQL for a non-dynamic option' { + Set-TargetResource @desiredStateRestart + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Restart-SqlService -Scope It -Times 1 -Exactly + } + + It 'Should warn about restart when required, but not requested' { + Set-TargetResource @desiredState + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-WarningMessage -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Restart-SqlService -Scope It -Times 0 -Exactly + } + + It 'Should call Connect-SQL to get option values' { + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 3 + } } } - finally - { - Restore-TestEnvironment -TestEnvironment $TestEnvironment - } } - -$testJob | Receive-Job -Wait +finally +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} diff --git a/Tests/Unit/MSFT_xSQLServerDatabase.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerDatabase.Tests.ps1 index 3444cd967..a0bcab475 100644 --- a/Tests/Unit/MSFT_xSQLServerDatabase.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerDatabase.Tests.ps1 @@ -1,229 +1,216 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerDatabase' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) - if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) - { - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) - } +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerDatabase' - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +#region HEADER - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit +# Unit Test Template Version: 1.1.0 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} - #endregion HEADER +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force - # Begin Testing - try - { - #region Pester Test Initialization +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +#endregion HEADER - $nodeName = 'localhost' - $instanceName = 'MSSQLSERVER' +# Begin Testing +try +{ + #region Pester Test Initialization - $defaultParameters = @{ - SQLInstanceName = $instanceName - SQLServer = $nodeName - } + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') - #endregion Pester Test Initialization + $nodeName = 'localhost' + $instanceName = 'MSSQLSERVER' - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Databases { - return @{ - 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + $defaultParameters = @{ + SQLInstanceName = $instanceName + SQLServer = $nodeName + } - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'UnknownDatabase' - } + #endregion Pester Test Initialization - $result = Get-TargetResource @testParameters + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should return the state as absent' { - $result.Ensure | Should Be 'Absent' - } + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownDatabase' + } - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + $result = Get-TargetResource @testParameters - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should return the state as absent' { + $result.Ensure | Should Be 'Absent' } - - Context 'When the system is in the desired state for a database' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'AdventureWorks' - } - - $result = Get-TargetResource @testParameters - It 'Should return the state as present' { - $result.Ensure | Should Be 'Present' - } + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state for a database' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + } + + $result = Get-TargetResource @testParameters - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should return the state as present' { + $result.Ensure | Should Be 'Present' + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name } - Assert-VerifiableMocks + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } } - - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Databases { - return @{ - 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - It 'Should return the state as absent when desired database does not exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'UnknownDatabase' - } - $result = Test-TargetResource @testParameters - $result | Should Be $false + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Context 'When the system is not in the desired state' { + It 'Should return the state as absent when desired database does not exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownDatabase' } - It 'Should return the state as present when desired database exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'AdventureWorks' - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - $result = Test-TargetResource @testParameters - $result | Should Be $true + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + It 'Should return the state as present when desired database exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' } - } - Context 'When the system is in the desired state' { - It 'Should return the state as present when desired database exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'AdventureWorks' - } + $result = Test-TargetResource @testParameters + $result | Should Be $true - $result = Test-TargetResource @testParameters - $result | Should Be $true + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Context 'When the system is in the desired state' { + It 'Should return the state as present when desired database exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' } - - It 'Should return the state as absent when desired database does not exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'UnknownDatabase' - } - $result = Test-TargetResource @testParameters - $result | Should Be $false + $result = Test-TargetResource @testParameters + $result | Should Be $true - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } + + It 'Should return the state as absent when desired database does not exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownDatabase' + } - Assert-VerifiableMocks + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } } - - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Databases { - return @{ - 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Mock -CommandName New-SqlDatabase -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Remove-SqlDatabase -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - It 'Should call the function New-SqlDatabase when desired database should be present' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'NewDatabase' - Ensure = 'Present' + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - Set-TargetResource @testParameters + Mock -CommandName New-SqlDatabase -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Mock -CommandName Remove-SqlDatabase -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlDatabase -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Context 'When the system is not in the desired state' { + It 'Should call the function New-SqlDatabase when desired database should be present' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'NewDatabase' + Ensure = 'Present' } + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlDatabase -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } + } - Context 'When the system is not in the desired state' { - It 'Should call the function Remove-SqlDatabase when desired database should be absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'AdventureWorks' - Ensure = 'Absent' - } + Context 'When the system is not in the desired state' { + It 'Should call the function Remove-SqlDatabase when desired database should be absent' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + Ensure = 'Absent' + } - Set-TargetResource @testParameters + Set-TargetResource @testParameters - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Remove-SqlDatabase -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Remove-SqlDatabase -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } } } - finally - { - #region FOOTER +} +finally +{ + #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion - } + #endregion } - -$testJob | Receive-Job -Wait diff --git a/Tests/Unit/MSFT_xSQLServerDatabaseOwner.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerDatabaseOwner.Tests.ps1 index c2869ee80..a96023522 100644 --- a/Tests/Unit/MSFT_xSQLServerDatabaseOwner.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerDatabaseOwner.Tests.ps1 @@ -1,270 +1,257 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerDatabaseOwner' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) - if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) - { - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) - } - - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerDatabaseOwner' - $TestEnvironment = Initialize-TestEnvironment -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit - #endregion HEADER +#region HEADER - # Begin Testing - try - { - #region Pester Test Initialization +# Unit Test Template Version: 1.1.0 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force - $defaultParameters = @{ - SQLInstanceName = 'MSSQLSERVER' - SQLServer = 'localhost' - } +$TestEnvironment = Initialize-TestEnvironment -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit +#endregion HEADER - #endregion Pester Test Initialization +# Begin Testing +try +{ + #region Pester Test Initialization - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Databases { - return @{ - 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Database = 'AdventureWorks' - Name = 'CONTOSO\SqlServiceAcct' - } + $defaultParameters = @{ + SQLInstanceName = 'MSSQLSERVER' + SQLServer = 'localhost' + } - Mock -CommandName Get-SqlDatabaseOwner -MockWith { - return $null - } -ModuleName $script:DSCResourceName -Verifiable + #endregion Pester Test Initialization + + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Database = 'AdventureWorks' + Name = 'CONTOSO\SqlServiceAcct' + } - $result = Get-TargetResource @testParameters + Mock -CommandName Get-SqlDatabaseOwner -MockWith { + return $null + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should return the name as null from the get method' { - $result.Name | Should Be $null - } + $result = Get-TargetResource @testParameters - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Database | Should Be $testParameters.Database - } + It 'Should return the name as null from the get method' { + $result.Name | Should Be $null + } - It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseOwner' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Database | Should Be $testParameters.Database } - Context 'When the specified database does not exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Database = 'UnknownDatabase' - Name = 'CONTOSO\SqlServiceAcct' - } + It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseOwner' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } - Mock -CommandName Get-SqlDatabaseOwner -MockWith { - return $null - } -ModuleName $script:DSCResourceName -Verifiable + Context 'When the specified database does not exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Database = 'UnknownDatabase' + Name = 'CONTOSO\SqlServiceAcct' + } - $result = Get-TargetResource @testParameters + Mock -CommandName Get-SqlDatabaseOwner -MockWith { + return $null + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should return the name as null from the get method' { - $result.Name | Should Be $null - } + $result = Get-TargetResource @testParameters - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Database | Should Be $testParameters.Database - } + It 'Should return the name as null from the get method' { + $result.Name | Should Be $null + } - It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseOwner' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Database | Should Be $testParameters.Database } - Context 'When the system is in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Database = 'AdventureWorks' - Name = 'CONTOSO\SqlServiceAcct' - } + It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseOwner' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } - Mock -CommandName Get-SqlDatabaseOwner -MockWith { return 'CONTOSO\SqlServiceAcct' } -ModuleName $script:DSCResourceName -Verifiable + Context 'When the system is in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Database = 'AdventureWorks' + Name = 'CONTOSO\SqlServiceAcct' + } - $result = Get-TargetResource @testParameters + Mock -CommandName Get-SqlDatabaseOwner -MockWith { return 'CONTOSO\SqlServiceAcct' } -ModuleName $script:DSCResourceName -Verifiable - It 'Should return the name of the owner from the get method' { - $result.Name | Should Be $testParameters.Name - } + $result = Get-TargetResource @testParameters - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Database | Should Be $testParameters.Database - } + It 'Should return the name of the owner from the get method' { + $result.Name | Should Be $testParameters.Name + } - It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseOwner' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Database | Should Be $testParameters.Database } - Assert-VerifiableMocks + It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseOwner' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Databases { - return @{ - 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + Assert-VerifiableMocks + } - Context 'When the system is not in the desired state' { - It 'Should return the state as false when desired login is not the database owner' { - $testParameters = $defaultParameters - $testParameters += @{ - Database = 'AdventureWorks' - Name = 'CONTOSO\SqlServiceAcct' - } + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + It 'Should return the state as false when desired login is not the database owner' { + $testParameters = $defaultParameters + $testParameters += @{ + Database = 'AdventureWorks' + Name = 'CONTOSO\SqlServiceAcct' + } - Mock -CommandName Get-SqlDatabaseOwner -MockWith { - return $null - } -ModuleName $script:DSCResourceName -Verifiable + Mock -CommandName Get-SqlDatabaseOwner -MockWith { + return $null + } -ModuleName $script:DSCResourceName -Verifiable - $result = Test-TargetResource @testParameters - $result | Should Be $false + $result = Test-TargetResource @testParameters + $result | Should Be $false - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } + } - Context 'When the system is in the desired state' { - It 'Should return the state as true when desired login is the database owner' { - $testParameters = $defaultParameters - $testParameters += @{ - Database = 'AdventureWorks' - Name = 'CONTOSO\SqlServiceAcct' - } + Context 'When the system is in the desired state' { + It 'Should return the state as true when desired login is the database owner' { + $testParameters = $defaultParameters + $testParameters += @{ + Database = 'AdventureWorks' + Name = 'CONTOSO\SqlServiceAcct' + } - Mock -CommandName Get-SqlDatabaseOwner -MockWith { - 'CONTOSO\SqlServiceAcct' - } -ModuleName $script:DSCResourceName -Verifiable + Mock -CommandName Get-SqlDatabaseOwner -MockWith { + 'CONTOSO\SqlServiceAcct' + } -ModuleName $script:DSCResourceName -Verifiable - $result = Test-TargetResource @testParameters - $result | Should Be $true + $result = Test-TargetResource @testParameters + $result | Should Be $true - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - - Assert-VerifiableMocks } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Databases { - return @{ - 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + Assert-VerifiableMocks + } - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Database = 'AdventureWorks' - Name = 'CONTOSO\SqlServiceAcct' - } + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Database = 'AdventureWorks' + Name = 'CONTOSO\SqlServiceAcct' + } - It 'Should call the function Set-SqlDatabaseOwner when desired login is not the database owner' { - Mock -CommandName Set-SqlDatabaseOwner -MockWith { } -ModuleName $script:DSCResourceName -Verifiable - - Set-TargetResource @testParameters - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should call the function Set-SqlDatabaseOwner when desired login is not the database owner' { + Mock -CommandName Set-SqlDatabaseOwner -MockWith { } -ModuleName $script:DSCResourceName -Verifiable - $testParameters.Database = 'UnknownDatabase' - - It 'Should throw an error when desired database does not exist' { - Mock -CommandName Set-SqlDatabaseOwner -MockWith { - return Throw - } -ModuleName $script:DSCResourceName -Verifiable - - { Set-TargetResource @testParameters } | Should Throw "Failed to setting the owner of database UnknownDatabase" - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } + + $testParameters.Database = 'UnknownDatabase' - Context 'When the system is in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Database = 'AdventureWorks' - Name = 'CONTOSO\SqlServiceAcct' - } - - It 'Should not call the function Set-SqlDatabaseOwner when desired login is the database owner' { - Mock -CommandName Get-SqlDatabaseOwner -MockWith { - 'CONTOSO\SqlServiceAcct' - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters + It 'Should throw an error when desired database does not exist' { + Mock -CommandName Set-SqlDatabaseOwner -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Throw "Failed to setting the owner of database UnknownDatabase" - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - - Assert-VerifiableMocks } - } - finally - { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Context 'When the system is in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Database = 'AdventureWorks' + Name = 'CONTOSO\SqlServiceAcct' + } + + It 'Should not call the function Set-SqlDatabaseOwner when desired login is the database owner' { + Mock -CommandName Get-SqlDatabaseOwner -MockWith { + 'CONTOSO\SqlServiceAcct' + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + } - #endregion + Assert-VerifiableMocks } } +finally +{ + #region FOOTER -$testJob | Receive-Job -Wait + Restore-TestEnvironment -TestEnvironment $TestEnvironment + + #endregion +} diff --git a/Tests/Unit/MSFT_xSQLServerEndpointPermission.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerEndpointPermission.Tests.ps1 index 81570f34c..f98ecf322 100644 --- a/Tests/Unit/MSFT_xSQLServerEndpointPermission.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerEndpointPermission.Tests.ps1 @@ -1,62 +1,141 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerEndpointPermission' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) - if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) - { - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerEndpointPermission' + +#region HEADER + +# Unit Test Template Version: 1.1.0 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Test Initialization + + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + + $nodeName = 'localhost' + $instanceName = 'DEFAULT' + $principal = 'COMPANY\SqlServiceAcct' + $otherPrincipal = 'COMPANY\OtherAcct' + $endpointName = 'DefaultEndpointMirror' + + $defaultParameters = @{ + InstanceName = $instanceName + NodeName = $nodeName + Name = $endpointName + Principal = $principal } - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + #endregion Pester Test Initialization + + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $otherPrincipal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the desired state as absent' { + $result.Ensure | Should Be 'Absent' + } - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + $result.Principal | Should Be $testParameters.Principal + } - #endregion HEADER + It 'Should not return any permissions' { + $result.Permission | Should Be '' + } - # Begin Testing - try - { - #region Pester Test Initialization + It 'Should call the mock function Get-SQLAlwaysOnEndpoint' { + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state' { + $testParameters = $defaultParameters + + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $principal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the desired state as present' { + $result.Ensure | Should Be 'Present' + } - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + $result.Principal | Should Be $testParameters.Principal + } - $nodeName = 'localhost' - $instanceName = 'DEFAULT' - $principal = 'COMPANY\SqlServiceAcct' - $otherPrincipal = 'COMPANY\OtherAcct' - $endpointName = 'DefaultEndpointMirror' + It 'Should return the permissions passed as parameter' { + $result.Permission | Should Be 'CONNECT' + } - $defaultParameters = @{ - InstanceName = $instanceName - NodeName = $nodeName - Name = $endpointName - Principal = $principal + It 'Should call the mock function Get-SQLAlwaysOnEndpoint' { + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } } - #endregion Pester Test Initialization + Assert-VerifiableMocks + } - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + Permission = 'CONNECT' + } + It 'Should return that desired state is absent when wanted desired state is to be Present' { Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint return New-Object Object | @@ -71,32 +150,20 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { ) } -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters - - It 'Should return the desired state as absent' { - $result.Ensure | Should Be 'Absent' - } - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - $result.Principal | Should Be $testParameters.Principal - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should not return any permissions' { - $result.Permission | Should Be '' - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should call the mock function Get-SQLAlwaysOnEndpoint' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' + Permission = 'CONNECT' } - - Context 'When the system is in the desired state' { - $testParameters = $defaultParameters + It 'Should return that desired state is absent when wanted desired state is to be Absent' { Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint return New-Object Object | @@ -111,331 +178,251 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { ) } -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters - It 'Should return the desired state as present' { - $result.Ensure | Should Be 'Present' - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - $result.Principal | Should Be $testParameters.Principal - } - - It 'Should return the permissions passed as parameter' { - $result.Permission | Should Be 'CONNECT' - } - - It 'Should call the mock function Get-SQLAlwaysOnEndpoint' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - - Assert-VerifiableMocks } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Context 'When the system is not in the desired state' { + Context 'When the system is in the desired state' { + It 'Should return that desired state is present when wanted desired state is to be Present' { $testParameters = $defaultParameters $testParameters += @{ Ensure = 'Present' Permission = 'CONNECT' } - It 'Should return that desired state is absent when wanted desired state is to be Present' { - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $otherPrincipal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $principal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - $result = Test-TargetResource @testParameters - $result | Should Be $false + $result = Test-TargetResource @testParameters + $result | Should Be $true - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + It 'Should return that desired state is present when wanted desired state is to be Absent' { $testParameters = $defaultParameters $testParameters += @{ Ensure = 'Absent' Permission = 'CONNECT' } - It 'Should return that desired state is absent when wanted desired state is to be Absent' { - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $principal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $otherPrincipal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - $result = Test-TargetResource @testParameters - $result | Should Be $false + $result = Test-TargetResource @testParameters + $result | Should Be $true - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } + } - Context 'When the system is in the desired state' { - It 'Should return that desired state is present when wanted desired state is to be Present' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - Permission = 'CONNECT' - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $principal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Test-TargetResource @testParameters - $result | Should Be $true + Assert-VerifiableMocks + } - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Context 'When the system is not in the desired state' { + It 'Should call the the method Grant when desired state is to be Present' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + Permission = 'CONNECT' } - It 'Should return that desired state is present when wanted desired state is to be Absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Permission = 'CONNECT' - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $otherPrincipal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $otherPrincipal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru | + Add-Member ScriptMethod Grant { + param( + $permissionSet, + $principal + ) + return + } -PassThru | + Add-Member ScriptMethod Revoke { + param( + $permissionSet, + $principal + ) + throw 'Called Revoke() when it shouldn''t been called' + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - $result = Test-TargetResource @testParameters - $result | Should Be $true + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It } - Assert-VerifiableMocks - } + It 'Should call the the method Revoke when desired state is to be Absent' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' + Permission = 'CONNECT' + } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Context 'When the system is not in the desired state' { - It 'Should call the the method Grant when desired state is to be Present' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - Permission = 'CONNECT' - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $otherPrincipal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) - ) - } -PassThru | - Add-Member ScriptMethod Grant { - param( - $permissionSet, - $principal - ) - return - } -PassThru | - Add-Member ScriptMethod Revoke { - param( - $permissionSet, - $principal + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $principal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru ) - throw 'Called Revoke() when it shouldn''t been called' - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru | + Add-Member ScriptMethod Grant { + param( + $permissionSet, + $principal + ) + throw 'Called Grant() when it shouldn''t been called' + } -PassThru | + Add-Member ScriptMethod Revoke { + param( + $permissionSet, + $principal + ) + return + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Not Throw - { Set-TargetResource @testParameters } | Should Not Throw + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It + } + } - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It + Context 'When the system is in the desired state' { + It 'Should not throw error when desired state is already Present' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + Permission = 'CONNECT' } - It 'Should call the the method Revoke when desired state is to be Absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Permission = 'CONNECT' - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $principal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) - ) - } -PassThru | - Add-Member ScriptMethod Grant { - param( - $permissionSet, - $principal - ) - throw 'Called Grant() when it shouldn''t been called' - } -PassThru | - Add-Member ScriptMethod Revoke { - param( - $permissionSet, - $principal + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $principal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru ) - return - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru | + Add-Member ScriptMethod Grant { + param( + $permissionSet, + $principal + ) + throw 'Called Grant() when it shouldn''t been called' + } -PassThru | + Add-Member ScriptMethod Revoke { + param( + $permissionSet, + $principal + ) + throw 'Called Revoke() when it shouldn''t been called' + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - { Set-TargetResource @testParameters } | Should Not Throw + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Context 'When the system is in the desired state' { - It 'Should not throw error when desired state is already Present' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - Permission = 'CONNECT' - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $principal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) - ) - } -PassThru | - Add-Member ScriptMethod Grant { - param( - $permissionSet, - $principal - ) - throw 'Called Grant() when it shouldn''t been called' - } -PassThru | - Add-Member ScriptMethod Revoke { - param( - $permissionSet, - $principal - ) - throw 'Called Revoke() when it shouldn''t been called' - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - { Set-TargetResource @testParameters } | Should Not Throw - - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + It 'Should not throw error when desired state is already Absent' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' + Permission = 'CONNECT' } - It 'Should not throw error when desired state is already Absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Permission = 'CONNECT' - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $otherPrincipal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) - ) - } -PassThru | - Add-Member ScriptMethod Grant { - param( - $permissionSet, - $principal - ) - throw 'Called Grant() when it shouldn''t been called' - } -PassThru | - Add-Member ScriptMethod Revoke { - param( - $permissionSet, - $principal + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $otherPrincipal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru ) - throw 'Called Revoke() when it shouldn''t been called' - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru | + Add-Member ScriptMethod Grant { + param( + $permissionSet, + $principal + ) + throw 'Called Grant() when it shouldn''t been called' + } -PassThru | + Add-Member ScriptMethod Revoke { + param( + $permissionSet, + $principal + ) + throw 'Called Revoke() when it shouldn''t been called' + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - { Set-TargetResource @testParameters } | Should Not Throw + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Assert-VerifiableMocks } + Assert-VerifiableMocks } - finally - { - #region FOOTER +} +finally +{ + #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion - } + #endregion } - -$testJob | Receive-Job -Wait diff --git a/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 index 290752bbc..c547725ef 100644 --- a/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 @@ -2,464 +2,451 @@ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param() -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerLogin' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) - if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) - { - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) - } +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerLogin' - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +#region HEADER - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit +# Unit Test Template Version: 1.1.0 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} - #endregion HEADER +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force - # Begin Testing - try - { - #region Pester Test Initialization +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +#endregion HEADER - $nodeName = 'localhost' - $instanceName = 'MSSQLSERVER' +# Begin Testing +try +{ + #region Pester Test Initialization - $mockSqlLoginUser = "dba" - $mockSqlLoginPassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force - $mockSqlLoginCredential = New-Object System.Management.Automation.PSCredential( $mockSqlLoginUser, $mockSqlLoginPassword ) + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') - $defaultParameters = @{ - SQLInstanceName = $instanceName - SQLServer = $nodeName - } + $nodeName = 'localhost' + $instanceName = 'MSSQLSERVER' - #endregion Pester Test Initialization - - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Logins { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\Stacy') -Property @{ LoginType = 'WindowsUser'} ) ) - 'John' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'John') -Property @{ LoginType = 'SqlLogin'} ) ) - 'COMPANY\SqlUsers' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\SqlUsers') -Property @{ LoginType = 'WindowsGroup'} ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\UnknownUser' - } + $mockSqlLoginUser = "dba" + $mockSqlLoginPassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force + $mockSqlLoginCredential = New-Object System.Management.Automation.PSCredential( $mockSqlLoginUser, $mockSqlLoginPassword ) - $result = Get-TargetResource @testParameters + $defaultParameters = @{ + SQLInstanceName = $instanceName + SQLServer = $nodeName + } - It 'Should not return the state as absent' { - $result.Ensure | Should Be 'Absent' - $result.LoginType | Should Be '' - } + #endregion Pester Test Initialization - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Logins { + return @{ + 'COMPANY\Stacy' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\Stacy') -Property @{ LoginType = 'WindowsUser'} ) ) + 'John' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'John') -Property @{ LoginType = 'SqlLogin'} ) ) + 'COMPANY\SqlUsers' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\SqlUsers') -Property @{ LoginType = 'WindowsGroup'} ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'COMPANY\UnknownUser' } - - Context 'When the system is in the desired state for a Windows user' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\Stacy' - } - - $result = Get-TargetResource @testParameters - It 'Should not return the state as present' { - $result.Ensure | Should Be 'Present' - $result.LoginType | Should Be 'WindowsUser' - } + $result = Get-TargetResource @testParameters - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } - - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should not return the state as absent' { + $result.Ensure | Should Be 'Absent' + $result.LoginType | Should Be '' } - Context 'When the system is in the desired state for a Windows group' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\SqlUsers' - } - - $result = Get-TargetResource @testParameters - - It 'Should return the state as present' { - $result.Ensure | Should Be 'Present' - $result.LoginType | Should Be 'WindowsGroup' - } - - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } - - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name } - Context 'When the system is in the desired state for a SQL login' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'John' - } - - $result = Get-TargetResource @testParameters - - It 'Should return the state as present' { - $result.Ensure | Should Be 'Present' - $result.LoginType | Should Be 'SqlLogin' - } - - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } - - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context } - - Assert-VerifiableMocks } + + Context 'When the system is in the desired state for a Windows user' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'COMPANY\Stacy' + } + + $result = Get-TargetResource @testParameters - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Logins { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\Stacy') -Property @{ LoginType = 'WindowsUser'} ) ) - 'John' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'John') -Property @{ LoginType = 'SqlLogin'} ) ) - 'COMPANY\SqlUsers' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\SqlUsers') -Property @{ LoginType = 'WindowsGroup'} ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - It 'Should return the state as absent when desired windows user does not exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\UnknownUser' - } + It 'Should not return the state as present' { + $result.Ensure | Should Be 'Present' + $result.LoginType | Should Be 'WindowsUser' + } - $result = Test-TargetResource @testParameters - $result | Should Be $false + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } - It 'Should return the state as present when desired login exists and login type is SQL login' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\SqlUsers' - LoginType = 'SqlLogin' - } + Context 'When the system is in the desired state for a Windows group' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'COMPANY\SqlUsers' + } + + $result = Get-TargetResource @testParameters - $result = Test-TargetResource @testParameters - $result | Should Be $true + It 'Should return the state as present' { + $result.Ensure | Should Be 'Present' + $result.LoginType | Should Be 'WindowsGroup' + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } - It 'Should return the state as present when desired login exists and login type is Windows' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'John' - LoginType = 'WindowsUser' - } + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } - $result = Test-TargetResource @testParameters - $result | Should Be $true + Context 'When the system is in the desired state for a SQL login' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'John' + } + + $result = Get-TargetResource @testParameters - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should return the state as present' { + $result.Ensure | Should Be 'Present' + $result.LoginType | Should Be 'SqlLogin' } - Context 'When the system is in the desired state' { - It 'Should return the state as present when desired windows user exists' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\Stacy' - } + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } - $result = Test-TargetResource @testParameters - $result | Should Be $true + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-VerifiableMocks + } - It 'Should return the state as present when desired windows group exists' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\SqlUsers' - LoginType = 'WindowsGroup' + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Logins { + return @{ + 'COMPANY\Stacy' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\Stacy') -Property @{ LoginType = 'WindowsUser'} ) ) + 'John' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'John') -Property @{ LoginType = 'SqlLogin'} ) ) + 'COMPANY\SqlUsers' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\SqlUsers') -Property @{ LoginType = 'WindowsGroup'} ) ) } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Context 'When the system is not in the desired state' { + It 'Should return the state as absent when desired windows user does not exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'COMPANY\UnknownUser' } - It 'Should return the state as present when desired sql login exists' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'John' - LoginType = 'SqlLogin' - } - - $result = Test-TargetResource @testParameters - $result | Should Be $true + $result = Test-TargetResource @testParameters + $result | Should Be $false - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Assert-VerifiableMocks - } - - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Logins { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\Stacy') -Property @{ LoginType = 'WindowsUser'} ) ) - 'John' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'John') -Property @{ LoginType = 'SqlLogin'} ) ) - 'COMPANY\SqlUsers' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\SqlUsers') -Property @{ LoginType = 'WindowsGroup'} ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { + It 'Should return the state as present when desired login exists and login type is SQL login' { $testParameters = $defaultParameters $testParameters += @{ - Name = 'UnknownSqlLogin' + Name = 'COMPANY\SqlUsers' LoginType = 'SqlLogin' } - It 'Should throw an error when desired login type is a SQL login and LoginCredential parameter is not passed' { - { Set-TargetResource @testParameters } | Should Throw - Assert-MockCalled Connect-SQL -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } - - $testParameters += @{ - LoginCredential = $mockSqlLoginCredential - } - - It 'Should not throw an error when desired login type is a SQL login' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'SqlLogin' - } - } -ModuleName $script:DSCResourceName -Verifiable + $result = Test-TargetResource @testParameters + $result | Should Be $true - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + It 'Should return the state as present when desired login exists and login type is Windows' { $testParameters = $defaultParameters $testParameters += @{ - Name = 'COMPANY\UnknownUser' + Name = 'John' LoginType = 'WindowsUser' } - It 'Should not throw an error when desired login type is a Windows User' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'WindowsUser' - } - } -ModuleName $script:DSCResourceName -Verifiable + $result = Test-TargetResource @testParameters + $result | Should Be $true - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + Context 'When the system is in the desired state' { + It 'Should return the state as present when desired windows user exists' { $testParameters = $defaultParameters $testParameters += @{ - Name = 'COMPANY\UnknownGroup' - LoginType = 'WindowsGroup' + Name = 'COMPANY\Stacy' } - It 'Should not throw an error when desired login type is a Windows Group' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'WindowsGroup' - } - } -ModuleName $script:DSCResourceName -Verifiable + $result = Test-TargetResource @testParameters + $result | Should Be $true - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + It 'Should return the state as present when desired windows group exists' { $testParameters = $defaultParameters $testParameters += @{ - Ensure = 'Absent' - Name = 'COMPANY\Stacy' + Name = 'COMPANY\SqlUsers' + LoginType = 'WindowsGroup' } - It 'Should call the function Remove-SqlLogin when desired state should be absent' { - # Mock the return value from the Get-method, because Test-method is ran at the end of the Set-method to validate that the removal (in this case) was successful. - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Absent' - } - } -ModuleName $script:DSCResourceName -Verifiable + $result = Test-TargetResource @testParameters + $result | Should Be $true - Mock -CommandName Remove-SqlLogin -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - Set-TargetResource @testParameters - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Remove-SqlLogin -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Context 'When the system is in the desired state' { + It 'Should return the state as present when desired sql login exists' { $testParameters = $defaultParameters $testParameters += @{ Name = 'John' LoginType = 'SqlLogin' } - It 'Should throw an error when desired login type is a SQL login and LoginCredential parameter is not passed' { - { Set-TargetResource @testParameters } | Should Throw - Assert-MockCalled Connect-SQL -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } + $result = Test-TargetResource @testParameters + $result | Should Be $true - $testParameters += @{ - LoginCredential = $mockSqlLoginCredential - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } - It 'Should not throw an error when desired login type is a SQL login' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'SqlLogin' - } - } -ModuleName $script:DSCResourceName -Verifiable + Assert-VerifiableMocks + } - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Logins { + return @{ + 'COMPANY\Stacy' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\Stacy') -Property @{ LoginType = 'WindowsUser'} ) ) + 'John' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'John') -Property @{ LoginType = 'SqlLogin'} ) ) + 'COMPANY\SqlUsers' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\SqlUsers') -Property @{ LoginType = 'WindowsGroup'} ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownSqlLogin' + LoginType = 'SqlLogin' + } - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\Stacy' - LoginType = 'WindowsUser' - } + It 'Should throw an error when desired login type is a SQL login and LoginCredential parameter is not passed' { + { Set-TargetResource @testParameters } | Should Throw + Assert-MockCalled Connect-SQL -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should not throw an error when desired login type is a Windows User' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'WindowsUser' - } - } -ModuleName $script:DSCResourceName -Verifiable + $testParameters += @{ + LoginCredential = $mockSqlLoginCredential + } - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should not throw an error when desired login type is a SQL login' { + Mock -CommandName Get-TargetResource -MockWith { + @{ + Ensure = 'Present' + LoginType = 'SqlLogin' + } + } -ModuleName $script:DSCResourceName -Verifiable - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\SqlUsers' - LoginType = 'WindowsGroup' - } + { Set-TargetResource @testParameters } | Should Not Throw + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should not throw an error when desired login type is a Windows Group' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'WindowsGroup' - } - } -ModuleName $script:DSCResourceName -Verifiable + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'COMPANY\UnknownUser' + LoginType = 'WindowsUser' + } - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should not throw an error when desired login type is a Windows User' { + Mock -CommandName Get-TargetResource -MockWith { + @{ + Ensure = 'Present' + LoginType = 'WindowsUser' + } + } -ModuleName $script:DSCResourceName -Verifiable - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Name = 'COMPANY\UnknownUser' - LoginType = 'SqlLogin' - } + { Set-TargetResource @testParameters } | Should Not Throw + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should not call the function Remove-SqlLogin when desired state is already absent' { - # Mock the return value from the Get-method, because Test-method is ran at the end of the Set-method to validate that the removal (in this case) was successful. - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Absent' - } - } -ModuleName $script:DSCResourceName -Verifiable + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'COMPANY\UnknownGroup' + LoginType = 'WindowsGroup' + } - Mock -CommandName Remove-SqlLogin -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + It 'Should not throw an error when desired login type is a Windows Group' { + Mock -CommandName Get-TargetResource -MockWith { + @{ + Ensure = 'Present' + LoginType = 'WindowsGroup' + } + } -ModuleName $script:DSCResourceName -Verifiable - Set-TargetResource @testParameters + { Set-TargetResource @testParameters } | Should Not Throw + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Remove-SqlLogin -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' + Name = 'COMPANY\Stacy' } - Assert-VerifiableMocks + It 'Should call the function Remove-SqlLogin when desired state should be absent' { + # Mock the return value from the Get-method, because Test-method is ran at the end of the Set-method to validate that the removal (in this case) was successful. + Mock -CommandName Get-TargetResource -MockWith { + @{ + Ensure = 'Absent' + } + } -ModuleName $script:DSCResourceName -Verifiable + + Mock -CommandName Remove-SqlLogin -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Remove-SqlLogin -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } } - } - finally - { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Context 'When the system is in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'John' + LoginType = 'SqlLogin' + } - #endregion + It 'Should throw an error when desired login type is a SQL login and LoginCredential parameter is not passed' { + { Set-TargetResource @testParameters } | Should Throw + Assert-MockCalled Connect-SQL -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + + $testParameters += @{ + LoginCredential = $mockSqlLoginCredential + } + + It 'Should not throw an error when desired login type is a SQL login' { + Mock -CommandName Get-TargetResource -MockWith { + @{ + Ensure = 'Present' + LoginType = 'SqlLogin' + } + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Not Throw + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'COMPANY\Stacy' + LoginType = 'WindowsUser' + } + + It 'Should not throw an error when desired login type is a Windows User' { + Mock -CommandName Get-TargetResource -MockWith { + @{ + Ensure = 'Present' + LoginType = 'WindowsUser' + } + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Not Throw + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'COMPANY\SqlUsers' + LoginType = 'WindowsGroup' + } + + It 'Should not throw an error when desired login type is a Windows Group' { + Mock -CommandName Get-TargetResource -MockWith { + @{ + Ensure = 'Present' + LoginType = 'WindowsGroup' + } + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Not Throw + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' + Name = 'COMPANY\UnknownUser' + LoginType = 'SqlLogin' + } + + It 'Should not call the function Remove-SqlLogin when desired state is already absent' { + # Mock the return value from the Get-method, because Test-method is ran at the end of the Set-method to validate that the removal (in this case) was successful. + Mock -CommandName Get-TargetResource -MockWith { + @{ + Ensure = 'Absent' + } + } -ModuleName $script:DSCResourceName -Verifiable + + Mock -CommandName Remove-SqlLogin -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Remove-SqlLogin -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + } + + Assert-VerifiableMocks } } +finally +{ + #region FOOTER -$testJob | Receive-Job -Wait + Restore-TestEnvironment -TestEnvironment $TestEnvironment + + #endregion +} diff --git a/Tests/Unit/MSFT_xSQLServerPermission.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerPermission.Tests.ps1 index 1e31dee8d..273563bad 100644 --- a/Tests/Unit/MSFT_xSQLServerPermission.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerPermission.Tests.ps1 @@ -1,58 +1,137 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerPermission' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) - if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) - { - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerPermission' + +#region HEADER + +# Unit Test Template Version: 1.1.0 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Test Initialization + + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + + $nodeName = 'localhost' + $instanceName = 'DEFAULT' + $principal = 'COMPANY\SqlServiceAcct' + $permission = @( 'AlterAnyAvailabilityGroup','ViewServerState' ) + + #endregion Pester Test Initialization + + $defaultParameters = @{ + InstanceName = $instanceName + NodeName = $nodeName + Principal = $principal + Permission = $permission } - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Context 'When the system is not in the desired state' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $principal - #endregion HEADER + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters - # Begin Testing - try - { - #region Pester Test Initialization + $result = Get-TargetResource @testParameters - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + It 'Should return the desired state as absent' { + $result.Ensure | Should Be 'Absent' + } - $nodeName = 'localhost' - $instanceName = 'DEFAULT' - $principal = 'COMPANY\SqlServiceAcct' - $permission = @( 'AlterAnyAvailabilityGroup','ViewServerState' ) + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $nodeName + $result.InstanceName | Should Be $instanceName + $result.Principal | Should Be $principal + } - #endregion Pester Test Initialization + It 'Should not return any permissions' { + $result.Permission | Should Be $null + } - $defaultParameters = @{ - InstanceName = $instanceName - NodeName = $nodeName - Principal = $principal - Permission = $permission + It 'Should call the mock function Get-SQLPSInstance' { + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } } + + Context 'When the system is in the desired state' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true + + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $principal + + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + + $result = Get-TargetResource @testParameters + + It 'Should return the desired state as present' { + $result.Ensure | Should Be 'Present' + } - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Context 'When the system is not in the desired state' { + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $nodeName + $result.InstanceName | Should Be $instanceName + $result.Principal | Should Be $principal + } + + It 'Should return the permissions passed as parameter' { + foreach ($currentPermission in $permission) { + if( $result.Permission -ccontains $currentPermission ) { + $permissionState = $true + } else { + $permissionState = $false + break + } + } + + $permissionState | Should Be $true + } + + It 'Should call the mock function Get-SQLPSInstance' { + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Context 'When the system is not in the desired state' { + It 'Should return that desired state is absent when wanted desired state is to be Present' { Mock -CommandName Get-SQLPSInstance -MockWith { [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false @@ -67,29 +146,44 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { } -ModuleName $script:DSCResourceName -Verifiable $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + } - $result = Get-TargetResource @testParameters + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return the desired state as absent' { - $result.Ensure | Should Be 'Absent' - } + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $nodeName - $result.InstanceName | Should Be $instanceName - $result.Principal | Should Be $principal - } + It 'Should return that desired state is absent when wanted desired state is to be Absent' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - It 'Should not return any permissions' { - $result.Permission | Should Be $null - } + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $principal - It 'Should call the mock function Get-SQLPSInstance' { - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - - Context 'When the system is in the desired state' { + } + + Context 'When the system is in the desired state' { + It 'Should return that desired state is present when wanted desired state is to be Present' { Mock -CommandName Get-SQLPSInstance -MockWith { [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true @@ -104,260 +198,154 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { } -ModuleName $script:DSCResourceName -Verifiable $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + } - $result = Get-TargetResource @testParameters + $result = Test-TargetResource @testParameters + $result | Should Be $true - It 'Should return the desired state as present' { - $result.Ensure | Should Be 'Present' - } + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $nodeName - $result.InstanceName | Should Be $instanceName - $result.Principal | Should Be $principal - } + It 'Should return that desired state is present when wanted desired state is to be Absent' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - It 'Should return the permissions passed as parameter' { - foreach ($currentPermission in $permission) { - if( $result.Permission -ccontains $currentPermission ) { - $permissionState = $true - } else { - $permissionState = $false - break - } - } - - $permissionState | Should Be $true - } + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $principal - It 'Should call the mock function Get-SQLPSInstance' { - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' } - } - Assert-VerifiableMocks + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Context 'When the system is not in the desired state' { - It 'Should return that desired state is absent when wanted desired state is to be Present' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - } + Assert-VerifiableMocks + } - $result = Test-TargetResource @testParameters - $result | Should Be $false + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Context 'When the system is not in the desired state' { + It 'Should not throw error when desired state is to be Present' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $principal - It 'Should return that desired state is absent when wanted desired state is to be Absent' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - } + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + } - $result = Test-TargetResource @testParameters - $result | Should Be $false + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It } - Context 'When the system is in the desired state' { - It 'Should return that desired state is present when wanted desired state is to be Present' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - } + It 'Should not throw error when desired state is to be Absent' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - $result = Test-TargetResource @testParameters - $result | Should Be $true + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $principal - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' } - It 'Should return that desired state is present when wanted desired state is to be Absent' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - } - - $result = Test-TargetResource @testParameters - $result | Should Be $true + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It } - - Assert-VerifiableMocks } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Context 'When the system is not in the desired state' { - It 'Should not throw error when desired state is to be Present' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - } + Context 'When the system is in the desired state' { + It 'Should not throw error when desired state is to be Present' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - { Set-TargetResource @testParameters } | Should Not Throw + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = 'Should not call Grant() or Revoke()' - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' } - It 'Should not throw error when desired state is to be Absent' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - } - - { Set-TargetResource @testParameters } | Should Not Throw + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Context 'When the system is in the desired state' { - It 'Should not throw error when desired state is to be Present' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = 'Should not call Grant() or Revoke()' - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - } + It 'Should not throw error when desired state is to be Absent' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - { Set-TargetResource @testParameters } | Should Not Throw + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = 'Should not call Grant() or Revoke()' - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' } - It 'Should not throw error when desired state is to be Absent' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = 'Should not call Grant() or Revoke()' - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - } - - { Set-TargetResource @testParameters } | Should Not Throw + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - #> - Assert-VerifiableMocks } +#> + Assert-VerifiableMocks } - finally - { - #region FOOTER +} +finally +{ + #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion - } + #endregion } - -$testJob | Receive-Job -Wait diff --git a/Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 index dee7d868c..451988992 100644 --- a/Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 @@ -1,275 +1,263 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerRole' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) - if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) - { - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) - } - - Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerRole' - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit +#region HEADER - #endregion HEADER +# Unit Test Template Version: 1.1.0 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} - # Begin Testing - try - { - #region Pester Test Initialization +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit - $nodeName = 'localhost' - $instanceName = 'MSSQLSERVER' +#endregion HEADER - $defaultParameters = @{ - SQLInstanceName = $instanceName - SQLServer = $nodeName - ServerRole = 'dbcreator' - } +# Begin Testing +try +{ + #region Pester Test Initialization - #endregion Pester Test Initialization + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Roles { - return @{ - 'dbcreator' = @( ( New-Object Microsoft.SqlServer.Management.Smo.ServerRole -ArgumentList @( $null, 'CONTOSO\SQL-Admin')) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + $nodeName = 'localhost' + $instanceName = 'MSSQLSERVER' - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'UnknownUser' - } - - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable + $defaultParameters = @{ + SQLInstanceName = $instanceName + SQLServer = $nodeName + ServerRole = 'dbcreator' + } - $result = Get-TargetResource @testParameters + #endregion Pester Test Initialization - It 'Should return the state as absent' { - $result.Ensure | Should Be 'Absent' - } - - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Roles { + return @{ + 'dbcreator' = @( ( New-Object Microsoft.SqlServer.Management.Smo.ServerRole -ArgumentList @( $null, 'CONTOSO\SQL-Admin')) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should call the mock function Connect-SQL and Confirm-SqlServerRoleMember' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownUser' } - - Context 'When the system is in the desired state for a loginName' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'CONTOSO\SQL-Admin' - Ensure = 'Present' - } - - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable + + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @testParameters - It 'Should return the state as present' { - $result.Ensure | Should Be 'Present' - } - - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + It 'Should return the state as absent' { + $result.Ensure | Should Be 'Absent' + } - It 'Should call the mock function Connect-SQL and Confirm-SqlServerRoleMember' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name } - Assert-VerifiableMocks + It 'Should call the mock function Connect-SQL and Confirm-SqlServerRoleMember' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } } + + Context 'When the system is in the desired state for a loginName' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'CONTOSO\SQL-Admin' + Ensure = 'Present' + } + + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Roles { - return @{ - 'dbcreator' = @( ( New-Object Microsoft.SqlServer.Management.Smo.ServerRole -ArgumentList @( $null, 'CONTOSO\SQL-Admin')) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - It 'Should return the test as false when desired loginName does not exist' { - - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'UnknownUser' - Ensure = 'Present' - } + $result = Get-TargetResource @testParameters - $result = Test-TargetResource @testParameters - $result | Should Be $false + It 'Should return the state as present' { + $result.Ensure | Should Be 'Present' + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } - It 'Should return the test as false when non-desired loginName exist' { + It 'Should call the mock function Connect-SQL and Confirm-SqlServerRoleMember' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable + Assert-VerifiableMocks + } - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'NonDesiredUser' - Ensure = 'Absent' + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Roles { + return @{ + 'dbcreator' = @( ( New-Object Microsoft.SqlServer.Management.Smo.ServerRole -ArgumentList @( $null, 'CONTOSO\SQL-Admin')) ) } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + It 'Should return the test as false when desired loginName does not exist' { - $result = Test-TargetResource @testParameters - $result | Should Be $false + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownUser' + Ensure = 'Present' } - } - Context 'When the system is in the desired state' { - It 'Should return the test as true when desired loginName exist' { + $result = Test-TargetResource @testParameters + $result | Should Be $false - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'CONTOSO\SQL-Admin' - Ensure = 'Present' - } + It 'Should return the test as false when non-desired loginName exist' { - $result = Test-TargetResource @testParameters - $result | Should Be $true + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'NonDesiredUser' + Ensure = 'Absent' } - - It 'Should return the test as true when non-desired loginName does not exist' { - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable + $result = Test-TargetResource @testParameters + $result | Should Be $false - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'CONTOSO\SQL-Admin' - Ensure = 'Absent' - } - - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - - Assert-VerifiableMocks } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Roles { - return @{ - 'dbcreator' = @( ( New-Object Microsoft.SqlServer.Management.Smo.ServerRole -ArgumentList @( $null, 'CONTOSO\SQL-Admin')) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state - PRESENT' { + Context 'When the system is in the desired state' { + It 'Should return the test as true when desired loginName exist' { + + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable + $testParameters = $defaultParameters $testParameters += @{ Name = 'CONTOSO\SQL-Admin' Ensure = 'Present' } - It 'Should call the mock function Connect-SQL and Add-SqlServerRoleMember' { - - Mock -CommandName Add-SqlServerRoleMember -MockWith { } -ModuleName $script:DSCResourceName -Verifiable - - Set-TargetResource @testParameters - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + $result = Test-TargetResource @testParameters + $result | Should Be $true - It 'Should return the state as present when desired loginName in server role successfully added' { - - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters - $result.Ensure | Should Be 'Present' - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } + + It 'Should return the test as true when non-desired loginName does not exist' { + + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable - Context 'When the system is not in the desired state - ABSENT' { $testParameters = $defaultParameters $testParameters += @{ - Name = 'NonDesiredLogin' + Name = 'CONTOSO\SQL-Admin' Ensure = 'Absent' } - It 'Should call the mock function Connect-SQL and Remove-SqlServerRoleMember' { + $result = Test-TargetResource @testParameters + $result | Should Be $true - Mock -CommandName Remove-SqlServerRoleMember -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } - Set-TargetResource @testParameters - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Remove-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-VerifiableMocks + } - It 'Should return the state as absent when desired loginName in server role successfully dropped' { - - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Roles { + return @{ + 'dbcreator' = @( ( New-Object Microsoft.SqlServer.Management.Smo.ServerRole -ArgumentList @( $null, 'CONTOSO\SQL-Admin')) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state - PRESENT' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'CONTOSO\SQL-Admin' + Ensure = 'Present' + } - $result = Get-TargetResource @testParameters - $result.Ensure | Should Be 'absent' - } + It 'Should call the mock function Connect-SQL and Add-SqlServerRoleMember' { + + Mock -CommandName Add-SqlServerRoleMember -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should return the state as present when desired loginName in server role successfully added' { + + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' } } - } - finally - { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Context 'When the system is not in the desired state - ABSENT' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'NonDesiredLogin' + Ensure = 'Absent' + } + + It 'Should call the mock function Connect-SQL and Remove-SqlServerRoleMember' { - #endregion + Mock -CommandName Remove-SqlServerRoleMember -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Remove-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should return the state as absent when desired loginName in server role successfully dropped' { + + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'absent' + } + } } } +finally +{ + #region FOOTER + + Restore-TestEnvironment -TestEnvironment $TestEnvironment -$testJob | Receive-Job -Wait + #endregion +} diff --git a/appveyor.yml b/appveyor.yml index ceed152e3..d2312e9c6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,6 +21,12 @@ build: false test_script: - ps: | + Write-Host 'Removing all SQL related PowerShell modules so they can not be used by the common tests.' + Write-Host 'This is a workaround because the AppVeyor build worker image contains SQL Server. More information in issue #239.' + Write-Host 'Modules that are being removed:' + Get-Module -ListAvailable -Name 'sql*' | ForEach-Object -Process { Write-Host $_.Path; Remove-Item $_.Path -Force; } + + # Start the $testResultsFile = ".\TestsResults.xml" $res = Invoke-Pester -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile)) From c246d4e40074b3ff20c35123e3063dc689e2e4ba Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 28 Dec 2016 09:50:34 +0100 Subject: [PATCH 02/31] xSQLServerSetup: Removed dependency of Win32_Product WMI class * Changes to xSQLServerSetup The resource no longer uses Win32_Product WMI class when evaluating if SQL Server Management Studio is installed. --- CHANGELOG.md | 2 + .../MSFT_xSQLServerSetup.psm1 | 80 +-- Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 | 634 +++++++++++++----- 3 files changed, 513 insertions(+), 203 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 188538dc1..21f02e473 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - Improvements how tests are initiated in AppVeyor - Removed previous workaround (issue #201) from unit tests. - Changes in appveyor.yml so that SQL modules are removed before common test is run. +- Changes to xSQLServerSetup + - The resource no longer uses Win32_Product WMI class when evaluating if SQL Server Management Studio is installed. See article [kb974524](https://support.microsoft.com/en-us/kb/974524) for more information. ## 4.0.0.0 diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 index 4c0a8148c..34b4f3a39 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 @@ -181,50 +181,52 @@ function Get-TargetResource $integrationServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $integrationServiceName}).StartName } - $products = Get-WmiObject -Class Win32_Product + $registryUninstallPath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall' - switch ($sqlVersion) - { - '10' - { - $identifyingNumber = '{72AB7E6F-BC24-481E-8C45-1AB5B3DD795D}' - } - - '11' - { - $identifyingNumber = '{A7037EB2-F953-4B12-B843-195F4D988DA1}' - } - - '12' - { - $identifyingNumber = '{75A54138-3B98-4705-92E4-F619825B121F}' - } - } - - if ($products | Where-Object {$_.IdentifyingNumber -eq $identifyingNumber}) + # Verify if SQL Server Management Studio 2008 or SQL Server Management Studio 2008 R2 (major version 10) is installed + $installedProductSqlServerManagementStudio2008R2 = Get-ItemProperty -Path ( + Join-Path -Path $registryUninstallPath -ChildPath '{72AB7E6F-BC24-481E-8C45-1AB5B3DD795D}' + ) -ErrorAction SilentlyContinue + + # Verify if SQL Server Management Studio 2012 (major version 11) is installed + $installedProductSqlServerManagementStudio2012 = Get-ItemProperty -Path ( + Join-Path -Path $registryUninstallPath -ChildPath '{A7037EB2-F953-4B12-B843-195F4D988DA1}' + ) -ErrorAction SilentlyContinue + + # Verify if SQL Server Management Studio 2014 (major version 12) is installed + $installedProductSqlServerManagementStudio2014 = Get-ItemProperty -Path ( + Join-Path -Path $registryUninstallPath -ChildPath '{75A54138-3B98-4705-92E4-F619825B121F}' + ) -ErrorAction SilentlyContinue + + if ( + ($sqlVersion -eq 10 -and $installedProductSqlServerManagementStudio2008R2) -or + ($sqlVersion -eq 11 -and $installedProductSqlServerManagementStudio2012) -or + ($sqlVersion -eq 12 -and $installedProductSqlServerManagementStudio2014) + ) { $features += 'SSMS,' } - switch ($sqlVersion) - { - '10' - { - $identifyingNumber = '{B5FE23CC-0151-4595-84C3-F1DE6F44FE9B}' - } - - '11' - { - $identifyingNumber = '{7842C220-6E9A-4D5A-AE70-0E138271F883}' - } - - '12' - { - $identifyingNumber = '{B5ECFA5C-AC4F-45A4-A12E-A76ABDD9CCBA}' - } - } - - if ($Products | Where-Object {$_.IdentifyingNumber -eq $identifyingNumber}) + # Evaluating if SQL Server Management Studio Advanced 2008 or SQL Server Management Studio Advanced 2008 R2 (major version 10) is installed + $installedProductSqlServerManagementStudioAdvanced2008R2 = Get-ItemProperty -Path ( + Join-Path -Path $registryUninstallPath -ChildPath '{B5FE23CC-0151-4595-84C3-F1DE6F44FE9B}' + ) -ErrorAction SilentlyContinue + + # Evaluating if SQL Server Management Studio Advanced 2012 (major version 11) is installed + $installedProductSqlServerManagementStudioAdvanced2012 = Get-ItemProperty -Path ( + Join-Path -Path $registryUninstallPath -ChildPath '{7842C220-6E9A-4D5A-AE70-0E138271F883}' + ) -ErrorAction SilentlyContinue + + # Evaluating if SQL Server Management Studio Advanced 2014 (major version 12) is installed + $installedProductSqlServerManagementStudioAdvanced2014 = Get-ItemProperty -Path ( + Join-Path -Path $registryUninstallPath -ChildPath '{B5ECFA5C-AC4F-45A4-A12E-A76ABDD9CCBA}' + ) -ErrorAction SilentlyContinue + + if ( + ($sqlVersion -eq 10 -and $installedProductSqlServerManagementStudioAdvanced2008R2) -or + ($sqlVersion -eq 11 -and $installedProductSqlServerManagementStudioAdvanced2012) -or + ($sqlVersion -eq 12 -and $installedProductSqlServerManagementStudioAdvanced2014) + ) { $features += 'ADV_SSMS,' } diff --git a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 index 6ba30fc31..3406b36ff 100644 --- a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 @@ -102,38 +102,44 @@ try return @() } - $mockGetWmiObject_SqlProduct = { + $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber = '{72AB7E6F-BC24-481E-8C45-1AB5B3DD795D}' + $mockSqlServerManagementStudio2012_ProductIdentifyingNumber = '{A7037EB2-F953-4B12-B843-195F4D988DA1}' + $mockSqlServerManagementStudio2014_ProductIdentifyingNumber = '{75A54138-3B98-4705-92E4-F619825B121F}' + $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber = '{B5FE23CC-0151-4595-84C3-F1DE6F44FE9B}' + $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber = '{7842C220-6E9A-4D5A-AE70-0E138271F883}' + $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber = '{B5ECFA5C-AC4F-45A4-A12E-A76ABDD9CCBA}' + + $mockRegistryUninstallProductsPath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall' + + $mockGetItemProperty_UninstallProducts2008R2 = { return @( - ( - # Mock product SSMS 2008 and SSMS 2008 R2 - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'IdentifyingNumber' -Value '{72AB7E6F-BC24-481E-8C45-1AB5B3DD795D}' -PassThru -Force - ), - ( - # Mock product ADV_SSMS 2008 and SSMS 2008 R2 - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'IdentifyingNumber' -Value '{B5FE23CC-0151-4595-84C3-F1DE6F44FE9B}' -PassThru -Force - ), - ( - # Mock product SSMS 2012 - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'IdentifyingNumber' -Value '{A7037EB2-F953-4B12-B843-195F4D988DA1}' -PassThru -Force - ), - ( - # Mock product ADV_SSMS 2012 - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'IdentifyingNumber' -Value '{7842C220-6E9A-4D5A-AE70-0E138271F883}' -PassThru -Force - ), - ( - # Mock product SSMS 2014 - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'IdentifyingNumber' -Value '{75A54138-3B98-4705-92E4-F619825B121F}' -PassThru -Force - ), - ( - # Mock product ADV_SSMS 2014 - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'IdentifyingNumber' -Value '{B5ECFA5C-AC4F-45A4-A12E-A76ABDD9CCBA}' -PassThru -Force - ) + $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber, # Mock product SSMS 2008 and SSMS 2008 R2 + $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber # Mock product ADV_SSMS 2012 + ) + } + + $mockGetItemProperty_UninstallProducts2012 = { + return @( + $mockSqlServerManagementStudio2012_ProductIdentifyingNumber, # Mock product ADV_SSMS 2008 and ADV_SSMS 2008 R2 + $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber # Mock product SSMS 2014 + ) + } + + $mockGetItemProperty_UninstallProducts2014 = { + return @( + $mockSqlServerManagementStudio2014_ProductIdentifyingNumber, # Mock product SSMS 2012 + $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber # Mock product ADV_SSMS 2014 + ) + } + + $mockGetItemProperty_UninstallProducts = { + return @( + $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber, # Mock product SSMS 2008 and SSMS 2008 R2 + $mockSqlServerManagementStudio2012_ProductIdentifyingNumber, # Mock product ADV_SSMS 2008 and ADV_SSMS 2008 R2 + $mockSqlServerManagementStudio2014_ProductIdentifyingNumber, # Mock product SSMS 2012 + $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber, # Mock product ADV_SSMS 2012 + $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber, # Mock product SSMS 2014 + $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber # Mock product ADV_SSMS 2014 ) } @@ -443,22 +449,36 @@ try SourceCredential = $null SourcePath = $mockSourcePath } - + if ($mockSqlMajorVersion -eq 13) { # Mock all SSMS products here to make sure we don't return any when testing SQL Server 2016 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -Verifiable + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable } else { - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockEmptyHashtable -Verifiable } + Mock -CommandName Get-WmiObject -ParameterFilter { + $Class -eq 'Win32_Service' + } -MockWith $mockGetService_DefaultInstance -Verifiable + Mock -CommandName NetUse -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -474,13 +494,25 @@ try 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 - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 6 -Scope It Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It } It 'Should not return any names of installed features' { @@ -531,19 +563,29 @@ try if ($mockSqlMajorVersion -eq 13) { # Mock all SSMS products here to make sure we don't return any when testing SQL Server 2016 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -Verifiable + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable } else { - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockEmptyHashtable -Verifiable } Mock -CommandName NetUse -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -559,13 +601,25 @@ try 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 - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 6 -Scope It Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It } It 'Should not return any names of installed features' { @@ -614,9 +668,26 @@ try SourcePath = $mockSourcePath } - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -Verifiable + if ($mockSqlMajorVersion -eq 10) { + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts2008R2 -Verifiable + } + + if ($mockSqlMajorVersion -eq 11) { + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts2012 -Verifiable + } + + if ($mockSqlMajorVersion -eq 12) { + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable + } Mock -CommandName NetUse -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable @@ -625,7 +696,7 @@ try } -MockWith $mockGetService_DefaultInstance -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -641,13 +712,36 @@ try Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 6 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -Exactly -Times 1 -Scope It + + if ($mockSqlMajorVersion -eq 13) { + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 0 -Scope It + } else { + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 2 -Scope It + } Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 6 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It } It 'Should return correct names of installed features' { @@ -700,9 +794,26 @@ try SourcePath = $mockSourcePathUNC } - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -Verifiable + if ($mockSqlMajorVersion -eq 10) { + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts2008R2 -Verifiable + } + + if ($mockSqlMajorVersion -eq 11) { + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts2012 -Verifiable + } + + if ($mockSqlMajorVersion -eq 12) { + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable + } Mock -CommandName NetUse -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable @@ -711,7 +822,7 @@ try } -MockWith $mockGetService_DefaultInstance -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -727,13 +838,36 @@ try Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 6 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -Exactly -Times 1 -Scope It + + if ($mockSqlMajorVersion -eq 13) { + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 0 -Scope It + } else { + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 2 -Scope It + } Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 6 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It } It 'Should return correct names of installed features' { @@ -795,22 +929,31 @@ try SourcePath = $mockSourcePath } - # Mock this here to make sure we don't return any older components (<=2014) when testing SQL Server 2016 if ($mockSqlMajorVersion -eq 13) { # Mock this here to make sure we don't return any older components (<=2014) when testing SQL Server 2016 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -Verifiable + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable } else { - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockEmptyHashtable -Verifiable } Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -825,13 +968,25 @@ try 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 - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 6 -Scope It Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It } It 'Should not return any names of installed features' { @@ -880,10 +1035,26 @@ try SourcePath = $mockSourcePath } - # Mock this here to make sure we don't return any older components (<=2014) when testing SQL Server 2016 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -Verifiable + if ($mockSqlMajorVersion -eq 10) { + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts2008R2 -Verifiable + } + + if ($mockSqlMajorVersion -eq 11) { + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts2012 -Verifiable + } + + if ($mockSqlMajorVersion -eq 12) { + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable + } Mock -CommandName Get-Service -MockWith $mockGetService_NamedInstance -Verifiable @@ -892,7 +1063,7 @@ try } -MockWith $mockGetService_NamedInstance -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -907,13 +1078,36 @@ try Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 6 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -Exactly -Times 1 -Scope It + + if ($mockSqlMajorVersion -eq 13) { + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 0 -Scope It + } else { + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 2 -Scope It + } Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 6 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It } It 'Should return correct names of installed features' { @@ -1019,7 +1213,7 @@ try } -MockWith $mockGetItem_SharedWowDirectory -Verifiable } - # For this thest we only need to test one SQL Server version + # For this test we only need to test one SQL Server version $mockSqlMajorVersion = 13 $mockDefaultInstance_InstanceId = "$($mockSqlDatabaseEngineName)$($mockSqlMajorVersion).$($mockDefaultInstance_InstanceName)" @@ -1041,12 +1235,17 @@ try } # Mock all SSMS products here to make sure we don't return any when testing SQL Server 2016 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -Verifiable - + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable + Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -1067,13 +1266,25 @@ try 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 - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 6 -Scope It Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It } It 'Should return that the desired state is asbent when SSMS product is missing' { @@ -1092,13 +1303,25 @@ try Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 6 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 6 -Scope It Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 6 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It } It 'Should return that the desired state is asbent when ADV_SSMS product is missing' { @@ -1117,13 +1340,25 @@ try Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 6 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 6 -Scope It Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 6 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It } } @@ -1136,9 +1371,20 @@ try SourcePath = $mockSourcePath } - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -Verifiable + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts2008R2 -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts2012 -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable @@ -1147,7 +1393,7 @@ try } -MockWith $mockGetService_DefaultInstance -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -1162,13 +1408,25 @@ try Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 6 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -Exactly -Times 6 -Scope It Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 6 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It } } @@ -1266,21 +1524,18 @@ try Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockEmptyHashtable -Verifiable - + Mock -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should set the system in the desired state when feature is SQLENGINE' { @@ -1310,8 +1565,14 @@ try Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $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 @@ -1356,8 +1617,14 @@ try Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $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 @@ -1387,8 +1654,15 @@ try Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $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 @@ -1414,18 +1688,15 @@ try Mock -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft copSQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - + Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -MockWith $mockEmptyHashtable -Verifiable } It 'Should set the system in the desired state when feature is SQLENGINE' { @@ -1455,8 +1726,15 @@ try Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $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 @@ -1501,8 +1779,15 @@ try Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $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 @@ -1531,8 +1816,14 @@ try Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $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 @@ -1559,8 +1850,13 @@ try SourcePath = $mockSourcePath } - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable @@ -1568,14 +1864,6 @@ try Mock -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should set the system in the desired state when feature is SQLENGINE' { @@ -1602,8 +1890,14 @@ try Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $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 @@ -1648,8 +1942,14 @@ try Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $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 @@ -1678,8 +1978,14 @@ try Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $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 From 950e0af9fe17903d2c8b4c39c3295b18ae4586f0 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 31 Dec 2016 15:42:21 +0100 Subject: [PATCH 03/31] xSQLServerSetup: Now using CIM cmdlet. Also resolves PSSA warnings. - Changes to xSQLServerSetup - The function Get-TemporaryFolder now has the correct OutputType - All of the $null is now on the left side of equality comparisons. - Suppressed the PSSA rule PSAvoidGlobalVars. - Now it uses CIM cmdlets to get information from WMI classes. - Updated the README.md. --- CHANGELOG.md | 2 + .../MSFT_xSQLServerSetup.psm1 | 17 +- Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 | 626 +++++++++++++++--- 3 files changed, 555 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21f02e473..819e21654 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - Changes in appveyor.yml so that SQL modules are removed before common test is run. - Changes to xSQLServerSetup - The resource no longer uses Win32_Product WMI class when evaluating if SQL Server Management Studio is installed. See article [kb974524](https://support.microsoft.com/en-us/kb/974524) for more information. + - Now it uses CIM cmdlets to get information from WMI classes. + - Resolved all of the PSScriptAnalyzer warnings that was triggered in the common tests. ## 4.0.0.0 diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 index 34b4f3a39..b6a913b65 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 @@ -93,8 +93,8 @@ function Get-TargetResource { $features += 'SQLENGINE,' - $sqlServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $databaseServiceName}).StartName - $agentServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $agentServiceName}).StartName + $sqlServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$databaseServiceName'").StartName + $agentServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$agentServiceName'").StartName $fullInstanceId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -Name $InstanceName).$InstanceName @@ -148,19 +148,19 @@ function Get-TargetResource if ($services | Where-Object {$_.Name -eq $fullTextServiceName}) { $features += 'FULLTEXT,' - $fulltextServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $fullTextServiceName}).StartName + $fulltextServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$fullTextServiceName'").StartName } if ($services | Where-Object {$_.Name -eq $reportServiceName}) { $features += 'RS,' - $reportingServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $reportServiceName}).StartName + $reportingServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$reportServiceName'").StartName } if ($services | Where-Object {$_.Name -eq $analysisServiceName}) { $features += 'AS,' - $analysisServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $analysisServiceName}).StartName + $analysisServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$analysisServiceName'").StartName $analysisServer = Connect-SQLAnalysis -SQLServer localhost -SQLInstanceName $InstanceName @@ -178,7 +178,7 @@ function Get-TargetResource if ($services | Where-Object {$_.Name -eq $integrationServiceName}) { $features += 'IS,' - $integrationServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $integrationServiceName}).StartName + $integrationServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$integrationServiceName'").StartName } $registryUninstallPath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall' @@ -437,6 +437,8 @@ function Get-TargetResource #> function Set-TargetResource { + # Suppressing this rule because $global:DSCMachineStatus is used to trigger a reboot, either by force or when there are pending changes. + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')] [CmdletBinding()] param ( @@ -711,7 +713,7 @@ function Set-TargetResource 'InstanceDir' ) - if ($BrowserSvcStartupType -ne $null) + if ($null -ne $BrowserSvcStartupType) { $argumentVars += 'BrowserSvcStartupType' } @@ -1290,6 +1292,7 @@ function Copy-ItemWithRoboCopy function Get-TemporaryFolder { [CmdletBinding()] + [OutputType([System.String])] param() return [IO.Path]::GetTempPath() diff --git a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 index 3406b36ff..980db1f51 100644 --- a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 @@ -143,6 +143,66 @@ try ) } + $mockGetCimInstance_DefaultInstance_DatabaseService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_DatabaseServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_DefaultInstance_AgentService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AgentServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockAgentServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_DefaultInstance_FullTextService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_FullTextServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_DefaultInstance_ReportingService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_ReportingServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_DefaultInstance_IntegrationService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion) -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_DefaultInstance_AnalysisService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AnalysisServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + $mockGetService_DefaultInstance = { return @( ( @@ -178,6 +238,66 @@ try ) } + $mockGetCimInstance_NamedInstance_DatabaseService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_DatabaseServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_NamedInstance_AgentService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AgentServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockAgentServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_NamedInstance_FullTextService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_FullTextServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_NamedInstance_ReportingService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_ReportingServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_NamedInstance_IntegrationService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockNamedInstance_IntegrationServiceName -f $mockSqlMajorVersion) -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_NamedInstance_AnalysisService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AnalysisServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + $mockGetService_NamedInstance = { return @( ( @@ -471,12 +591,9 @@ try } -MockWith $mockEmptyHashtable -Verifiable } - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockGetService_DefaultInstance -Verifiable - Mock -CommandName NetUse -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable @@ -511,8 +628,7 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It } It 'Should not return any names of installed features' { @@ -618,8 +734,7 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It } It 'Should not return any names of installed features' { @@ -691,10 +806,44 @@ try Mock -CommandName NetUse -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockGetService_DefaultInstance -Verifiable + #region Mock Get-CimInstance + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable + + # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error + Mock -CommandName Get-CimInstance -MockWith { + throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" + } + #endregion Mock Get-CimInstance + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable @@ -740,8 +889,37 @@ try } -Exactly -Times 2 -Scope It } - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 6 -Scope It + #region Assert Get-CimInstance + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" + } -Exactly -Times 1 -Scope It + #endregion Assert Get-CimInstance } It 'Should return correct names of installed features' { @@ -817,9 +995,43 @@ try Mock -CommandName NetUse -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockGetService_DefaultInstance -Verifiable + + #region Mock Get-CimInstance + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable + + # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error + Mock -CommandName Get-CimInstance -MockWith { + throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" + } + #endregion Mock Get-CimInstance Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" @@ -866,8 +1078,37 @@ try } -Exactly -Times 2 -Scope It } - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 6 -Scope It + #region Assert Get-CimInstance + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" + } -Exactly -Times 1 -Scope It + #endregion Assert Get-CimInstance } It 'Should return correct names of installed features' { @@ -951,7 +1192,7 @@ try } Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable @@ -985,8 +1226,7 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It } It 'Should not return any names of installed features' { @@ -1058,9 +1298,42 @@ try Mock -CommandName Get-Service -MockWith $mockGetService_NamedInstance -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockGetService_NamedInstance -Verifiable + #region Mock Get-CimInstance + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_DatabaseServiceName'" + } -MockWith $mockGetCimInstance_NamedInstance_DatabaseService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_AgentServiceName'" + } -MockWith $mockGetCimInstance_NamedInstance_AgentService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_FullTextServiceName'" + } -MockWith $mockGetCimInstance_NamedInstance_FullTextService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_ReportingServiceName'" + } -MockWith $mockGetCimInstance_NamedInstance_ReportingService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockNamedInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -MockWith $mockGetCimInstance_NamedInstance_IntegrationService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_AnalysisServiceName'" + } -MockWith $mockGetCimInstance_NamedInstance_AnalysisService -Verifiable + + # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error + Mock -CommandName Get-CimInstance -MockWith { + throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" + } + #endregion Mock Get-CimInstance Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" @@ -1106,8 +1379,37 @@ try } -Exactly -Times 2 -Scope It } - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 6 -Scope It + #region Assert Get-CimInstance + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_DatabaseServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_AgentServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_FullTextServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_ReportingServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockNamedInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_AnalysisServiceName'" + } -Exactly -Times 1 -Scope It + #endregion Assert Get-CimInstance } It 'Should return correct names of installed features' { @@ -1256,9 +1558,7 @@ try It 'Should return that the desired state is absent when no products are installed' { Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable $result = Test-TargetResource @testParameters $result| Should Be $false @@ -1283,16 +1583,48 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It } It 'Should return that the desired state is asbent when SSMS product is missing' { Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockGetService_DefaultInstance -Verifiable + #region Mock Get-CimInstance + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable + + # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error + Mock -CommandName Get-CimInstance -MockWith { + throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" + } + #endregion Mock Get-CimInstance # Change the default features for this test. $testParameters.Features = 'SSMS' @@ -1320,16 +1652,78 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 6 -Scope It + #region Assert Get-CimInstance + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" + } -Exactly -Times 1 -Scope It + #endregion Assert Get-CimInstance } It 'Should return that the desired state is asbent when ADV_SSMS product is missing' { Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockGetService_DefaultInstance -Verifiable + #region Mock Get-CimInstance + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable + + # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error + Mock -CommandName Get-CimInstance -MockWith { + throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" + } + #endregion Mock Get-CimInstance # Change the default features for this test. $testParameters.Features = 'ADV_SSMS' @@ -1357,8 +1751,37 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 6 -Scope It + #region Assert Get-CimInstance + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" + } -Exactly -Times 1 -Scope It + #endregion Assert Get-CimInstance } } @@ -1388,9 +1811,42 @@ try Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockGetService_DefaultInstance -Verifiable + #region Mock Get-CimInstance + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" + } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable + + # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error + Mock -CommandName Get-CimInstance -MockWith { + throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" + } + #endregion Mock Get-CimInstance Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" @@ -1425,8 +1881,37 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 6 -Scope It + #region Assert Get-CimInstance + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" + } -Exactly -Times 1 -Scope It + #endregion Assert Get-CimInstance } } @@ -1533,9 +2018,7 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockEmptyHashtable -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable } It 'Should set the system in the desired state when feature is SQLENGINE' { @@ -1562,9 +2045,7 @@ try ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It - + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or @@ -1614,9 +2095,7 @@ try ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It - + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or @@ -1651,9 +2130,7 @@ try ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It - + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or @@ -1685,10 +2162,7 @@ try Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockEmptyHashtable -Verifiable - + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or @@ -1723,9 +2197,7 @@ try ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It - + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or @@ -1776,9 +2248,7 @@ try ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It - + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or @@ -1813,9 +2283,7 @@ try ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It - + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or @@ -1860,10 +2328,7 @@ try } -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable } It 'Should set the system in the desired state when feature is SQLENGINE' { @@ -1887,8 +2352,7 @@ try ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or @@ -1939,9 +2403,7 @@ try ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It - + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or @@ -1975,9 +2437,7 @@ try ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It - + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or From 1550523ded928006a061b087998c9f5a5acfbbcc Mon Sep 17 00:00:00 2001 From: David Reynolds Date: Sat, 31 Dec 2016 13:11:01 -0600 Subject: [PATCH 04/31] Changes to tests for MSFT_xSQLServerScript * Renamed MSFT_xSQLServerScript.Test to MSFT_xSQLServerScript.Tests * Updated MSFT_SQLServerScript.Tests.ps1 to match updated test template * Changed test to mock Import-Module in place of Import-SQLPSModule * Updated some capitalization * Added new line at EoF --- Tests/Unit/MSFT_xSQLServerScript.Test.ps1 | 212 ----------------- Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 | 225 ++++++++++++++++++ .../SqlPowerShellSqlExecutionException.cs | 9 + 3 files changed, 234 insertions(+), 212 deletions(-) delete mode 100644 Tests/Unit/MSFT_xSQLServerScript.Test.ps1 create mode 100644 Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 create mode 100644 Tests/Unit/Stubs/SqlPowerShellSqlExecutionException.cs diff --git a/Tests/Unit/MSFT_xSQLServerScript.Test.ps1 b/Tests/Unit/MSFT_xSQLServerScript.Test.ps1 deleted file mode 100644 index 66f132305..000000000 --- a/Tests/Unit/MSFT_xSQLServerScript.Test.ps1 +++ /dev/null @@ -1,212 +0,0 @@ -<# - .SYNOPSIS - Automated unit test for MSFT_xSQLServerScript DSC Resource -#> - - -$Script:DSCModuleName = 'MSFT_xSQLServerScript' -$Script:DSCResourceName = 'MSFT_xSQLServerScript' - -#region HEADER - -# Unit Test Template Version: 1.1.0 -[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) -if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) -{ - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) -} - -Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force -$TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit - -#endregion HEADER - -Add-Type -ErrorAction SilentlyContinue -TypeDefinition @' -namespace Microsoft.SqlServer.Management.PowerShell -{ - public class SqlPowerShellSqlExecutionException : System.Exception - { - public SqlPowerShellSqlExecutionException() - { - } - } -} -'@ - -# Begin Testing -try -{ - #region Pester Test Initialization - Function script:Invoke-SqlCmd {} - #endregion Pester Test Initialization - - #region Test Sql script throws an error - Describe 'Test Sql script throws an error' { - Mock -CommandName Import-Module -MockWith {} - - $testParameters = @{ - ServerInstance = $env:COMPUTERNAME - SetFilePath = "set.sql" - GetFilePath = "get.sql" - TestFilePath = "test.sql" - } - - It 'Get method returns successfully' { - Mock -CommandName Invoke-SqlCmd -MockWith { "" } - - $result = Get-TargetResource @testParameters - - $result.ServerInstance | should be $testParameters.ServerInstance - $result.SetFilePath | should be $testParameters.SetFilePath - $result.GetFilePath | should be $testParameters.GetFilePath - $result.TestFilePath | should be $testParameters.TestFilePath - $result.GetType() | should be "hashtable" - } - - It 'Test method returns false' { - Mock -CommandName Invoke-SqlCmd -MockWith { throw New-Object Microsoft.SqlServer.Management.PowerShell.SqlPowerShellSqlExecutionException} - - Test-TargetResource @testParameters | should be $false - } - - It 'Set method calls Invoke-SqlCmd' { - Mock -CommandName Invoke-SqlCmd -MockWith { "" } -Verifiable - - $result = Set-TargetResource @testParameters - - Assert-MockCalled -CommandName Invoke-SqlCmd -Times 1 - } - } - #endregion Test Sql script throws an error - - #region Test Sql script returns null - Describe 'Test Sql script returns null' { - Mock -CommandName Import-Module -MockWith {} - Mock -CommandName Invoke-SqlCmd -MockWith { $null } - - $testParameters = @{ - ServerInstance = $env:COMPUTERNAME - SetFilePath = "set.sql" - GetFilePath = "get.sql" - TestFilePath = "test.sql" - } - - It 'Get method returns successfully' { - $result = Get-TargetResource @testParameters - - $result.ServerInstance | should be $testParameters.ServerInstance - $result.SetFilePath | should be $testParameters.SetFilePath - $result.GetFilePath | should be $testParameters.GetFilePath - $result.TestFilePath | should be $testParameters.TestFilePath - $result.GetType() | should be "hashtable" - } - - It 'Test method returns true' { - Test-TargetResource @testParameters | should be $true - } - } - #endregion Test Sql script returns null - - #region Get SQl script throws and error - Describe 'Get SQl script throws and error' { - Mock -CommandName Import-Module -MockWith {} - - $testParameters = @{ - ServerInstance = $env:COMPUTERNAME - SetFilePath = "set.sql" - GetFilePath = "get.sql" - TestFilePath = "test.sql" - } - - It 'Get throws when get sql script throws' { - $throwMessage = "Failed to run SQL Script" - - Mock -CommandName Invoke-SqlCmd -MockWith { Throw $throwMessage } - - { Get-TargetResource @testParameters } | should throw $throwMessage - } - - It 'Test method returns true' { - Mock -CommandName Invoke-SqlCmd -MockWith { $null } - - Test-TargetResource @testParameters | should be $true - } - } - #endregion - - #region Set-TargetResource throws when Set Sql script throws - Describe 'Set-TargetResource throws when Set Sql script throws' { - Mock -CommandName Import-Module -MockWith {} - Mock -CommandName Invoke-SqlCmd -MockWith { $null } - - $testParameters = @{ - ServerInstance = $env:COMPUTERNAME - SetFilePath = "set.sql" - GetFilePath = "get.sql" - TestFilePath = "test.sql" - } - - It 'Get method returns successfully' { - $result = Get-TargetResource @testParameters - - $result.ServerInstance | should be $testParameters.ServerInstance - $result.SetFilePath | should be $testParameters.SetFilePath - $result.GetFilePath | should be $testParameters.GetFilePath - $result.TestFilePath | should be $testParameters.TestFilePath - $result.GetType() | should be "hashtable" - } - - It 'Test method returns true' { - Test-TargetResource @testParameters | should be $true - } - - It 'Set method throws' { - $throwMessage = "Failed to execute set Sql script" - - Mock -CommandName Invoke-SqlCmd -MockWith { throw $throwMessage } - - { Set-TargetResource @testParameters } | should throw $throwMessage - } - } - #endregion - - #region Failed to import SQLPS module - Describe 'Failed to import SQLPS module' { - $throwMessage = "Failed to import module SQLPS" - - Mock -CommandName Import-Module -MockWith { throw $throwMessage } - - $testParameters = @{ - ServerInstance = $env:COMPUTERNAME - SetFilePath = "set.sql" - GetFilePath = "get.sql" - TestFilePath = "test.sql" - } - - It 'Get method throws' { - { $result = Get-TargetResource @testParameters } | should throw $throwMessage - } - - It 'Test method throws' { - { Test-TargetResource @testParameters } | should throw $throwMessage - } - - It 'Set method throws' { - { Set-TargetResource @testParameters} | should throw $throwMessage - } - } - #endregion - -} -finally -{ - #region FOOTER - - Restore-TestEnvironment -TestEnvironment $TestEnvironment - - #endregion -} diff --git a/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 new file mode 100644 index 000000000..2d051156d --- /dev/null +++ b/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 @@ -0,0 +1,225 @@ +<# + .SYNOPSIS + Automated unit test for MSFT_xSQLServerScript DSC Resource +#> + +# Suppression of this PSSA rule allowed in tests. +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +Param() + +#region HEADER + +# Unit Test Template Version: 1.2.0 +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName 'xSQLServer' ` + -DSCResourceName 'MSFT_xSQLServerScript' ` + -TestType Unit + +#endregion HEADER + +function Invoke-TestSetup { + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SqlPowerShellSqlExecutionException.cs') + Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SQLPSStub.psm1') -Force +} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope 'MSFT_xSQLServerScript' { + + $testParameters = @{ + ServerInstance = $env:COMPUTERNAME + SetFilePath = "set.sql" + GetFilePath = "get.sql" + TestFilePath = "test.sql" + } + + $dscResourceName = 'MSFT_xSQLServerScript' + $sqlServerHelperModuleName = 'xSQLServerHelper' + + Describe "$dscResourceName\Get-TargetResource" { + + Context 'Get-TargetResource fails to import SQLPS module' { + $throwMessage = "Failed to import SQLPS module." + + Mock -CommandName Import-Module -MockWith { throw $throwMessage } -ModuleName $sqlServerHelperModuleName + + It 'Should throw an error' { + { Get-TargetResource @testParameters } | Should Throw $throwMessage + } + } + + Context 'Get-TargetResource returns script results successfully' { + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { "" } -ModuleName $dscResourceName + + $result = Get-TargetResource @testParameters + + It 'Should return the expected results' { + $result.ServerInstance | Should Be $testParameters.ServerInstance + $result.SetFilePath | Should Be $testParameters.SetFilePath + $result.GetFilePath | Should Be $testParameters.GetFilePath + $result.TestFilePath | Should Be $testParameters.TestFilePath + $result.GetType() | Should Be "hashtable" + } + } + + Context 'Get SQl script throws an error' { + $errorMessage = "Failed to run SQL Script" + + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { Throw $errorMessage } -ModuleName $script:DSCResourceName + + It 'Should throw an error' { + { Get-TargetResource @testParameters } | Should Throw $errorMessage + } + } + } + + Describe "$dscResourceName\Set-TargetResource" { + + Context 'Set-TargetResource fails to import SQLPS module' { + $throwMessage = "Failed to import SQLPS module." + + Mock -CommandName Import-Module -MockWith { throw $throwMessage } -ModuleName $sqlServerHelperModuleName + + It 'Should throw an error' { + { Set-TargetResource @testParameters } | Should Throw $throwMessage + } + } + + Context 'Set-TargetResource runs script without issue' { + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { "" } -ModuleName $dscResourceName + + $result = Set-TargetResource @testParameters + + It 'Should return the expected results' { + $result | Should Be "" + } + } + + Context 'Set SQL script trhows an error' { + $errorMessage = "Failed to run SQL Script" + + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { Throw $errorMessage } -ModuleName $script:DSCResourceName + + It 'Should throw an error' { + { Set-TargetResource @testParameters } | Should Throw $errorMessage + } + } + } + + Describe "$dscResourceName\Test-TargetResource" { + Context 'Test-TargetResource fails to import SQLPS module' { + $throwMessage = "Failed to import SQLPS module." + + Mock -CommandName Import-Module -MockWith { throw $throwMessage } -ModuleName $sqlServerHelperModuleName + + It 'Should throw an error' { + { Set-TargetResource @testParameters } | Should Throw $throwMessage + } + } + + Context 'Test-TargetResource runs script without issue' { + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith {} -ModuleName $dscResourceName + + $result = Test-TargetResource @testParameters + + It 'Should return true' { + $result | Should Be $true + } + } + + Context 'Test SQL script throws SQL execution error' { + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { throw New-Object Microsoft.SqlServer.Management.PowerShell.SqlPowerShellSqlExecutionException} -ModuleName $dscResourceName + + $result = Test-TargetResource @testParameters + + It 'Should return false' { + $result | Should Be $false + } + } + + Context 'Test SQL script throws a different error' { + $errorMessage = "Failed to run SQL Script" + + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { Throw $errorMessage } -ModuleName $script:DSCResourceName + + It 'Should throw an error' { + { Test-TargetResource @testParameters } | Should Throw $errorMessage + } + } + } + + Describe "$dscResourceName\Invoke-SqlScript" { + $invokeScriptParameters = @{ + ServerInstance = $env:COMPUTERNAME + SqlScriptPath = "set.sql" + } + + Context 'Invoke-SqlScript fails to import SQLPS module' { + $throwMessage = "Failed to import SQLPS module." + + Mock -CommandName Import-Module -MockWith { throw $throwMessage } -ModuleName $sqlServerHelperModuleName + + It 'Should throw an error' { + { Invoke-SqlScript @invokeScriptParameters } | Should Throw $throwMessage + } + } + + Context 'Invoke-SQlScript is called with credentials' { + $passwordPlain = "password" + $user = "User" + + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith {} -ParameterFilter { ($Username -eq $user) -and ($Password -eq $passwordPlain) } -ModuleName $dscResourceName + + $password = ConvertTo-SecureString -String $passwordPlain -AsPlainText -Force + $cred = New-Object pscredential -ArgumentList $user, $password + + $invokeScriptParameters.Add("Credential", $cred) + + $null = Invoke-SqlScript @invokeScriptParameters + + It 'Should call Invoke-Sqlcmd with correct parameters' { + Assert-MockCalled -CommandName Invoke-Sqlcmd -Times 1 -Exactly -ParameterFilter { ($Username -eq $user) -and ($Password -eq $passwordPlain) } + } + } + + Context 'Invoke-SqlScript fails to execute the SQL scripts' { + $errorMessage = "Failed to run SQL Script" + + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { Throw $errorMessage } -ModuleName $script:DSCResourceName + + It 'Should throw an error' { + { Invoke-SqlScript @invokeScriptParameters } | Should Throw $errorMessage + } + } + } + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/Tests/Unit/Stubs/SqlPowerShellSqlExecutionException.cs b/Tests/Unit/Stubs/SqlPowerShellSqlExecutionException.cs new file mode 100644 index 000000000..27cbaf0cb --- /dev/null +++ b/Tests/Unit/Stubs/SqlPowerShellSqlExecutionException.cs @@ -0,0 +1,9 @@ +namespace Microsoft.SqlServer.Management.PowerShell +{ + public class SqlPowerShellSqlExecutionException : System.Exception + { + public SqlPowerShellSqlExecutionException() + { + } + } +} From a0cff8ae45e699f54c04a507866a5356b3d7df6b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 3 Jan 2017 12:37:20 +0100 Subject: [PATCH 05/31] README.md: Changes to contributing and resource list section (#281) Changes to README.md Changed the contributing section to help new contributors. Added links for each resource so it is easier to navigate to the parameter list for each resource. Moved the list of resources in alphabetical order. Moved each resource parameter list into alphabetical order. Removed old text mentioning System Center. --- CHANGELOG.md | 6 + README.md | 485 ++++++++++++++++++++++++++------------------------- 2 files changed, 256 insertions(+), 235 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 819e21654..5a8e6ef90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ - Improvements how tests are initiated in AppVeyor - Removed previous workaround (issue #201) from unit tests. - Changes in appveyor.yml so that SQL modules are removed before common test is run. +- Changes to README.md + - Changed the contributing section to help new contributors. + - Added links for each resource so it is easier to navigate to the parameter list for each resource. + - Moved the list of resources in alphabetical order. + - Moved each resource parameter list into alphabetical order. + - Removed old text mentioning System Center. - Changes to xSQLServerSetup - The resource no longer uses Win32_Product WMI class when evaluating if SQL Server Management Studio is installed. See article [kb974524](https://support.microsoft.com/en-us/kb/974524) for more information. - Now it uses CIM cmdlets to get information from WMI classes. diff --git a/README.md b/README.md index fdf8cee54..c6cea7031 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,30 @@ [![Build status](https://ci.appveyor.com/api/projects/status/mxn453y284eab8li/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/xsqlserver/branch/master) -The **xSQLServer** module contains DSC resources for deployment and configuration of SQL Server in a way that is fully compliant with the requirements of System Center. +The **xSQLServer** module contains DSC resources for deployment and configuration of SQL Server. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## Contributing -Please check out common DSC Resources [contributing guidelines](https://github.com/PowerShell/DscResource.Kit/blob/master/CONTRIBUTING.md). -Also please check out the [specific guidelines](https://github.com/PowerShell/xSQLServer/blob/dev/CONTRIBUTING.md) for contributing to xSQLServer. +Regardless of the way you want to contribute we are tremendously happy to have you here. + +There are several ways you can contribute. You can submit an issue to report a bug. You can submit an issue to request an improvment. You can take part in discussions for issues. You can review pull requests and comment on other contributors changes. +You can also improve the resources and tests, or even create new resources, by sending in pull requests yourself. + +* If you want to submit an issue or take part in discussions, please browse the list of [issues](https://github.com/PowerShell/xSQLServer/issues). Please check out [Contributing to the DSC Resource Kit](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md) on how to work with issues. +* If you want to review pull requests, please first check out the [Review Pull Request guidelines](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md#reviewing-pull-requests), and the browse the list of [pull requests](https://github.com/PowerShell/xSQLServer/pulls) and look for those pull requests with label 'needs review'. +* If you want to improve the resources or tests, or create a new resource, then please check out the follwing guidelines. + * The [Contributing to the DSC Resource Kit](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md) guidelines. + * The specific [Contributing to xSQLServer](https://github.com/PowerShell/xSQLServer/blob/dev/CONTRIBUTING.md) guidelines. + * The common [Style Guidelines](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md). + * The common [Best Practices](https://github.com/PowerShell/DscResources/blob/master/BestPractices.md) guidelines. + * The common [Testing Guidelines](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md). + * If you are new to GitHub (and git), then please check out [Getting Started with GitHub](https://github.com/PowerShell/DscResources/blob/master/GettingStartedWithGitHub.md). + * If you are new to Pester and writing test, then please check out [Getting started with Pester](https://github.com/PowerShell/DscResources/blob/master/GettingStartedWithPester.md). + +If you need any help along the way, don't be afraid to ask. We are here for each other. ## Installation @@ -43,103 +58,159 @@ A full list of changes in each version can be found in the [change log](CHANGELO ## Resources -* **xSQLServerSetup** installs a standalone SQL Server instance -* **xSQLServerFirewall** configures firewall settings to allow remote access to a SQL Server instance. -* **xSQLServerRSSecureConnectionLevel** sets the secure connection level for SQL Server Reporting Services. -* **xSQLServerFailoverClusterSetup** installs SQL Server failover cluster instances. -* **xSQLServerRSConfig** configures SQL Server Reporting Services to use a database engine in another instance. -* **xSQLServerLogin** resource to manage SQL logins -* **xSQLServerRole** resource to manage SQL server roles -* **xSQLServerDatabaseRole** resource to manage SQL database roles -* **xSQLServerDatabasePermissions** resource to manage SQL database permissions -* **xSQLServerDatabaseOwner** resource to manage SQL database owners -* **xSQLDatabaseRecoveryModel** resource to manage database recovery model -* **xSQLServerMaxDop** resource to manage MaxDegree of Parallelism for SQL Server -* **xSQLServerMemory** resource to manage Memory for SQL Server -* **xSQLServerNetwork** resource to manage SQL Server Network Protocols -* **xSQLServerDatabase** resource to manage ensure database is present or absent -* **xSQLAOGroupEnsure** resource to ensure availability group is present or absent -* **xSQLAOGroupJoin** resource to join a replica to an existing availability group -* **xSQLServerAlwaysOnService** resource to enable always on on a SQL Server -* **xSQLServerEndpoint** resource to ensure database endpoint is present or absent -* **xWaitForAvailabilityGroup** resource to wait till availability group is created on primary server -* **xSQLServerConfiguration** resource to manage [SQL Server Configuration Options](https://msdn.microsoft.com/en-us/library/ms189631.aspx) -* **xSQLServerPermission** Grant or revoke permission on the SQL Server. -* **xSQLServerEndpointState** Change state of the endpoint. -* **xSQLServerEndpointPermission** Grant or revoke permission on the endpoint. -* **xSQLServerAvailabilityGroupListener** Create or remove an availability group listener. -* **xSQLServerReplication** resource to manage SQL Replication distribution and publishing. -* **xSQLServerScript** resource to extend DSCs Get/Set/Test functionality to T-SQL -* **xSQLServerAlias** resource to manage SQL Server client Aliases +* [**xSQLAOGroupEnsure**](#xsqlaogroupensure) resource to ensure availability group is present or absent +* [**xSQLAOGroupJoin**](#xsqlaogroupjoin) resource to join a replica to an existing availability group +* [**xSQLDatabaseRecoveryModel**](#xsqldatabaserecoverymodel) resource to manage database recovery model +* [**xSQLServerAlias**](#xsqlserveralias) resource to manage SQL Server client Aliases +* [**xSQLServerAlwaysOnService**](#xsqlserveralwaysonservice) resource to enable always on on a SQL Server +* [**xSQLServerAvailabilityGroupListener**](#xsqlserveravailabilitygrouplistener) Create or remove an availability group listener. +* [**xSQLServerConfiguration**](#xsqlserverconfiguration) resource to manage [SQL Server Configuration Options](https://msdn.microsoft.com/en-us/library/ms189631.aspx) +* [**xSQLServerDatabase**](#xsqlserverdatabase) resource to manage ensure database is present or absent +* [**xSQLServerDatabaseRole**](#xsqlserverdatabaserole) resource to manage SQL database roles +* [**xSQLServerDatabaseOwner**](#xsqlserverdatabaseowner) resource to manage SQL database owners +* [**xSQLServerDatabasePermissions**](#xsqlserverdatabasepermissions) resource to manage SQL database permissions +* [**xSQLServerEndpoint**](#xsqlserverendpoint) resource to ensure database endpoint is present or absent +* [**xSQLServerEndpointPermission**](#xsqlserverendpointpermission) Grant or revoke permission on the endpoint. +* [**xSQLServerEndpointState**](#xsqlserverendpointstate) Change state of the endpoint. +* [**xSQLServerFailoverClusterSetup**](#xsqlserverfailoverclustersetup) installs SQL Server failover cluster instances. +* [**xSQLServerFirewall**](#xsqlserverfirewall) configures firewall settings to allow remote access to a SQL Server instance. +* [**xSQLServerLogin**](#xsqlserverlogin) resource to manage SQL logins +* [**xSQLServerMaxDop**](#xsqlservermaxdop) resource to manage MaxDegree of Parallelism for SQL Server +* [**xSQLServerMemory**](#xsqlservermemory) resource to manage Memory for SQL Server +* [**xSQLServerNetwork**](#xsqlservernetwork) resource to manage SQL Server Network Protocols +* [**xSQLServerPermission**](#xsqlserverpermission) Grant or revoke permission on the SQL Server. +* [**xSQLServerRole**](#xsqlserverrole) resource to manage SQL server roles +* [**xSQLServerReplication**](#xsqlserverreplication) resource to manage SQL Replication distribution and publishing. +* [**xSQLServerRSConfig**](#xsqlserverrsconfig) configures SQL Server Reporting Services to use a database engine in another instance. +* [**xSQLServerRSSecureConnectionLevel**](#xsqlserverrssecureconnectionlevel) sets the secure connection level for SQL Server Reporting Services. +* [**xSQLServerScript**](#xsqlserverscript) resource to extend DSCs Get/Set/Test functionality to T-SQL +* [**xSQLServerSetup**](#xsqlserversetup) installs a standalone SQL Server instance +* [**xWaitForAvailabilityGroup**](#xwaitforavailabilitygroup) resource to wait till availability group is created on primary server -### xSQLServerSetup +### xSQLAOGroupEnsure -* **SourcePath**: (Required) The path to the root of the source files for installation. I.e and UNC path to a shared resource. -* **SourceFolder**: Folder within the source path containing the source files for installation. Default value is 'Source'. -* **SetupCredential**: (Required) Credential to be used to perform the installation. -* **SourceCredential**: Credential used to access SourcePath. -* **SuppressReboot**: Suppresses reboot. -* **ForceReboot**: Forces reboot. -* **Features**: (Key) SQL features to be installed. -* **InstanceName**: (Key) SQL instance to be installed. -* **InstanceID**: SQL instance ID, if different from InstanceName. -* **PID**: Product key for licensed installations. -* **UpdateEnabled**: Enabled updates during installation. -* **UpdateSource**: Path to the source of updates to be applied during installation. -* **SQMReporting**: Enable customer experience reporting. -* **ErrorReporting**: Enable error reporting. -* **InstallSharedDir**: Installation path for shared SQL files. -* **InstallSharedWOWDir**: Installation path for x86 shared SQL files. -* **InstanceDir**: Installation path for SQL instance files. -* **SQLSvcAccount**: Service account for the SQL service. -* **SQLSvcAccountUsername**: (Read) Output user name for the SQL service. -* **AgtSvcAccount**: Service account for the SQL Agent service. -* **AgtSvcAccountUsername**: (Read) Output user name for the SQL Agent service. -* **SQLCollation**: Collation for SQL. -* **SQLSysAdminAccounts**: Array of accounts to be made SQL administrators. -* **SecurityMode**: Security mode to apply to the SQL Server instance. -* **SAPwd**: SA password, if SecurityMode is set to 'SQL'. -* **InstallSQLDataDir**: Root path for SQL database files. -* **SQLUserDBDir**: Path for SQL database files. -* **SQLUserDBLogDir**: Path for SQL log files. -* **SQLTempDBDir**: Path for SQL TempDB files. -* **SQLTempDBLogDir**: Path for SQL TempDB log files. -* **SQLBackupDir**: Path for SQL backup files. -* **FTSvcAccount**: Service account for the Full Text service. -* **FTSvcAccountUsername**: (Read) Output username for the Full Text service. -* **RSSvcAccount**: Service account for Reporting Services service. -* **RSSvcAccountUsername**: (Read) Output username for the Reporting Services service. -* **ASSvcAccount**: Service account for Analysis Services service. -* **ASSvcAccountUsername**: (Read) Output username for the Analysis Services service. -* **ASCollation**: Collation for Analysis Services. -* **ASSysAdminAccounts**: Array of accounts to be made Analysis Services admins. -* **ASDataDir**: Path for Analysis Services data files. -* **ASLogDir**: Path for Analysis Services log files. -* **ASBackupDir**: Path for Analysis Services backup files. -* **ASTempDir**: Path for Analysis Services temp files. -* **ASConfigDir**: Path for Analysis Services config. -* **ISSvcAccount**: Service account for Integration Services service. -* **ISSvcAccountUsername**: (Read) Output user name for the Integration Services service. -* **BrowserSvcStartupType**: Specifies the startup mode for SQL Server Browser service. Valid values are 'Automatic', 'Disabled' or 'Manual'. +* **Ensure**: (Key) Determines whether the availability group should be added or removed. +* **AvailabilityGroupName** (Key) Name for availability group. +* **AvailabilityGroupNameListener** Listener name for availability group. +* **AvailabilityGroupNameIP** List of IP addresses associated with listener. +* **AvailabilityGroupSubMask** Network subnetmask for listener. +* **AvailabilityGroupPort** Port availability group should listen on. +* **ReadableSecondary** Mode secondaries should operate under (None, ReadOnly, ReadIntent). +* **AutoBackupPreference** Where backups should be backed up from (Primary, Secondary). +* **BackupPriority** The percentage weight for backup prority (default 50). +* **EndPointPort** The TCP port for the SQL AG Endpoint (default 5022). +* **SQLServer**: The SQL Server for the database. +* **SQLInstance**: The SQL instance for the database. +* **SetupCredential**: (Required) Credential to be used to Grant Permissions on SQL Server, set this to $null to use Windows Authentication. -### xSQLServerFirewall +### xSQLAOGroupJoin -* **Ensure**: (Key) Ensures that SQL firewall rules are **Present** or **Absent** on the machine. -* **SourcePath**: (Required) UNC path to the root of the source files for installation. -* **SourceFolder**: Folder within the source path containing the source files for installation. -* **Features**: (Key) SQL features to enable firewall rules for. -* **InstanceName**: (Key) SQL instance to enable firewall rules for. -* **DatabaseEngineFirewall**: Is the firewall rule for the Database Engine enabled? -* **BrowserFirewall**: Is the firewall rule for the Browser enabled? -* **ReportingServicesFirewall**: Is the firewall rule for Reporting Services enabled? -* **AnalysisServicesFirewall**: Is the firewall rule for Analysis Services enabled? -* **IntegrationServicesFirewall**: Is the firewall rule for the Integration Services enabled? +* **Ensure**: (key) If the replica should be joined ('Present') to the Availability Group or not joined ('Absent') to the Availability Group. +* **AvailabilityGroupName** (key) The name Availability Group to join. +* **SQLServer**: Name of the SQL server to be configured. +* **SQLInstanceName**: Name of the SQL instance to be configured. +* **SetupCredential**: (Required) Credential to be used to Grant Permissions in SQL. -### xSQLServerRSSecureConnectionLevel +### xSQLDatabaseRecoveryModel -* **InstanceName**: (Key) SQL instance to set secure connection level for. -* **SecureConnectionLevel**: (Key) SQL Server Reporting Service secure connection level. -* **Credential**: (Required) Credential with administrative permissions to the SQL instance. +* **DatabaseName**: (key) The SQL database name +* **SQLServerInstance**: (Required) The SQL server and instance +* **RecoveryModel**: (Required) Recovery Model (Full, Simple, BulkLogged) + +### xSQLServerAlias + +* **Ensure**: Determines whether the alias should be added or removed. Default value is 'Present' +* **Name**: (Key) The name of Alias (e.g. svr01\inst01). +* **ServerName**: (Key) The SQL Server you are aliasing (the netbios name or FQDN). +* **Protocol**: Protocol to use when connecting. Valid values are 'TCP' or 'NP' (Named Pipes). Default value is 'TCP'. +* **TCPPort**: The TCP port SQL is listening on. Only used when protocol is set to 'TCP'. Default value is port 1433. +* **UseDynamicTcpPort**: The UseDynamicTcpPort specify that the Net-Library will determine the port dynamically. The port specified in Port number will not be used. Default value is '$false'. +* **PipeName**: (Read) Named Pipes path from the Get-TargetResource method. + +### xSQLServerAlwaysOnService + +* **Ensure**: (Required) An enumerated value that describes if SQL server should have AlwaysOn property present or absent. +* **SQLServer**: (Key) The hostname of the SQL Server to be configured. +* **SQLInstance**: (Key) Name of the SQL instance to be configured. +* **RestartTimeout**: The length of time, in seconds, to wait for the service to restart. Default is 120 seconds. + +### xSQLServerAvailabilityGroupListener + +*This resource requires that the CNO has been delegated the right `Create computer object` on the organizational unit (OU) in which the CNO resides.* + +* **InstanceName** The SQL Server instance name of the primary replica. +* **NodeName** The host name or FQDN of the primary replica. +* **Ensure** If the availability group listener should be present or absent. +* **Name** The name of the availability group listener, max 15 characters. This name will be used as the Virtual Computer Object (VCO). +* **AvailabilityGroup** The name of the availability group to which the availability group listener is or will be connected. +* **IpAddress** The IP address used for the availability group listener, in the format 192.168.10.45/255.255.252.0. If using DCHP, set to the first IP-address of the DHCP subnet, in the format 192.168.8.1/255.255.252.0. Must be valid in the cluster-allowed IP range. +* **Port** The port used for the availability group listener. +* **DHCP** If DHCP should be used for the availability group listener instead of static IP address. + +### xSQLServerConfiguration + +* **SQLServer**: (Key) The hostname of the SQL Server to be configured +* **SQLInstanceName**: (Write) Name of the SQL instance to be configured. Default is 'MSSQLSERVER' +* **OptionName**: (Key) The name of the SQL configuration option to be checked. For all possible values reference [MSDN](https://msdn.microsoft.com/en-us/library/ms189631.aspx) or run sp_configure. +* **OptionValue**: (Required) The desired value of the SQL configuration option +* **RestartService**: Determines whether the instance should be restarted after updating the configuration option +* **RestartTimeout**: The length of time, in seconds, to wait for the service to restart. Default is 120 seconds. + +### xSQLServerDatabase + +* **Database**: (key) Database to be created or dropped +* **Ensure**: (Default = 'Present') An enumerated value that describes if Database is to be present or absent. +* **SQLServer**: (key) The SQL Server for the database +* **SQLInstance**: (key) The SQL instance for the database + +### xSQLServerDatabaseRole + +* **Ensure**: If 'Present' (the default value) then the login (user) will be added to the role(s). If 'Absent' then the login (user) will be removed from the role(s). +* **Name**: (Key) The name of the login that will become a member, or removed as a member, of the role(s). +* **SQLServer**: (Key) The SQL server on which the instance exist. +* **SQLInstanceName**: (Key) The SQL instance in which the database exist. +* **Database**: (Key) The database in which the login (user) and role(s) exist. +* **Role**: One or more roles to which the login (user) will be added or removed. + +### xSQLServerDatabaseOwner + +* **Database**: (Key) The SQL Database +* **Name**: (Required) The name of the SQL login for the owner +* **SQLServer**: The SQL Server for the database +* **SQLInstance**: The SQL instance for the database + +### xSQLServerDatabasePermissions + +* **Database**: (Key) The SQL Database +* **Name**: (Required) The name of permissions for the SQL database +* **Permissions**: (Required) The set of Permissions for the SQL database +* **SQLServer**: The SQL Server for the database +* **SQLInstanceName**: The SQL instance for the database + +### xSQLServerEndpoint + +* **EndPointName**: Name for endpoint to be created on SQL Server +* **Ensure**: (key) An enumerated value that describes if endpoint is to be present or absent on SQL Server +* **Port**: Port Endpoint should listen on +* **AuthorizedUser**: User who should have connect ability to endpoint +* **SQLServer**: The SQL Server for the database +* **SQLInstance**: The SQL instance for the database + +### xSQLServerEndpointPermission + +* **InstanceName** The SQL Server instance name. +* **NodeName** The host name or FQDN. +* **Ensure** If the permission should be present or absent. +* **Name** The name of the endpoint. +* **Principal** The login to which permission will be set. +* **Permission** The permission to set for the login. Valid value for permission are only CONNECT. + +### xSQLServerEndpointState + +* **InstanceName** The SQL Server instance name. +* **NodeName** The host name or FQDN. +* **Name** The name of the endpoint. +* **State** The state of the endpoint. Valid states are Started, Stopped or Disabled. ### xSQLServerFailoverClusterSetup @@ -191,13 +262,18 @@ A full list of changes in each version can be found in the [change log](CHANGELO * **ISSvcAccountUsername**: Output user name for the Integration Services service. * **ISFileSystemFolder**: File system folder for Integration Services. -### xSQLServerRSConfig +### xSQLServerFirewall -* **InstanceName**: (Key) Name of the SQL Server Reporting Services instance to be configured. -* **RSSQLServer**: (Required) Name of the SQL Server to host the Reporting Service database. -* **RSSQLInstanceName**: (Required) Name of the SQL Server instance to host the Reporting Service database. -* **SQLAdminCredential**: (Required) Credential to be used to perform the configuration. -* **IsInitialized**: Output is the Reporting Services instance initialized. +* **Ensure**: (Key) Ensures that SQL firewall rules are **Present** or **Absent** on the machine. +* **SourcePath**: (Required) UNC path to the root of the source files for installation. +* **SourceFolder**: Folder within the source path containing the source files for installation. +* **Features**: (Key) SQL features to enable firewall rules for. +* **InstanceName**: (Key) SQL instance to enable firewall rules for. +* **DatabaseEngineFirewall**: Is the firewall rule for the Database Engine enabled? +* **BrowserFirewall**: Is the firewall rule for the Browser enabled? +* **ReportingServicesFirewall**: Is the firewall rule for Reporting Services enabled? +* **AnalysisServicesFirewall**: Is the firewall rule for Analysis Services enabled? +* **IntegrationServicesFirewall**: Is the firewall rule for the Integration Services enabled? ### xSQLServerLogin @@ -208,44 +284,6 @@ A full list of changes in each version can be found in the [change log](CHANGELO * **SQLServer**: (Key) The SQL Server for the login. * **SQLInstanceName**: (Key) The SQL instance for the login. -### xSQLServerRole - -* **Name**: (Key) Name of the SQL Login to create -* **Ensure**: If the values should be present or absent. Valid values are 'Present' or 'Absent'. -* **ServerRole**: Type of SQL role to add.(bulkadmin, dbcreator, diskadmin, processadmin , public, securityadmin, serveradmin , setupadmin, sysadmin) -* **SQLServer**: SQL Server where login should be created -* **SQLInstance**: (Key) SQL Instance for the login - -### xSQLServerDatabaseRole - -* **Ensure**: If 'Present' (the default value) then the login (user) will be added to the role(s). If 'Absent' then the login (user) will be removed from the role(s). -* **Name**: (Key) The name of the login that will become a member, or removed as a member, of the role(s). -* **SQLServer**: (Key) The SQL server on which the instance exist. -* **SQLInstanceName**: (Key) The SQL instance in which the database exist. -* **Database**: (Key) The database in which the login (user) and role(s) exist. -* **Role**: One or more roles to which the login (user) will be added or removed. - -### xSQLServerDatabasePermissions - -* **Database**: (Key) The SQL Database -* **Name**: (Required) The name of permissions for the SQL database -* **Permissions**: (Required) The set of Permissions for the SQL database -* **SQLServer**: The SQL Server for the database -* **SQLInstanceName**: The SQL instance for the database - -### xSQLServerDatabaseOwner - -* **Database**: (Key) The SQL Database -* **Name**: (Required) The name of the SQL login for the owner -* **SQLServer**: The SQL Server for the database -* **SQLInstance**: The SQL instance for the database - -### xSQLDatabaseRecoveryModel - -* **DatabaseName**: (key) The SQL database name -* **SQLServerInstance**: (Required) The SQL server and instance -* **RecoveryModel**: (Required) Recovery Model (Full, Simple, BulkLogged) - ### xSQLServerMaxDop * **Ensure**: An enumerated value that describes if Min and Max memory is configured @@ -272,68 +310,6 @@ A full list of changes in each version can be found in the [change log](CHANGELO * **TCPPort**: Custom TCP port. * **RestartService**: If true will restart SQL Service instance service after update. Default false. -### xSQLServerDatabase - -* **Database**: (key) Database to be created or dropped -* **Ensure**: (Default = 'Present') An enumerated value that describes if Database is to be present or absent. -* **SQLServer**: (key) The SQL Server for the database -* **SQLInstance**: (key) The SQL instance for the database - -### xSQLAOGroupEnsure - -* **Ensure**: (Key) Determines whether the availability group should be added or removed. -* **AvailabilityGroupName** (Key) Name for availability group. -* **AvailabilityGroupNameListener** Listener name for availability group. -* **AvailabilityGroupNameIP** List of IP addresses associated with listener. -* **AvailabilityGroupSubMask** Network subnetmask for listener. -* **AvailabilityGroupPort** Port availability group should listen on. -* **ReadableSecondary** Mode secondaries should operate under (None, ReadOnly, ReadIntent). -* **AutoBackupPreference** Where backups should be backed up from (Primary, Secondary). -* **BackupPriority** The percentage weight for backup prority (default 50). -* **EndPointPort** The TCP port for the SQL AG Endpoint (default 5022). -* **SQLServer**: The SQL Server for the database. -* **SQLInstance**: The SQL instance for the database. -* **SetupCredential**: (Required) Credential to be used to Grant Permissions on SQL Server, set this to $null to use Windows Authentication. - -### xSQLAOGroupJoin - -* **Ensure**: (key) If the replica should be joined ('Present') to the Availability Group or not joined ('Absent') to the Availability Group. -* **AvailabilityGroupName** (key) The name Availability Group to join. -* **SQLServer**: Name of the SQL server to be configured. -* **SQLInstanceName**: Name of the SQL instance to be configured. -* **SetupCredential**: (Required) Credential to be used to Grant Permissions in SQL. - -### xSQLServerAlwaysOnService - -* **Ensure**: (Required) An enumerated value that describes if SQL server should have AlwaysOn property present or absent. -* **SQLServer**: (Key) The hostname of the SQL Server to be configured. -* **SQLInstance**: (Key) Name of the SQL instance to be configured. -* **RestartTimeout**: The length of time, in seconds, to wait for the service to restart. Default is 120 seconds. - -### xSQLServerEndpoint - -* **EndPointName**: Name for endpoint to be created on SQL Server -* **Ensure**: (key) An enumerated value that describes if endpoint is to be present or absent on SQL Server -* **Port**: Port Endpoint should listen on -* **AuthorizedUser**: User who should have connect ability to endpoint -* **SQLServer**: The SQL Server for the database -* **SQLInstance**: The SQL instance for the database - -### xWaitforAvailabilityGroup - -* **Name**: (key) Name for availability group -* **RetryIntervalSec**: Interval to check for availability group -* **RetryCount**: Maximum number of retries to check availability group creation - -### xSQLServerConfiguration - -* **SQLServer**: (Key) The hostname of the SQL Server to be configured -* **SQLInstanceName**: (Write) Name of the SQL instance to be configured. Default is 'MSSQLSERVER' -* **OptionName**: (Key) The name of the SQL configuration option to be checked. For all possible values reference [MSDN](https://msdn.microsoft.com/en-us/library/ms189631.aspx) or run sp_configure. -* **OptionValue**: (Required) The desired value of the SQL configuration option -* **RestartService**: Determines whether the instance should be restarted after updating the configuration option -* **RestartTimeout**: The length of time, in seconds, to wait for the service to restart. Default is 120 seconds. - ### xSQLServerPermission * **InstanceName** The SQL Server instance name. @@ -342,34 +318,13 @@ A full list of changes in each version can be found in the [change log](CHANGELO * **Principal** The login to which permission will be set. * **Permission** The permission to set for the login. Valid values are AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint. -### xSQLServerEndpointState - -* **InstanceName** The SQL Server instance name. -* **NodeName** The host name or FQDN. -* **Name** The name of the endpoint. -* **State** The state of the endpoint. Valid states are Started, Stopped or Disabled. - -### xSQLServerEndpointPermission - -* **InstanceName** The SQL Server instance name. -* **NodeName** The host name or FQDN. -* **Ensure** If the permission should be present or absent. -* **Name** The name of the endpoint. -* **Principal** The login to which permission will be set. -* **Permission** The permission to set for the login. Valid value for permission are only CONNECT. - -### xSQLServerAvailabilityGroupListener - -*This resource requires that the CNO has been delegated the right `Create computer object` on the organizational unit (OU) in which the CNO resides.* +### xSQLServerRole -* **InstanceName** The SQL Server instance name of the primary replica. -* **NodeName** The host name or FQDN of the primary replica. -* **Ensure** If the availability group listener should be present or absent. -* **Name** The name of the availability group listener, max 15 characters. This name will be used as the Virtual Computer Object (VCO). -* **AvailabilityGroup** The name of the availability group to which the availability group listener is or will be connected. -* **IpAddress** The IP address used for the availability group listener, in the format 192.168.10.45/255.255.252.0. If using DCHP, set to the first IP-address of the DHCP subnet, in the format 192.168.8.1/255.255.252.0. Must be valid in the cluster-allowed IP range. -* **Port** The port used for the availability group listener. -* **DHCP** If DHCP should be used for the availability group listener instead of static IP address. +* **Name**: (Key) Name of the SQL Login to create +* **Ensure**: If the values should be present or absent. Valid values are 'Present' or 'Absent'. +* **ServerRole**: Type of SQL role to add.(bulkadmin, dbcreator, diskadmin, processadmin , public, securityadmin, serveradmin , setupadmin, sysadmin) +* **SQLServer**: SQL Server where login should be created +* **SQLInstance**: (Key) SQL Instance for the login ### xSQLServerReplication @@ -383,6 +338,20 @@ A full list of changes in each version can be found in the [change log](CHANGELO * **UseTrustedConnection**: (Default = $true) Publisher security mode. * **UninstallWithForce**: (Default = $true) Force flag for uninstall procedure +### xSQLServerRSConfig + +* **InstanceName**: (Key) Name of the SQL Server Reporting Services instance to be configured. +* **RSSQLServer**: (Required) Name of the SQL Server to host the Reporting Service database. +* **RSSQLInstanceName**: (Required) Name of the SQL Server instance to host the Reporting Service database. +* **SQLAdminCredential**: (Required) Credential to be used to perform the configuration. +* **IsInitialized**: Output is the Reporting Services instance initialized. + +### xSQLServerRSSecureConnectionLevel + +* **InstanceName**: (Key) SQL instance to set secure connection level for. +* **SecureConnectionLevel**: (Key) SQL Server Reporting Service secure connection level. +* **Credential**: (Required) Credential with administrative permissions to the SQL instance. + ### xSQLServerScript * **ServerInstance**: (Required) The name of an instance of the Database Engine. For default instances, only specify the computer name. For named instances, use the format ComputerName\\InstanceName. @@ -392,12 +361,58 @@ A full list of changes in each version can be found in the [change log](CHANGELO * **Credential**: Specifies the credentials for making a SQL Server Authentication connection to an instance of the Database Engine. * **Variable**: Creates a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. -### xSQLServerAlias +### xSQLServerSetup -* **Ensure**: Determines whether the alias should be added or removed. Default value is 'Present' -* **Name**: (Key) The name of Alias (e.g. svr01\inst01). -* **ServerName**: (Key) The SQL Server you are aliasing (the netbios name or FQDN). -* **Protocol**: Protocol to use when connecting. Valid values are 'TCP' or 'NP' (Named Pipes). Default value is 'TCP'. -* **TCPPort**: The TCP port SQL is listening on. Only used when protocol is set to 'TCP'. Default value is port 1433. -* **UseDynamicTcpPort**: The UseDynamicTcpPort specify that the Net-Library will determine the port dynamically. The port specified in Port number will not be used. Default value is '$false'. -* **PipeName**: (Read) Named Pipes path from the Get-TargetResource method. +* **SourcePath**: (Required) The path to the root of the source files for installation. I.e and UNC path to a shared resource. +* **SourceFolder**: Folder within the source path containing the source files for installation. Default value is 'Source'. +* **SetupCredential**: (Required) Credential to be used to perform the installation. +* **SourceCredential**: Credential used to access SourcePath. +* **SuppressReboot**: Suppresses reboot. +* **ForceReboot**: Forces reboot. +* **Features**: (Key) SQL features to be installed. +* **InstanceName**: (Key) SQL instance to be installed. +* **InstanceID**: SQL instance ID, if different from InstanceName. +* **PID**: Product key for licensed installations. +* **UpdateEnabled**: Enabled updates during installation. +* **UpdateSource**: Path to the source of updates to be applied during installation. +* **SQMReporting**: Enable customer experience reporting. +* **ErrorReporting**: Enable error reporting. +* **InstallSharedDir**: Installation path for shared SQL files. +* **InstallSharedWOWDir**: Installation path for x86 shared SQL files. +* **InstanceDir**: Installation path for SQL instance files. +* **SQLSvcAccount**: Service account for the SQL service. +* **SQLSvcAccountUsername**: (Read) Output user name for the SQL service. +* **AgtSvcAccount**: Service account for the SQL Agent service. +* **AgtSvcAccountUsername**: (Read) Output user name for the SQL Agent service. +* **SQLCollation**: Collation for SQL. +* **SQLSysAdminAccounts**: Array of accounts to be made SQL administrators. +* **SecurityMode**: Security mode to apply to the SQL Server instance. +* **SAPwd**: SA password, if SecurityMode is set to 'SQL'. +* **InstallSQLDataDir**: Root path for SQL database files. +* **SQLUserDBDir**: Path for SQL database files. +* **SQLUserDBLogDir**: Path for SQL log files. +* **SQLTempDBDir**: Path for SQL TempDB files. +* **SQLTempDBLogDir**: Path for SQL TempDB log files. +* **SQLBackupDir**: Path for SQL backup files. +* **FTSvcAccount**: Service account for the Full Text service. +* **FTSvcAccountUsername**: (Read) Output username for the Full Text service. +* **RSSvcAccount**: Service account for Reporting Services service. +* **RSSvcAccountUsername**: (Read) Output username for the Reporting Services service. +* **ASSvcAccount**: Service account for Analysis Services service. +* **ASSvcAccountUsername**: (Read) Output username for the Analysis Services service. +* **ASCollation**: Collation for Analysis Services. +* **ASSysAdminAccounts**: Array of accounts to be made Analysis Services admins. +* **ASDataDir**: Path for Analysis Services data files. +* **ASLogDir**: Path for Analysis Services log files. +* **ASBackupDir**: Path for Analysis Services backup files. +* **ASTempDir**: Path for Analysis Services temp files. +* **ASConfigDir**: Path for Analysis Services config. +* **ISSvcAccount**: Service account for Integration Services service. +* **ISSvcAccountUsername**: (Read) Output user name for the Integration Services service. +* **BrowserSvcStartupType**: Specifies the startup mode for SQL Server Browser service. Valid values are 'Automatic', 'Disabled' or 'Manual'. + +### xWaitforAvailabilityGroup + +* **Name**: (key) Name for availability group +* **RetryIntervalSec**: Interval to check for availability group +* **RetryCount**: Maximum number of retries to check availability group creation From 2276eafcef2282e9a64d7dcba474962caf6b45a3 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 3 Jan 2017 13:15:48 +0100 Subject: [PATCH 06/31] README.md: Small fixes to sections and fixed wrong product name (#284) - Changes to README.md - Fixed the wrong product name in README.md - Fixed a type in the Requirements section. - Added link to Examples folder. - Fixed typo in the Installation section. --- CHANGELOG.md | 3 +++ README.md | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a8e6ef90..128591da8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ - Moved the list of resources in alphabetical order. - Moved each resource parameter list into alphabetical order. - Removed old text mentioning System Center. + - Now the correct product name is written in the installation section, and a typo was also fixed. + - Fixed a typo in the Requirements section. + - Added link to Examples folder in the Examples section. - Changes to xSQLServerSetup - The resource no longer uses Win32_Product WMI class when evaluating if SQL Server Management Studio is installed. See article [kb974524](https://support.microsoft.com/en-us/kb/974524) for more information. - Now it uses CIM cmdlets to get information from WMI classes. diff --git a/README.md b/README.md index c6cea7031..e57dcae60 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ If you need any help along the way, don't be afraid to ask. We are here for each ## Installation -To manually install the module, download the source code and unzip the contents of the '\Modules\xSQLServer' directory to the '$env:ProgramFiles\WindowsPowerShell\Modules folder'. +To manually install the module, download the source code and unzip the contents of the '\Modules\xSQLServer' directory to the '$env:ProgramFiles\WindowsPowerShell\Modules' folder. To install from the PowerShell gallery using PowerShellGet (in PowerShell 5.0) run the following command: @@ -37,7 +37,7 @@ To install from the PowerShell gallery using PowerShellGet (in PowerShell 5.0) r Find-Module -Name xSQLServer -Repository PSGallery | Install-Module ``` -To confirm installation, run the below command and ensure you see the Office Online Server DSC resoures available: +To confirm installation, run the below command and ensure you see the SQL Server DSC resources available: ```powershell Get-DscResource -Module xSQLServer @@ -45,12 +45,12 @@ Get-DscResource -Module xSQLServer ## Requirements -The minimum PowerShell version required is 4.0, which ships in Windows 8.1 or Windows Server 2012R2 (or higher versions). But PowerShell 4.0 can also be installed on Windows Server 2008 R2. +The minimum PowerShell version required is 4.0, which ships in Windows 8.1 or Windows Server 2012 R2 (or higher versions). But PowerShell 4.0 can also be installed on Windows Server 2008 R2. The preferred version is PowerShell 5.0 or higher, which ships with Windows 10 or Windows Server 2016. ## Examples -You can review the "examples" directory in the xSQLServer module for some general use scenarios for all of the resources that are in the module. +You can review the [Examples](/Examples) directory in the xSQLServer module for some general use scenarios for all of the resources that are in the module. ## Change log From 7cf0a7f785a526e64b1975d43fb6ed95657ab059 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 5 Jan 2017 14:07:39 +0100 Subject: [PATCH 07/31] xSQLServerScript: Changes to the resource and tests (#280) - Changes to xSQLServerScript - All of the $null is now on the left side of equality comparisons. - All credential parameters now also has the type [System.Management.Automation.Credential()] to work with PowerShell 4.0. - It is now possible to configure two instances on the same node, with the same script. - Added to the description text for the parameter 'Credential' describing how to authenticate using Windows Authentication. - Added examples to show how to authenticate using either SQL or Windows authentication. - Added a note in README.md for xSQLServerScript that there is a known problem running the resource using PowerShell 4.0. --- CHANGELOG.md | 6 + .../MSFT_xSQLServerScript.psm1 | 139 +++++++++++++++-- .../MSFT_xSQLServerScript.schema.mof | 16 +- .../1-RunScriptUsingSQLAuthentication.ps1 | 41 +++++ .../2-RunScriptUsingWindowsAuthentication.ps1 | 53 +++++++ Examples/SQLServerScript.ps1 | 28 ---- README.md | 15 +- Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 | 143 ++++++++++-------- 8 files changed, 323 insertions(+), 118 deletions(-) create mode 100644 Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1 create mode 100644 Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1 delete mode 100644 Examples/SQLServerScript.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 128591da8..851d50146 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,12 @@ - The resource no longer uses Win32_Product WMI class when evaluating if SQL Server Management Studio is installed. See article [kb974524](https://support.microsoft.com/en-us/kb/974524) for more information. - Now it uses CIM cmdlets to get information from WMI classes. - Resolved all of the PSScriptAnalyzer warnings that was triggered in the common tests. +- Changes to xSQLServerScript + - All credential parameters now also has the type [System.Management.Automation.Credential()] to better work with PowerShell 4.0. + - It is now possible to configure two instances on the same node, with the same script. + - Added to the description text for the parameter `Credential` describing how to authenticate using Windows Authentication. + - Added examples to show how to authenticate using either SQL or Windows authentication. + - A recent issue showed that there is a known problem running this resource using PowerShell 4.0. For more information, see [issue #273](https://github.com/PowerShell/xSQLServer/issues/273) ## 4.0.0.0 diff --git a/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.psm1 b/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.psm1 index 9026f0e88..75b23494f 100644 --- a/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.psm1 +++ b/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.psm1 @@ -1,10 +1,39 @@ -$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Write-Verbose -Message "CurrentPath: $currentPath" - -# Load Common Code -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -Verbose:$false -ErrorAction Stop - - +$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') + +<# + .SYNOPSIS + Returns the current state of the SQL Server features. + + .PARAMETER ServerInstance + The name of an instance of the Database Engine. For a default instance, only specify the computer name. For a named instances, + use the format ComputerName\InstanceName. + + .PARAMETER SetFilePath + Path to the T-SQL file that will perform Set action. + + .PARAMETER GetFilePath + Path to the T-SQL file that will perform Get action. + Any values returned by the T-SQL queries will also be returned by the cmdlet Get-DscConfiguration through the `GetResult` property. + + .PARAMETER TestFilePath + Path to the T-SQL file that will perform Test action. + Any script that does not throw an error or returns null is evaluated to true. + The cmdlet Invoke-SqlCmd treats T-SQL Print statements as verbose text, and will not cause the test to return false. + + .PARAMETER Credential + The credentials to authenticate with, using SQL Authentication. To authenticate using Windows Authentication, assign the credentials + to the built-in parameter `PsDscRunAsCredential`. If both parameters `Credential` and `PsDscRunAsCredential` are not assigned, + then SYSTEM account will be used to authenticate using Windows Authentication. + + .PARAMETER Variable + Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. + Use a Windows PowerShell array to specify multiple variables and their values. For more information how to use this, + please go to the help documentation for [Invoke-Sqlcmd](https://technet.microsoft.com/en-us/library/mt683370.aspx). + + .OUTPUTS + Hash table containing key 'GetResult' which holds the value of the result from the SQL script that was ran from the parameter 'GetFilePath'. +#> function Get-TargetResource { [CmdletBinding()] @@ -28,23 +57,24 @@ function Get-TargetResource $TestFilePath, [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Credential, [System.String[]] $Variable - ) + ) $result = Invoke-SqlScript -ServerInstance $ServerInstance -SqlScriptPath $GetFilePath ` -Credential $Credential -Variable $Variable -ErrorAction Stop $getResult = Out-String -InputObject $result - + $returnValue = @{ ServerInstance = [System.String] $ServerInstance SetFilePath = [System.String] $SetFilePath GetFilePath = [System.String] $GetFilePath TestFilePath = [System.String] $TestFilePath - Username = [System.Management.Automation.PSCredential] $Credential + Username = [System.Object] $Credential Variable = [System.String[]] $Variable GetResult = [System.String[]] $getresult } @@ -52,6 +82,36 @@ function Get-TargetResource $returnValue } +<# + .SYNOPSIS + Returns the current state of the SQL Server features. + + .PARAMETER ServerInstance + The name of an instance of the Database Engine. For a default instance, only specify the computer name. For a named instances, + use the format ComputerName\InstanceName. + + .PARAMETER SetFilePath + Path to the T-SQL file that will perform Set action. + + .PARAMETER GetFilePath + Path to the T-SQL file that will perform Get action. + Any values returned by the T-SQL queries will also be returned by the cmdlet Get-DscConfiguration through the `GetResult` property. + + .PARAMETER TestFilePath + Path to the T-SQL file that will perform Test action. + Any script that does not throw an error or returns null is evaluated to true. + The cmdlet Invoke-SqlCmd treats T-SQL Print statements as verbose text, and will not cause the test to return false. + + .PARAMETER Credential + The credentials to authenticate with, using SQL Authentication. To authenticate using Windows Authentication, assign the credentials + to the built-in parameter `PsDscRunAsCredential`. If both parameters `Credential` and `PsDscRunAsCredential` are not assigned, + then SYSTEM account will be used to authenticate using Windows Authentication. + + .PARAMETER Variable + Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. + Use a Windows PowerShell array to specify multiple variables and their values. For more information how to use this, + please go to the help documentation for [Invoke-Sqlcmd](https://technet.microsoft.com/en-us/library/mt683370.aspx). +#> function Set-TargetResource { [CmdletBinding()] @@ -74,6 +134,7 @@ function Set-TargetResource $TestFilePath, [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Credential, [System.String[]] @@ -84,7 +145,37 @@ function Set-TargetResource -Credential $Credential -Variable $Variable -ErrorAction Stop } +<# + .SYNOPSIS + Returns the current state of the SQL Server features. + + .PARAMETER ServerInstance + The name of an instance of the Database Engine. For a default instance, only specify the computer name. For a named instances, + use the format ComputerName\InstanceName. + .PARAMETER SetFilePath + Path to the T-SQL file that will perform Set action. + + .PARAMETER GetFilePath + Path to the T-SQL file that will perform Get action. + Any values returned by the T-SQL queries will also be returned by the cmdlet Get-DscConfiguration through the `GetResult` property. + + .PARAMETER TestFilePath + Path to the T-SQL file that will perform Test action. + Any script that does not throw an error or returns null is evaluated to true. + The cmdlet Invoke-SqlCmd treats T-SQL Print statements as verbose text, and will not cause the test to return false. + + .PARAMETER Credential + The credentials to authenticate with, using SQL Authentication. To authenticate using Windows Authentication, assign the credentials + to the built-in parameter `PsDscRunAsCredential`. If both parameters `Credential` and `PsDscRunAsCredential` are not assigned, + then SYSTEM account will be used to authenticate using Windows Authentication. + + .PARAMETER Variable + Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. + Use a Windows PowerShell array to specify multiple variables and their values. For more information how to use this, + please go to the help documentation for [Invoke-Sqlcmd](https://technet.microsoft.com/en-us/library/mt683370.aspx). + +#> function Test-TargetResource { [CmdletBinding()] @@ -108,6 +199,7 @@ function Test-TargetResource $TestFilePath, [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Credential, [System.String[]] @@ -115,11 +207,11 @@ function Test-TargetResource ) try - { + { $result = Invoke-SqlScript -ServerInstance $ServerInstance -SqlScriptPath $TestFilePath ` -Credential $Credential -Variable $Variable -ErrorAction Stop - if($result -eq $null) + if($null -eq $result) { return $true } @@ -135,6 +227,25 @@ function Test-TargetResource } } +<# + .SYNOPSIS + Execute an SQL script located in a file on disk. + + .PARAMETER ServerInstance + The name of an instance of the Database Engine. + For default instances, only specify the computer name. For named instances, use the format ComputerName\InstanceName. + + .PARAMETER SqlScriptPath + Path to SQL script file that will be executed. + + .PARAMETER Credential + The credentials to use to authenticate using SQL Authentication. To authenticate using Windows Authentication, assing the credentials + to the built-in parameter 'PsDscRunAsCredential'. If both parameters 'Credential' and 'PsDscRunAsCredential' are not assigned, then + the SYSTEM account will be used to authenticate using Windows Authentication. + + .PARAMETER Variable + Creates a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. +#> function Invoke-SqlScript { param @@ -148,6 +259,7 @@ function Invoke-SqlScript $SqlScriptPath, [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Credential, [System.String[]] @@ -159,7 +271,7 @@ function Invoke-SqlScript if($null -ne $Credential) { $null = $PSBoundParameters.Add("Username", $Credential.UserName) - $null = $PSBoundParameters.Add("Password", $Credential.GetNetworkCredential().password) + $null = $PSBoundParameters.Add("Password", $Credential.GetNetworkCredential().password) } $null = $PSBoundParameters.Remove("Credential") @@ -169,4 +281,3 @@ function Invoke-SqlScript } Export-ModuleMember -Function *-TargetResource - diff --git a/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.schema.mof b/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.schema.mof index e7c9ab6af..275ccd78c 100644 --- a/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.schema.mof +++ b/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.schema.mof @@ -1,13 +1,11 @@ - [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerScript")] class MSFT_xSQLServerScript : OMI_BaseResource { - [Required, Description("The name of an instance of the Database Engine. For default instances, only specify the computer name. For named instances, use the format ComputerName\\InstanceName")] String ServerInstance; - [Key, Description("Path to SQL file that will perform Set action.")] String SetFilePath; - [Key, Description("Path to SQL file that will perform Get action.")] String GetFilePath; - [Key, Description("Path to SQL file that will perform Test action.")] String TestFilePath; - [Write, EmbeddedInstance("MSFT_Credential"), Description("Credential to be used to run SQL scripts.")] String Credential; - [Write, Description("Creates a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable.")] String Variable[]; - [Read, Description("Result of Get action.")] String GetResult[]; + [Key, Description("The name of an instance of the Database Engine. For a default instance, only specify the computer name. For a named instance, use the format ComputerName\\InstanceName")] String ServerInstance; + [Key, Description("Path to the T-SQL file that will perform Set action.")] String SetFilePath; + [Key, Description("Path to the T-SQL file that will perform Get action. Any values returned by the T-SQL queries will also be returned by the cmdlet Get-DscConfiguration through the 'GetResult' property.")] String GetFilePath; + [Key, Description("Path to the T-SQL file that will perform Test action. Any script that does not throw an error or returns null is evaluated to true. The cmdlet Invoke-SqlCmd treats T-SQL Print statements as verbose text, and will not cause the test to return false.")] String TestFilePath; + [Write, EmbeddedInstance("MSFT_Credential"), Description("The credentials to authenticate with, using SQL Authentication. To authenticate using Windows Authentication, assign the credentials to the built-in parameter 'PsDscRunAsCredential'. If both parameters 'Credential' and 'PsDscRunAsCredential' are not assigned, then SYSTEM account will be used to authenticate using Windows Authentication.")] String Credential; + [Write, Description("Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. Use a Windows PowerShell array to specify multiple variables and their values. For more information how to use this, please go to the help documentation for Invoke-Sqlcmd.")] String Variable[]; + [Read, Description("Contains the values returned from the T-SQL script provided in the parameter 'GetFilePath' when cmdlet Get-DscConfiguration is run.")] String GetResult[]; }; - diff --git a/Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1 b/Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1 new file mode 100644 index 000000000..5fdf998ec --- /dev/null +++ b/Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1 @@ -0,0 +1,41 @@ +<# +.EXAMPLE + This example shows how to run SQL script using SQL Authentication. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SqlCredential + ) + + Import-DscResource -ModuleName xSQLServer + + Node localhost + { + xSQLServerScript 'RunSQLScript' + { + ServerInstance = 'localhost\SQL2016' + Credential = $SqlCredential + + SetFilePath = 'C:\DSCTemp\SQLScripts\Set-RunSQLScript.sql' + TestFilePath = 'C:\DSCTemp\SQLScripts\Test-RunSQLScript.sql' + GetFilePath = 'C:\DSCTemp\SQLScripts\Get-RunSQLScript.sql' + Variable = @("FilePath=C:\temp\log\AuditFiles") + } + } +} + +$configurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + } + ) +} + +Example -SqlCredential (Get-Credential) -ConfigurationData $configurationData -OutputPath 'C:\DSCTemp\Configuration' + +Start-DscConfiguration -Path 'C:\DSCTemp\Configuration' -Wait -Verbose -Force diff --git a/Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1 b/Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1 new file mode 100644 index 000000000..49ab909b5 --- /dev/null +++ b/Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1 @@ -0,0 +1,53 @@ +<# +.EXAMPLE + These two example shows how to run SQL script using Windows Authentication. + First example shows how the resource is run as account SYSTEM. And the second example shows how the resource is run with a user account. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $WindowsCredential + ) + + Import-DscResource -ModuleName xSQLServer + + Node localhost + { + xSQLServerScript 'RunSQLScript-AsSYSTEM' + { + ServerInstance = 'localhost\SQL2016' + + SetFilePath = 'C:\DSCTemp\SQLScripts\Set-RunSQLScript-AsSYSTEM.sql' + TestFilePath = 'C:\DSCTemp\SQLScripts\Test-RunSQLScript-AsSYSTEM.sql' + GetFilePath = 'C:\DSCTemp\SQLScripts\Get-RunSQLScript-AsSYSTEM.sql' + Variable = @("FilePath=C:\temp\log\AuditFiles") + } + + xSQLServerScript 'RunSQLScript-AsUSER' + { + ServerInstance = 'localhost\SQL2016' + + SetFilePath = 'C:\DSCTemp\SQLScripts\Set-RunSQLScript-AsUSER.sql' + TestFilePath = 'C:\DSCTemp\SQLScripts\Test-RunSQLScript-AsUSER.sql' + GetFilePath = 'C:\DSCTemp\SQLScripts\Get-RunSQLScript-AsUSER.sql' + Variable = @("FilePath=C:\temp\log\AuditFiles") + + PsDscRunAsCredential = $WindowsCredential + } + } +} + +$configurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + } + ) +} + +Example -SqlCredential (Get-Credential) -ConfigurationData $configurationData -OutputPath 'C:\DSCTemp\Configuration' + +Start-DscConfiguration -Path 'C:\DSCTemp\Configuration' -Wait -Verbose -Force diff --git a/Examples/SQLServerScript.ps1 b/Examples/SQLServerScript.ps1 deleted file mode 100644 index 922470347..000000000 --- a/Examples/SQLServerScript.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -configuration SQLSettings -{ - Import-DscResource -ModuleName 'xSQLServer' - - Node 'localhost' - { - xSQLServerScript SqlSettings - { - ServerInstance = "$env:COMPUTERNAME\SMA" - SetFilePath = "C:\temp\Set-SQlsettings.sql" - TestFilePath = "C:\temp\Test-SQlsettings.sql" - GetFilePath = "C:\temp\Get-SQlsettings.sql" - Variable = @("FilePath=C:\temp\log\AuditFiles") - } - } -} - -$configData = @{ - AllNodes = @( - @{ - NodeName = 'localhost' - } - ) -} - -SQLSettings -ConfigurationData $configData - -Start-DscConfiguration -Path .\SQLSettings -Wait -Force -Verbose diff --git a/README.md b/README.md index e57dcae60..cde18c2c5 100644 --- a/README.md +++ b/README.md @@ -354,12 +354,15 @@ A full list of changes in each version can be found in the [change log](CHANGELO ### xSQLServerScript -* **ServerInstance**: (Required) The name of an instance of the Database Engine. For default instances, only specify the computer name. For named instances, use the format ComputerName\\InstanceName. -* **SetFilePath**: (Key) Path to SQL file that will perform Set action. -* **GetFilePath**: (Key) Path to SQL file that will perform Get action. SQL Queries returned by this function are returned by the Get-DscConfiguration cmdlet with the GetResult parameter. -* **TestFilePath**: (Key) Path to SQL file that will perform Test action. Any Script that does not throw an error and returns null is evaluated to true. Invoke-SqlCmd treats SQL Print statements as verbose text, this will not cause a Test to return false. -* **Credential**: Specifies the credentials for making a SQL Server Authentication connection to an instance of the Database Engine. -* **Variable**: Creates a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. +_Note: There is a known problem running this resource using PowerShell 4.0. See [issue #273](https://github.com/PowerShell/xSQLServer/issues/273) for more information._ + +* **ServerInstance**: (Key) The name of an instance of the Database Engine. For a default instance, only specify the computer name. For a named instances, use the format ComputerName\\InstanceName. +* **SetFilePath**: (Key) Path to the T-SQL file that will perform Set action. +* **GetFilePath**: (Key) Path to the T-SQL file that will perform Get action. Any values returned by the T-SQL queries will also be returned by the cmdlet Get-DscConfiguration through the `GetResult` property. +* **TestFilePath**: (Key) Path to the T-SQL file that will perform Test action. Any script that does not throw an error or returns null is evaluated to true. The cmdlet Invoke-SqlCmd treats T-SQL Print statements as verbose text, and will not cause the test to return false. +* **Credential**: The credentials to authenticate with, using SQL Authentication. To authenticate using Windows Authentication, assign the credentials to the built-in parameter `PsDscRunAsCredential`. If both parameters `Credential` and `PsDscRunAsCredential` are not assigned, then SYSTEM account will be used to authenticate using Windows Authentication. +* **Variable**: Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. Use a Windows PowerShell array to specify multiple variables and their values. For more information how to use this, please go to the help documentation for [Invoke-Sqlcmd](https://technet.microsoft.com/en-us/library/mt683370.aspx). +* **GetResult**: (Read) Contains the values returned from the T-SQL script provided in the parameter `GetFilePath` when cmdlet Get-DscConfiguration is run. ### xSQLServerSetup diff --git a/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 index 2d051156d..d6db145cc 100644 --- a/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 @@ -17,60 +17,65 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) } -Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName 'xSQLServer' ` - -DSCResourceName 'MSFT_xSQLServerScript' ` - -TestType Unit + -DSCResourceName 'MSFT_xSQLServerScript' ` + -TestType Unit #endregion HEADER function Invoke-TestSetup { - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SqlPowerShellSqlExecutionException.cs') - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SQLPSStub.psm1') -Force + Add-Type -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'SqlPowerShellSqlExecutionException.cs') + Import-Module -Name (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Force } function Invoke-TestCleanup { Restore-TestEnvironment -TestEnvironment $TestEnvironment } + # Begin Testing try { Invoke-TestSetup - InModuleScope 'MSFT_xSQLServerScript' { + InModuleScope 'MSFT_xSQLServerScript' { + + $script:DSCModuleName = 'xSQLServer' + $resourceName = 'MSFT_xSQLServerScript' + $sqlServerHelperModuleName = 'xSQLServerHelper' $testParameters = @{ ServerInstance = $env:COMPUTERNAME - SetFilePath = "set.sql" - GetFilePath = "get.sql" + SetFilePath = "set.sql" + GetFilePath = "get.sql" TestFilePath = "test.sql" } - $dscResourceName = 'MSFT_xSQLServerScript' - $sqlServerHelperModuleName = 'xSQLServerHelper' - - Describe "$dscResourceName\Get-TargetResource" { + Describe "$resourceName\Get-TargetResource" { Context 'Get-TargetResource fails to import SQLPS module' { $throwMessage = "Failed to import SQLPS module." - Mock -CommandName Import-Module -MockWith { throw $throwMessage } -ModuleName $sqlServerHelperModuleName + Mock -CommandName Import-Module -MockWith { + throw $throwMessage + } -ModuleName $sqlServerHelperModuleName - It 'Should throw an error' { + It 'Should throw the correct error from Import-Module' { { Get-TargetResource @testParameters } | Should Throw $throwMessage } } Context 'Get-TargetResource returns script results successfully' { Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName - Mock -CommandName Invoke-Sqlcmd -MockWith { "" } -ModuleName $dscResourceName - - $result = Get-TargetResource @testParameters + Mock -CommandName Invoke-Sqlcmd -MockWith { + return '' + } It 'Should return the expected results' { + $result = Get-TargetResource @testParameters $result.ServerInstance | Should Be $testParameters.ServerInstance $result.SetFilePath | Should Be $testParameters.SetFilePath $result.GetFilePath | Should Be $testParameters.GetFilePath @@ -79,110 +84,121 @@ try } } - Context 'Get SQl script throws an error' { + Context 'Get-TargetResource throws an error when running the script in the GetFilePath parameter' { $errorMessage = "Failed to run SQL Script" Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName - Mock -CommandName Invoke-Sqlcmd -MockWith { Throw $errorMessage } -ModuleName $script:DSCResourceName + Mock -CommandName Invoke-Sqlcmd -MockWith { + throw $errorMessage + } - It 'Should throw an error' { + It 'Should throw the correct error from Invoke-Sqlcmd' { { Get-TargetResource @testParameters } | Should Throw $errorMessage } - } + } } - Describe "$dscResourceName\Set-TargetResource" { + Describe "$resourceName\Set-TargetResource" { Context 'Set-TargetResource fails to import SQLPS module' { $throwMessage = "Failed to import SQLPS module." Mock -CommandName Import-Module -MockWith { throw $throwMessage } -ModuleName $sqlServerHelperModuleName - It 'Should throw an error' { + It 'Should throw the correct error from Import-Module' { { Set-TargetResource @testParameters } | Should Throw $throwMessage } } Context 'Set-TargetResource runs script without issue' { Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName - Mock -CommandName Invoke-Sqlcmd -MockWith { "" } -ModuleName $dscResourceName - - $result = Set-TargetResource @testParameters + Mock -CommandName Invoke-Sqlcmd -MockWith { + return '' + } It 'Should return the expected results' { - $result | Should Be "" + $result = Set-TargetResource @testParameters + $result | Should Be '' } } - Context 'Set SQL script trhows an error' { + Context 'Set-TargetResource throws an error when running the script in the SetFilePath parameter' { $errorMessage = "Failed to run SQL Script" Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName - Mock -CommandName Invoke-Sqlcmd -MockWith { Throw $errorMessage } -ModuleName $script:DSCResourceName + Mock -CommandName Invoke-Sqlcmd -MockWith { + throw $errorMessage + } - It 'Should throw an error' { + It 'Should throw the correct error from Invoke-Sqlcmd' { { Set-TargetResource @testParameters } | Should Throw $errorMessage } } } - - Describe "$dscResourceName\Test-TargetResource" { + + Describe "$resourceName\Test-TargetResource" { Context 'Test-TargetResource fails to import SQLPS module' { - $throwMessage = "Failed to import SQLPS module." + $throwMessage = 'Failed to import SQLPS module.' - Mock -CommandName Import-Module -MockWith { throw $throwMessage } -ModuleName $sqlServerHelperModuleName + Mock -CommandName Import-Module -MockWith { + throw $throwMessage + } -ModuleName $sqlServerHelperModuleName - It 'Should throw an error' { + It 'Should throw the correct error from Import-Module' { { Set-TargetResource @testParameters } | Should Throw $throwMessage } } Context 'Test-TargetResource runs script without issue' { Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName - Mock -CommandName Invoke-Sqlcmd -MockWith {} -ModuleName $dscResourceName - - $result = Test-TargetResource @testParameters + Mock -CommandName Invoke-Sqlcmd -MockWith {} It 'Should return true' { + $result = Test-TargetResource @testParameters $result | Should Be $true } } - Context 'Test SQL script throws SQL execution error' { + Context 'Test-TargetResource throws the exception SqlPowerShellSqlExecutionException when running the script in the TestFilePath parameter' { Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName - Mock -CommandName Invoke-Sqlcmd -MockWith { throw New-Object Microsoft.SqlServer.Management.PowerShell.SqlPowerShellSqlExecutionException} -ModuleName $dscResourceName - - $result = Test-TargetResource @testParameters + Mock -CommandName Invoke-Sqlcmd -MockWith { + throw New-Object Microsoft.SqlServer.Management.PowerShell.SqlPowerShellSqlExecutionException + } It 'Should return false' { + $result = Test-TargetResource @testParameters $result | Should Be $false } } - Context 'Test SQL script throws a different error' { + Context 'Test-TargetResource throws an unexpected error when running the script in the TestFilePath parameter' { $errorMessage = "Failed to run SQL Script" Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName - Mock -CommandName Invoke-Sqlcmd -MockWith { Throw $errorMessage } -ModuleName $script:DSCResourceName + Mock -CommandName Invoke-Sqlcmd -MockWith { + throw $errorMessage + } - It 'Should throw an error' { + It 'Should throw the correct error from Invoke-Sqlcmd' { { Test-TargetResource @testParameters } | Should Throw $errorMessage - } + } } } - Describe "$dscResourceName\Invoke-SqlScript" { + Describe "$resourceName\Invoke-SqlScript" { $invokeScriptParameters = @{ - ServerInstance = $env:COMPUTERNAME - SqlScriptPath = "set.sql" + ServerInstance = $env:COMPUTERNAME + SqlScriptPath = "set.sql" } Context 'Invoke-SqlScript fails to import SQLPS module' { $throwMessage = "Failed to import SQLPS module." - Mock -CommandName Import-Module -MockWith { throw $throwMessage } -ModuleName $sqlServerHelperModuleName + Mock -CommandName Import-Module -MockWith { + throw $throwMessage + } -ModuleName $sqlServerHelperModuleName - It 'Should throw an error' { + It 'Should throw the correct error from Import-Module' { { Invoke-SqlScript @invokeScriptParameters } | Should Throw $throwMessage } } @@ -192,17 +208,20 @@ try $user = "User" Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName - Mock -CommandName Invoke-Sqlcmd -MockWith {} -ParameterFilter { ($Username -eq $user) -and ($Password -eq $passwordPlain) } -ModuleName $dscResourceName + Mock -CommandName Invoke-Sqlcmd -MockWith {} -ParameterFilter { + ($Username -eq $user) -and ($Password -eq $passwordPlain) + } $password = ConvertTo-SecureString -String $passwordPlain -AsPlainText -Force $cred = New-Object pscredential -ArgumentList $user, $password - $invokeScriptParameters.Add("Credential", $cred) - - $null = Invoke-SqlScript @invokeScriptParameters - It 'Should call Invoke-Sqlcmd with correct parameters' { - Assert-MockCalled -CommandName Invoke-Sqlcmd -Times 1 -Exactly -ParameterFilter { ($Username -eq $user) -and ($Password -eq $passwordPlain) } + $invokeScriptParameters.Add("Credential", $cred) + $null = Invoke-SqlScript @invokeScriptParameters + + Assert-MockCalled -CommandName Invoke-Sqlcmd -ParameterFilter { + ($Username -eq $user) -and ($Password -eq $passwordPlain) + } -Times 1 -Exactly -Scope It } } @@ -210,13 +229,15 @@ try $errorMessage = "Failed to run SQL Script" Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName - Mock -CommandName Invoke-Sqlcmd -MockWith { Throw $errorMessage } -ModuleName $script:DSCResourceName + Mock -CommandName Invoke-Sqlcmd -MockWith { + throw $errorMessage + } - It 'Should throw an error' { + It 'Should throw the correct error from Invoke-Sqlcmd' { { Invoke-SqlScript @invokeScriptParameters } | Should Throw $errorMessage - } + } } - } + } } } finally From 4b4d2b02979fa8d4e64be5df13c7233dd9d019db Mon Sep 17 00:00:00 2001 From: Ryan Bartram Date: Fri, 6 Jan 2017 09:57:12 +0100 Subject: [PATCH 08/31] xSQLServerSetup: Support for MSA and other nt authority accounts (#285) - Changes to xSQLServerSetup - Update to support other NT Authority accounts including Local Service and Network Service. Also supports Managed Service Accounts (MSA). - Wrote Unit Test for helper function Join-ServiceAccountInfo --- CHANGELOG.md | 1 + .../MSFT_xSQLServerSetup.psm1 | 184 +++--- Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 | 622 ++++++++++-------- 3 files changed, 433 insertions(+), 374 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 851d50146..bf1618244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - The resource no longer uses Win32_Product WMI class when evaluating if SQL Server Management Studio is installed. See article [kb974524](https://support.microsoft.com/en-us/kb/974524) for more information. - Now it uses CIM cmdlets to get information from WMI classes. - Resolved all of the PSScriptAnalyzer warnings that was triggered in the common tests. + - Improvement for service accounts to enable support for Managed Service Accounts as well as other nt authority accounts - Changes to xSQLServerScript - All credential parameters now also has the type [System.Management.Automation.Credential()] to better work with PowerShell 4.0. - It is now possible to configure two instances on the same node, with the same script. diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 index b6a913b65..5b6083f96 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 @@ -8,7 +8,7 @@ Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $script .PARAMETER SourcePath The path to the root of the source files for installation. I.e and UNC path to a shared resource. - + .PARAMETER SourceFolder Folder within the source path containing the source files for installation. Default value is 'Source'. @@ -54,10 +54,10 @@ function Get-TargetResource { NetUse -SourcePath $SourcePath -Credential $SourceCredential -Ensure 'Present' } - + $path = Join-Path -Path (Join-Path -Path $SourcePath -ChildPath $SourceFolder) -ChildPath 'setup.exe' $path = ResolvePath -Path $path - + New-VerboseMessage -Message "Using path: $path" $sqlVersion = GetSQLVersion -Path $path @@ -66,7 +66,7 @@ function Get-TargetResource { NetUse -SourcePath $SourcePath -Credential $SourceCredential -Ensure 'Absent' } - + if ($InstanceName -eq 'MSSQLSERVER') { $databaseServiceName = 'MSSQLSERVER' @@ -83,9 +83,9 @@ function Get-TargetResource $reportServiceName = "ReportServer`$$InstanceName" $analysisServiceName = "MSOLAP`$$InstanceName" } - + $integrationServiceName = "MsDtsServer$($sqlVersion)0" - + $features = '' $services = Get-Service @@ -105,7 +105,7 @@ function Get-TargetResource { New-VerboseMessage -Message 'Replication feature detected' $Features += 'REPLICATION,' - } + } else { New-VerboseMessage -Message 'Replication feature not detected' @@ -118,7 +118,7 @@ function Get-TargetResource $sqlCollation = $databaseServer.Collation - $sqlSystemAdminAccounts = @() + $sqlSystemAdminAccounts = @() foreach ($sqlUser in $databaseServer.Logins) { foreach ($sqlRole in $sqlUser.ListMembers()) @@ -129,13 +129,13 @@ function Get-TargetResource } } } - + if ($databaseServer.LoginMode -eq 'Mixed') { $securityMode = 'SQL' } else - { + { $securityMode = 'Windows' } @@ -161,7 +161,7 @@ function Get-TargetResource { $features += 'AS,' $analysisServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$analysisServiceName'").StartName - + $analysisServer = Connect-SQLAnalysis -SQLServer localhost -SQLInstanceName $InstanceName $analysisCollation = $analysisServer.ServerProperties['CollationName'].Value @@ -187,17 +187,17 @@ function Get-TargetResource $installedProductSqlServerManagementStudio2008R2 = Get-ItemProperty -Path ( Join-Path -Path $registryUninstallPath -ChildPath '{72AB7E6F-BC24-481E-8C45-1AB5B3DD795D}' ) -ErrorAction SilentlyContinue - + # Verify if SQL Server Management Studio 2012 (major version 11) is installed $installedProductSqlServerManagementStudio2012 = Get-ItemProperty -Path ( Join-Path -Path $registryUninstallPath -ChildPath '{A7037EB2-F953-4B12-B843-195F4D988DA1}' ) -ErrorAction SilentlyContinue - + # Verify if SQL Server Management Studio 2014 (major version 12) is installed $installedProductSqlServerManagementStudio2014 = Get-ItemProperty -Path ( Join-Path -Path $registryUninstallPath -ChildPath '{75A54138-3B98-4705-92E4-F619825B121F}' ) -ErrorAction SilentlyContinue - + if ( ($sqlVersion -eq 10 -and $installedProductSqlServerManagementStudio2008R2) -or ($sqlVersion -eq 11 -and $installedProductSqlServerManagementStudio2012) -or @@ -211,7 +211,7 @@ function Get-TargetResource $installedProductSqlServerManagementStudioAdvanced2008R2 = Get-ItemProperty -Path ( Join-Path -Path $registryUninstallPath -ChildPath '{B5FE23CC-0151-4595-84C3-F1DE6F44FE9B}' ) -ErrorAction SilentlyContinue - + # Evaluating if SQL Server Management Studio Advanced 2012 (major version 11) is installed $installedProductSqlServerManagementStudioAdvanced2012 = Get-ItemProperty -Path ( Join-Path -Path $registryUninstallPath -ChildPath '{7842C220-6E9A-4D5A-AE70-0E138271F883}' @@ -314,7 +314,7 @@ function Get-TargetResource .PARAMETER SourcePath The path to the root of the source files for installation. I.e and UNC path to a shared resource. - + .PARAMETER SourceFolder Folder within the source path containing the source files for installation. Default value is 'Source'. @@ -581,7 +581,7 @@ function Set-TargetResource $InstanceName = $InstanceName.ToUpper() - $mediaSourcePath = (Join-Path -Path $SourcePath -ChildPath $SourceFolder) + $mediaSourcePath = (Join-Path -Path $SourcePath -ChildPath $SourceFolder) if ($SourceCredential) { @@ -592,16 +592,16 @@ function Set-TargetResource New-VerboseMessage -Message "Robocopy is copying media from source '$mediaSourcePath' to destination '$mediaDestinationPath'" Copy-ItemWithRoboCopy -Path $mediaSourcePath -DestinationPath $mediaDestinationPath - + NetUse -SourcePath $SourcePath -Credential $SourceCredential -Ensure 'Absent' $mediaSourcePath = $mediaDestinationPath } $path = ResolvePath (Join-Path -Path $mediaSourcePath -ChildPath 'setup.exe') - + New-VerboseMessage -Message "Using path: $path" - + $sqlVersion = GetSQLVersion -Path $path # Determine features to install @@ -621,7 +621,7 @@ function Set-TargetResource $featuresToInstall += "$feature," } } - + $Features = $featuresToInstall.Trim(',') # If SQL shared components already installed, clear InstallShared*Dir variables @@ -733,29 +733,12 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey('SQLSvcAccount')) { - if ($SQLSvcAccount.UserName -eq "SYSTEM") - { - $arguments += " /SQLSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"" - } - else - { - $arguments += " /SQLSVCACCOUNT=`"" + $SQLSvcAccount.UserName + "`"" - $arguments += " /SQLSVCPASSWORD=`"" + $SQLSvcAccount.GetNetworkCredential().Password + "`"" - } + $arguments = $arguments | Join-ServiceAccountInfo -UsernameArgumentName 'SQLSVCACCOUNT' -PasswordArgumentName 'SQLSVCPASSWORD' -User $SQLSvcAccount } if($PSBoundParameters.ContainsKey('AgtSvcAccount')) { - if($AgtSvcAccount.UserName -eq 'SYSTEM') - { - $arguments += " /AGTSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"" - } - else - { - $arguments += " /AGTSVCACCOUNT=`"" + $AgtSvcAccount.UserName + "`"" - $arguments += " /AGTSVCPASSWORD=`"" + $AgtSvcAccount.GetNetworkCredential().Password + "`"" - } - + $arguments = $arguments | Join-ServiceAccountInfo -UsernameArgumentName 'AGTSVCACCOUNT' -PasswordArgumentName 'AGTSVCPASSWORD' -User $AgtSvcAccount } $arguments += ' /AGTSVCSTARTUPTYPE=Automatic' @@ -765,31 +748,15 @@ function Set-TargetResource { if ($PSBoundParameters.ContainsKey('FTSvcAccount')) { - if ($FTSvcAccount.UserName -eq 'SYSTEM') - { - $arguments += " /FTSVCACCOUNT=`"NT AUTHORITY\LOCAL SERVICE`"" - } - else - { - $arguments += " /FTSVCACCOUNT=`"" + $FTSvcAccount.UserName + "`"" - $arguments += " /FTSVCPASSWORD=`"" + $FTSvcAccount.GetNetworkCredential().Password + "`"" - } + $arguments = $arguments | Join-ServiceAccountInfo -UsernameArgumentName 'FTSVCACCOUNT' -PasswordArgumentName 'FTSVCPASSWORD' -User $FTSvcAccount } } if ($Features.Contains('RS')) { - if ($PSBoundParameters.ContainsKey("RSSvcAccount")) + if ($PSBoundParameters.ContainsKey('RSSvcAccount')) { - if ($RSSvcAccount.UserName -eq "SYSTEM") - { - $arguments += " /RSSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"" - } - else - { - $arguments += " /RSSVCACCOUNT=`"" + $RSSvcAccount.UserName + "`"" - $arguments += " /RSSVCPASSWORD=`"" + $RSSvcAccount.GetNetworkCredential().Password + "`"" - } + $arguments = $arguments | Join-ServiceAccountInfo -UsernameArgumentName 'RSSVCACCOUNT' -PasswordArgumentName 'RSSVCPASSWORD' -User $RSSvcAccount } } @@ -806,15 +773,7 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey('ASSvcAccount')) { - if($ASSvcAccount.UserName -eq 'SYSTEM') - { - $arguments += " /ASSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"" - } - else - { - $arguments += " /ASSVCACCOUNT=`"" + $ASSvcAccount.UserName + "`"" - $arguments += " /ASSVCPASSWORD=`"" + $ASSvcAccount.GetNetworkCredential().Password + "`"" - } + $arguments = $arguments | Join-ServiceAccountInfo -UsernameArgumentName 'ASSVCACCOUNT' -PasswordArgumentName 'ASSVCPASSWORD' -User $ASSvcAccount } } @@ -822,15 +781,7 @@ function Set-TargetResource { if ($PSBoundParameters.ContainsKey('ISSvcAccount')) { - if ($ISSvcAccount.UserName -eq 'SYSTEM') - { - $arguments += " /ISSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"" - } - else - { - $arguments += " /ISSVCACCOUNT=`"" + $ISSvcAccount.UserName + "`"" - $arguments += " /ISSVCPASSWORD=`"" + $ISSvcAccount.GetNetworkCredential().Password + "`"" - } + $arguments = $arguments | Join-ServiceAccountInfo -UsernameArgumentName 'ISSVCACCOUNT' -PasswordArgumentName 'ISSVCPASSWORD' -User $ISSvcAccount } } @@ -852,7 +803,7 @@ function Set-TargetResource $arguments += " `"$adminAccount`"" } } - + if ($SecurityMode -eq 'SQL') { $arguments += " /SAPwd=" + $SAPwd.GetNetworkCredential().Password @@ -922,7 +873,7 @@ function Set-TargetResource .PARAMETER SourcePath The path to the root of the source files for installation. I.e and UNC path to a shared resource. - + .PARAMETER SourceFolder Folder within the source path containing the source files for installation. Default value is 'Source'. @@ -1189,7 +1140,7 @@ function Test-TargetResource $result = $false if ($sqlData.Features ) - { + { $result = $true foreach ($feature in $Features.Split(",")) @@ -1230,10 +1181,10 @@ function GetSQLVersion <# .SYNOPSIS - Returns the first item value in the registry location provided in the Path parameter. + Returns the first item value in the registry location provided in the Path parameter. .PARAMETER Path - String containing the path to the registry. + String containing the path to the registry. #> function Get-FirstItemPropertyValue { @@ -1245,7 +1196,7 @@ function Get-FirstItemPropertyValue $Path ) - $registryProperty = Get-Item -Path $Path -ErrorAction SilentlyContinue + $registryProperty = Get-Item -Path $Path -ErrorAction SilentlyContinue if ($registryProperty) { $registryProperty = $registryProperty | Select-Object -ExpandProperty Property | Select-Object -First 1 @@ -1253,8 +1204,8 @@ function Get-FirstItemPropertyValue { $registryPropertyValue = (Get-ItemProperty -Path $Path -Name $registryProperty).$registryProperty.TrimEnd('\') } - } - + } + return $registryPropertyValue } @@ -1264,7 +1215,7 @@ function Get-FirstItemPropertyValue .PARAMETER Path Source path to be copied. - + .PARAMETER DestinationPath The path to the destination. #> @@ -1287,7 +1238,7 @@ function Copy-ItemWithRoboCopy <# .SYNOPSIS - Returns the path of the current user's temporary folder. + Returns the path of the current user's temporary folder. #> function Get-TemporaryFolder { @@ -1298,4 +1249,63 @@ function Get-TemporaryFolder return [IO.Path]::GetTempPath() } +<# + .SYNOPSIS + Returns the argument string appeneded with the account information as is given in UserAlias and User parameters +#> +function Join-ServiceAccountInfo +{ + <# + Suppressing this rule because there are parameters that contain the text 'UserName' and 'Password' + but they are not actually used to pass any credentials. Instead the parameters are used to provide the + argument that should be evaluated for setup.exe. + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] + param( + [Parameter(Mandatory, ValueFromPipeline=$true)] + [string] + $ArgumentString, + + [Parameter(Mandatory)] + [PSCredential] + $User, + + [Parameter(Mandatory)] + [string] + $UsernameArgumentName, + + [Parameter(Mandatory)] + [string] + $PasswordArgumentName + ) + + process { + + <# + Regex to determine if given username is an NT Authority account or not. + Accepted inputs are optional ntauthority with or without space between nt and authority + then a predefined list of users system, networkservice and localservice + #> + if($User.UserName.ToUpper() -match '^(NT ?AUTHORITY\\)?(SYSTEM|LOCALSERVICE|NETWORKSERVICE)$') + { + # Dealing with NT Authority user + $ArgumentString += (' /{0}="NT AUTHORITY\{1}"' -f $UsernameArgumentName, $matches[2]) + } + elseif ($User.UserName -like '*$') + { + # Dealing with Managed Service Account + $ArgumentString += (' /{0}="{1}"' -f $UsernameArgumentName, $User.UserName) + } + else + { + # Dealing with local or domain user + $ArgumentString += (' /{0}="{1}"' -f $UsernameArgumentName, $User.UserName) + $ArgumentString += (' /{0}="{1}"' -f $PasswordArgumentName, $User.GetNetworkCredential().Password) + } + + return $ArgumentString + } +} + Export-ModuleMember -Function *-TargetResource diff --git a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 index 980db1f51..d40071c50 100644 --- a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 @@ -84,7 +84,7 @@ try $mockNamedInstance_IntegrationServiceName = $mockSqlIntegrationName $mockNamedInstance_AnalysisServiceName = "$($mockSqlAnalysisName)`$$($mockNamedInstance_InstanceName)" - $mockmockSetupCredentialUserName = "COMPANY\sqladmin" + $mockmockSetupCredentialUserName = "COMPANY\sqladmin" $mockmockSetupCredentialPassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force $mockSetupCredential = New-Object System.Management.Automation.PSCredential( $mockmockSetupCredentialUserName, $mockmockSetupCredentialPassword ) @@ -110,7 +110,7 @@ try $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber = '{B5ECFA5C-AC4F-45A4-A12E-A76ABDD9CCBA}' $mockRegistryUninstallProductsPath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall' - + $mockGetItemProperty_UninstallProducts2008R2 = { return @( $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber, # Mock product SSMS 2008 and SSMS 2008 R2 @@ -146,7 +146,7 @@ try $mockGetCimInstance_DefaultInstance_DatabaseService = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_DatabaseServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -156,7 +156,7 @@ try $mockGetCimInstance_DefaultInstance_AgentService = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AgentServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockAgentServiceAccount -PassThru -Force ) @@ -166,7 +166,7 @@ try $mockGetCimInstance_DefaultInstance_FullTextService = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_FullTextServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -176,7 +176,7 @@ try $mockGetCimInstance_DefaultInstance_ReportingService = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_ReportingServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -186,7 +186,7 @@ try $mockGetCimInstance_DefaultInstance_IntegrationService = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion) -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -196,7 +196,7 @@ try $mockGetCimInstance_DefaultInstance_AnalysisService = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AnalysisServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -206,32 +206,32 @@ try $mockGetService_DefaultInstance = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_DatabaseServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AgentServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockAgentServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_FullTextServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_ReportingServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion) -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AnalysisServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -241,7 +241,7 @@ try $mockGetCimInstance_NamedInstance_DatabaseService = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_DatabaseServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -251,7 +251,7 @@ try $mockGetCimInstance_NamedInstance_AgentService = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AgentServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockAgentServiceAccount -PassThru -Force ) @@ -261,7 +261,7 @@ try $mockGetCimInstance_NamedInstance_FullTextService = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_FullTextServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -271,7 +271,7 @@ try $mockGetCimInstance_NamedInstance_ReportingService = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_ReportingServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -281,7 +281,7 @@ try $mockGetCimInstance_NamedInstance_IntegrationService = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockNamedInstance_IntegrationServiceName -f $mockSqlMajorVersion) -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -291,7 +291,7 @@ try $mockGetCimInstance_NamedInstance_AnalysisService = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AnalysisServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -301,32 +301,32 @@ try $mockGetService_NamedInstance = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_DatabaseServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AgentServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockAgentServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_FullTextServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_ReportingServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockNamedInstance_IntegrationServiceName -f $mockSqlMajorVersion) -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AnalysisServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -336,7 +336,7 @@ try $mockGetItemProperty_ConfigurationState = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'SQL_Replication_Core_Inst' -Value 1 -PassThru -Force ) ) @@ -345,12 +345,12 @@ try $mockGetItemProperty_SQL = { return @( ( - New-Object Object | - Add-Member -MemberType NoteProperty -Name $mockDefaultInstance_InstanceName -Value $mockDefaultInstance_InstanceId -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name $mockDefaultInstance_InstanceName -Value $mockDefaultInstance_InstanceId -PassThru -Force ), ( - New-Object Object | - Add-Member -MemberType NoteProperty -Name $mockNamedInstance_InstanceName -Value $mockNamedInstance_InstanceId -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name $mockNamedInstance_InstanceName -Value $mockNamedInstance_InstanceId -PassThru -Force ) ) } @@ -358,8 +358,8 @@ try $mockGetItemProperty_SharedDirectory = { return @( ( - New-Object Object | - Add-Member -MemberType NoteProperty -Name '28A1158CDF9ED6B41B2B7358982D4BA8' -Value $mockSqlSharedDirectory -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name '28A1158CDF9ED6B41B2B7358982D4BA8' -Value $mockSqlSharedDirectory -PassThru -Force ) ) } @@ -367,8 +367,8 @@ try $mockGetItem_SharedDirectory = { return @( ( - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'Property' -Value '28A1158CDF9ED6B41B2B7358982D4BA8' -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Property' -Value '28A1158CDF9ED6B41B2B7358982D4BA8' -PassThru -Force ) ) } @@ -376,8 +376,8 @@ try $mockGetItemProperty_SharedWowDirectory = { return @( ( - New-Object Object | - Add-Member -MemberType NoteProperty -Name '28A1158CDF9ED6B41B2B7358982D4BA8' -Value $mockSqlSharedWowDirectory -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name '28A1158CDF9ED6B41B2B7358982D4BA8' -Value $mockSqlSharedWowDirectory -PassThru -Force ) ) } @@ -385,8 +385,8 @@ try $mockGetItem_SharedWowDirectory = { return @( ( - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'Property' -Value '28A1158CDF9ED6B41B2B7358982D4BA8' -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Property' -Value '28A1158CDF9ED6B41B2B7358982D4BA8' -PassThru -Force ) ) } @@ -394,7 +394,7 @@ try $mockGetItemProperty_Setup = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'SqlProgramDir' -Value $mockSqlProgramDirectory -PassThru -Force ) ) @@ -403,7 +403,7 @@ try $mockGetItemProperty_ServicesAnalysis = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'ImagePath' -Value ('"C:\Program Files\Microsoft SQL Server\OLAP\bin\msmdsrv.exe" -s "{0}"' -f $mockSqlAnalysisConfigDirectory) -PassThru -Force ) ) @@ -412,7 +412,7 @@ try $mockConnectSQL = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'LoginMode' -Value $mockSqlLoginMode -PassThru | Add-Member -MemberType NoteProperty -Name 'Collation' -Value $mockSqlCollation -PassThru | Add-Member -MemberType NoteProperty -Name 'InstallDataDirectory' -Value $mockSqlInstallPath -PassThru | @@ -423,7 +423,7 @@ try Add-Member -MemberType NoteProperty -Name 'DefaultLog' -Value $mockSqlDefaultDatabaseLogPath -PassThru | Add-Member ScriptProperty Logins { return @( ( New-Object Object | - Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockSqlSystemAdministrator -PassThru | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockSqlSystemAdministrator -PassThru | Add-Member ScriptMethod ListMembers { return @('sysadmin') } -PassThru -Force @@ -436,7 +436,7 @@ try $mockConnectSQLAnalysis = { return @( ( - New-Object Object | + New-Object Object | Add-Member ScriptProperty ServerProperties { return @{ 'CollationName' = @( New-Object Object | Add-Member NoteProperty -Name 'Value' -Value $mockSqlAnalysisCollation -PassThru -Force ) @@ -445,7 +445,7 @@ try 'LogDir' = @( New-Object Object | Add-Member NoteProperty -Name 'Value' -Value $mockSqlAnalysisLogDirectory -PassThru -Force ) 'BackupDir' = @( New-Object Object | Add-Member NoteProperty -Name 'Value' -Value $mockSqlAnalysisBackupDirectory -PassThru -Force ) } - } -PassThru | + } -PassThru | Add-Member ScriptProperty Roles { return @{ 'Administrators' = @( New-Object Object | @@ -466,11 +466,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 + 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 diffentent 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). - #> + #> $mockStartWin32ProcessExpectedArgument = '' $mockStartWin32Process = { if ( $Arguments -ne $mockStartWin32ProcessExpectedArgument ) @@ -481,7 +481,7 @@ try return 'Process started' } #endregion Function mocks - + # Default parameters that are used for the It-blocks $mockDefaultParameters = @{ SetupCredential = $mockSetupCredential @@ -495,7 +495,7 @@ try # Local path to TestDrive:\ $mockSourcePath = $TestDrive.FullName $mockSqlMediaPath = Join-Path -Path $mockSourcePath -ChildPath $mockSourceFolder - + # UNC path to TestDrive:\ $testDrive_DriveShare = (Split-Path -Path $mockSourcePath -Qualifier) -replace ':','$' $mockSourcePathUNC = Join-Path -Path "\\localhost\$testDrive_DriveShare" -ChildPath (Split-Path -Path $mockSourcePath -NoQualifier) @@ -504,7 +504,7 @@ try # Mocking folder structure and mocking setup.exe New-Item -Path $mockSqlMediaPath -ItemType Directory Set-Content (Join-Path -Path $mockSqlMediaPath -ChildPath 'setup.exe') -Value 'Mock exe file' - + #endregion Setting up TestDrive:\ BeforeEach { @@ -512,40 +512,40 @@ try Mock -CommandName GetSQLVersion -MockWith $mockGetSQLVersion -Verifiable Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable Mock -CommandName Connect-SQLAnalysis -MockWith $mockConnectSQLAnalysis -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName -or $Name -eq $mockNamedInstance_InstanceName) - } -MockWith $mockGetItemProperty_SQL -Verifiable + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName -or $Name -eq $mockNamedInstance_InstanceName) + } -MockWith $mockGetItemProperty_SQL -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { ( $Path -eq "HKLM:\SYSTEM\CurrentControlSet\Services\$mockDefaultInstance_AnalysisServiceName" -or $Path -eq "HKLM:\SYSTEM\CurrentControlSet\Services\$mockNamedInstance_AnalysisServiceName" - ) -and - $Name -eq 'ImagePath' - } -MockWith $mockGetItemProperty_ServicesAnalysis -Verifiable + ) -and + $Name -eq 'ImagePath' + } -MockWith $mockGetItemProperty_ServicesAnalysis -Verifiable # Mocking SharedDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable + } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable - Mock -CommandName Get-Item -ParameterFilter { + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItem_SharedDirectory -Verifiable + } -MockWith $mockGetItem_SharedDirectory -Verifiable # Mocking SharedWowDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable + } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable - Mock -CommandName Get-Item -ParameterFilter { + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItem_SharedWowDirectory -Verifiable + } -MockWith $mockGetItem_SharedWowDirectory -Verifiable } $testProductVersion | ForEach-Object -Process { @@ -572,7 +572,7 @@ try if ($mockSqlMajorVersion -eq 13) { # Mock all SSMS products here to make sure we don't return any when testing SQL Server 2016 - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or @@ -581,7 +581,7 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable } else { - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or @@ -596,17 +596,17 @@ try Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return the same values as passed as parameters' { $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - + Assert-MockCalled -CommandName NetUse -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 @@ -676,10 +676,10 @@ try SourceCredential = $mockSetupCredential SourcePath = $mockSourcePathUNC } - + if ($mockSqlMajorVersion -eq 13) { # Mock all SSMS products here to make sure we don't return any when testing SQL Server 2016 - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or @@ -688,7 +688,7 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable } else { - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or @@ -702,17 +702,17 @@ try Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return the same values as passed as parameters' { $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - + Assert-MockCalled -CommandName NetUse -Exactly -Times 2 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It @@ -784,21 +784,21 @@ try } if ($mockSqlMajorVersion -eq 10) { - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts2008R2 -Verifiable } if ($mockSqlMajorVersion -eq 11) { - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts2012 -Verifiable } if ($mockSqlMajorVersion -eq 12) { - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable @@ -808,32 +808,32 @@ try Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable #region Mock Get-CimInstance - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable @@ -843,20 +843,20 @@ try throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" } #endregion Mock Get-CimInstance - + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return the same values as passed as parameters' { $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - + Assert-MockCalled -CommandName NetUse -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It @@ -886,36 +886,36 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 2 -Scope It + } -Exactly -Times 2 -Scope It } #region Assert Get-CimInstance - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" } -Exactly -Times 1 -Scope It @@ -973,21 +973,21 @@ try } if ($mockSqlMajorVersion -eq 10) { - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts2008R2 -Verifiable } if ($mockSqlMajorVersion -eq 11) { - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts2012 -Verifiable } if ($mockSqlMajorVersion -eq 12) { - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable @@ -995,34 +995,34 @@ try Mock -CommandName NetUse -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable - + #region Mock Get-CimInstance - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable @@ -1035,17 +1035,17 @@ try Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return the same values as passed as parameters' { $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - + Assert-MockCalled -CommandName NetUse -Exactly -Times 2 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It @@ -1075,36 +1075,36 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 2 -Scope It + } -Exactly -Times 2 -Scope It } #region Assert Get-CimInstance - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" } -Exactly -Times 1 -Scope It @@ -1159,7 +1159,7 @@ try $mockSqlTempDatabaseLogPath = '' $mockSqlDefaultDatabaseFilePath = "C:\Program Files\Microsoft SQL Server\$($mockNamedInstance_InstanceId)\MSSQL\DATA\" $mockSqlDefaultDatabaseLogPath = "C:\Program Files\Microsoft SQL Server\$($mockNamedInstance_InstanceId)\MSSQL\DATA\" - + Context "When SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for named instance" { BeforeEach { $testParameters = $mockDefaultParameters.Clone() @@ -1172,7 +1172,7 @@ try if ($mockSqlMajorVersion -eq 13) { # Mock this here to make sure we don't return any older components (<=2014) when testing SQL Server 2016 - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or @@ -1181,7 +1181,7 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable } else { - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or @@ -1195,17 +1195,17 @@ try Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return the same values as passed as parameters' { $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - + 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 @@ -1276,21 +1276,21 @@ try } if ($mockSqlMajorVersion -eq 10) { - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts2008R2 -Verifiable } if ($mockSqlMajorVersion -eq 11) { - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts2012 -Verifiable } if ($mockSqlMajorVersion -eq 12) { - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable @@ -1299,32 +1299,32 @@ try Mock -CommandName Get-Service -MockWith $mockGetService_NamedInstance -Verifiable #region Mock Get-CimInstance - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockNamedInstance_DatabaseServiceName'" } -MockWith $mockGetCimInstance_NamedInstance_DatabaseService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockNamedInstance_AgentServiceName'" } -MockWith $mockGetCimInstance_NamedInstance_AgentService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockNamedInstance_FullTextServiceName'" } -MockWith $mockGetCimInstance_NamedInstance_FullTextService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockNamedInstance_ReportingServiceName'" } -MockWith $mockGetCimInstance_NamedInstance_ReportingService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$(($mockNamedInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" } -MockWith $mockGetCimInstance_NamedInstance_IntegrationService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockNamedInstance_AnalysisServiceName'" } -MockWith $mockGetCimInstance_NamedInstance_AnalysisService -Verifiable @@ -1337,17 +1337,17 @@ try Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return the same values as passed as parameters' { $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -1376,36 +1376,36 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 2 -Scope It + } -Exactly -Times 2 -Scope It } #region Assert Get-CimInstance - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockNamedInstance_DatabaseServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockNamedInstance_AgentServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockNamedInstance_FullTextServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockNamedInstance_ReportingServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$(($mockNamedInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockNamedInstance_AnalysisServiceName'" } -Exactly -Times 1 -Scope It @@ -1455,14 +1455,14 @@ try Assert-VerifiableMocks } - + Describe "xSQLServerSetup\Test-TargetResource" -Tag 'Test' { #region Setting up TestDrive:\ # Local path to TestDrive:\ $mockSourcePath = $TestDrive.FullName $mockSqlMediaPath = Join-Path -Path $mockSourcePath -ChildPath $mockSourceFolder - + # UNC path to TestDrive:\ $testDrive_DriveShare = (Split-Path -Path $mockSourcePath -Qualifier) -replace ':','$' $mockSourcePathUNC = Join-Path -Path "\\localhost\$testDrive_DriveShare" -ChildPath (Split-Path -Path $mockSourcePath -NoQualifier) @@ -1471,7 +1471,7 @@ try # Mocking folder structure and mocking setup.exe New-Item -Path $mockSqlMediaPath -ItemType Directory Set-Content (Join-Path -Path $mockSqlMediaPath -ChildPath 'setup.exe') -Value 'Mock exe file' - + #endregion Setting up TestDrive:\ BeforeEach { @@ -1479,40 +1479,40 @@ try Mock -CommandName GetSQLVersion -MockWith $mockGetSQLVersion -Verifiable Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable Mock -CommandName Connect-SQLAnalysis -MockWith $mockConnectSQLAnalysis -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName -or $Name -eq $mockNamedInstance_InstanceName) - } -MockWith $mockGetItemProperty_SQL -Verifiable + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName -or $Name -eq $mockNamedInstance_InstanceName) + } -MockWith $mockGetItemProperty_SQL -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { ( $Path -eq "HKLM:\SYSTEM\CurrentControlSet\Services\$mockDefaultInstance_AnalysisServiceName" -or $Path -eq "HKLM:\SYSTEM\CurrentControlSet\Services\$mockNamedInstance_AnalysisServiceName" - ) -and - $Name -eq 'ImagePath' - } -MockWith $mockGetItemProperty_ServicesAnalysis -Verifiable + ) -and + $Name -eq 'ImagePath' + } -MockWith $mockGetItemProperty_ServicesAnalysis -Verifiable # Mocking SharedDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable + } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable - Mock -CommandName Get-Item -ParameterFilter { + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItem_SharedDirectory -Verifiable + } -MockWith $mockGetItem_SharedDirectory -Verifiable # Mocking SharedWowDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable + } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable - Mock -CommandName Get-Item -ParameterFilter { + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItem_SharedWowDirectory -Verifiable + } -MockWith $mockGetItem_SharedWowDirectory -Verifiable } # For this test we only need to test one SQL Server version @@ -1537,7 +1537,7 @@ try } # Mock all SSMS products here to make sure we don't return any when testing SQL Server 2016 - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or @@ -1545,14 +1545,14 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable - + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return that the desired state is absent when no products are installed' { @@ -1562,7 +1562,7 @@ try $result = Test-TargetResource @testParameters $result| Should Be $false - + 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 @@ -1590,32 +1590,32 @@ try Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable #region Mock Get-CimInstance - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable @@ -1631,7 +1631,7 @@ try $result = Test-TargetResource @testParameters $result| Should Be $false - + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -1653,32 +1653,32 @@ try } -Exactly -Times 6 -Scope It #region Assert Get-CimInstance - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" } -Exactly -Times 1 -Scope It @@ -1689,32 +1689,32 @@ try Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable #region Mock Get-CimInstance - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable @@ -1730,7 +1730,7 @@ try $result = Test-TargetResource @testParameters $result| Should Be $false - + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -1752,32 +1752,32 @@ try } -Exactly -Times 6 -Scope It #region Assert Get-CimInstance - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" } -Exactly -Times 1 -Scope It @@ -1794,17 +1794,17 @@ try SourcePath = $mockSourcePath } - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts2008R2 -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts2012 -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable @@ -1812,32 +1812,32 @@ try Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable #region Mock Get-CimInstance - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable - Mock -CommandName Get-CimInstance -ParameterFilter { + Mock -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable @@ -1850,17 +1850,17 @@ try Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return that the desired state is present' { $result = Test-TargetResource @testParameters $result| Should Be $true - + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -1882,39 +1882,39 @@ try } -Exactly -Times 6 -Scope It #region Assert Get-CimInstance - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_Service' -and $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" } -Exactly -Times 1 -Scope It #endregion Assert Get-CimInstance } } - + Assert-VerifiableMocks } @@ -1924,7 +1924,7 @@ try # Local path to TestDrive:\ $mockSourcePath = $TestDrive.FullName $mockSqlMediaPath = Join-Path -Path $mockSourcePath -ChildPath $mockSourceFolder - + # UNC path to TestDrive:\ $testDrive_DriveShare = (Split-Path -Path $mockSourcePath -Qualifier) -replace ':','$' $mockSourcePathUNC = Join-Path -Path "\\localhost\$testDrive_DriveShare" -ChildPath (Split-Path -Path $mockSourcePath -NoQualifier) @@ -1933,7 +1933,7 @@ try # Mocking folder structure and mocking setup.exe New-Item -Path $mockSqlMediaPath -ItemType Directory Set-Content (Join-Path -Path $mockSqlMediaPath -ChildPath 'setup.exe') -Value 'Mock exe file' - + #endregion Setting up TestDrive:\ BeforeEach { @@ -1941,40 +1941,40 @@ try Mock -CommandName GetSQLVersion -MockWith $mockGetSQLVersion -Verifiable Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable Mock -CommandName Connect-SQLAnalysis -MockWith $mockConnectSQLAnalysis -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName -or $Name -eq $mockNamedInstance_InstanceName) - } -MockWith $mockGetItemProperty_SQL -Verifiable + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName -or $Name -eq $mockNamedInstance_InstanceName) + } -MockWith $mockGetItemProperty_SQL -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { ( $Path -eq "HKLM:\SYSTEM\CurrentControlSet\Services\$mockDefaultInstance_AnalysisServiceName" -or $Path -eq "HKLM:\SYSTEM\CurrentControlSet\Services\$mockNamedInstance_AnalysisServiceName" - ) -and - $Name -eq 'ImagePath' - } -MockWith $mockGetItemProperty_ServicesAnalysis -Verifiable + ) -and + $Name -eq 'ImagePath' + } -MockWith $mockGetItemProperty_ServicesAnalysis -Verifiable # Mocking SharedDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable + } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable - Mock -CommandName Get-Item -ParameterFilter { + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItem_SharedDirectory -Verifiable + } -MockWith $mockGetItem_SharedDirectory -Verifiable # Mocking SharedWowDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable + } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable - Mock -CommandName Get-Item -ParameterFilter { + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItem_SharedWowDirectory -Verifiable + } -MockWith $mockGetItem_SharedWowDirectory -Verifiable Mock -CommandName StartWin32Process -MockWith $mockStartWin32Process -Verifiable Mock -CommandName WaitForWin32ProcessEnd -Verifiable @@ -2009,7 +2009,7 @@ try Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or @@ -2017,12 +2017,12 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockEmptyHashtable -Verifiable - + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable } It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = + $mockStartWin32ProcessExpectedArgument = '/Quiet="True"', '/IAcceptSQLServerLicenseTerms="True"', '/Action="Install"', @@ -2033,16 +2033,16 @@ try '/ASSysAdminAccounts="COMPANY\sqladmin"' -join ' ' { Set-TargetResource @testParameters } | Should Not Throw - + Assert-MockCalled -CommandName NetUse -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-TemporaryFolder -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 - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It @@ -2078,7 +2078,7 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = + $mockStartWin32ProcessExpectedArgument = '/Quiet="True"', '/IAcceptSQLServerLicenseTerms="True"', '/Action="Install"', @@ -2086,13 +2086,13 @@ try '/Features="SSMS"' -join ' ' { Set-TargetResource @testParameters } | Should Not Throw - + 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 - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It @@ -2113,7 +2113,7 @@ try It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = + $mockStartWin32ProcessExpectedArgument = '/Quiet="True"', '/IAcceptSQLServerLicenseTerms="True"', '/Action="Install"', @@ -2121,13 +2121,13 @@ try '/Features="ADV_SSMS"' -join ' ' { Set-TargetResource @testParameters } | Should Not Throw - + 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 - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It @@ -2163,7 +2163,7 @@ try Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or @@ -2174,7 +2174,7 @@ try } It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = + $mockStartWin32ProcessExpectedArgument = '/Quiet="True"', '/IAcceptSQLServerLicenseTerms="True"', '/Action="Install"', @@ -2185,16 +2185,16 @@ try '/ASSysAdminAccounts="COMPANY\sqladmin"' -join ' ' { Set-TargetResource @testParameters } | Should Not Throw - + Assert-MockCalled -CommandName NetUse -Exactly -Times 4 -Scope It Assert-MockCalled -CommandName Get-TemporaryFolder -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 - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It @@ -2231,7 +2231,7 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = + $mockStartWin32ProcessExpectedArgument = '/Quiet="True"', '/IAcceptSQLServerLicenseTerms="True"', '/Action="Install"', @@ -2239,13 +2239,13 @@ try '/Features="SSMS"' -join ' ' { Set-TargetResource @testParameters } | Should Not Throw - + 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 - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It @@ -2267,7 +2267,7 @@ try It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = + $mockStartWin32ProcessExpectedArgument = '/Quiet="True"', '/IAcceptSQLServerLicenseTerms="True"', '/Action="Install"', @@ -2275,12 +2275,12 @@ try '/Features="ADV_SSMS"' -join ' ' { Set-TargetResource @testParameters } | Should Not Throw - + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It @@ -2318,7 +2318,7 @@ try SourcePath = $mockSourcePath } - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or @@ -2332,7 +2332,7 @@ try } It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = + $mockStartWin32ProcessExpectedArgument = '/Quiet="True"', '/IAcceptSQLServerLicenseTerms="True"', '/Action="Install"', @@ -2343,13 +2343,13 @@ try '/ASSysAdminAccounts="COMPANY\sqladmin"' -join ' ' { Set-TargetResource @testParameters } | Should Not Throw - + 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 - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It @@ -2386,7 +2386,7 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = + $mockStartWin32ProcessExpectedArgument = '/Quiet="True"', '/IAcceptSQLServerLicenseTerms="True"', '/Action="Install"', @@ -2394,13 +2394,13 @@ try '/Features="SSMS"' -join ' ' { Set-TargetResource @testParameters } | Should Not Throw - + 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 - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It @@ -2421,7 +2421,7 @@ try It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = + $mockStartWin32ProcessExpectedArgument = '/Quiet="True"', '/IAcceptSQLServerLicenseTerms="True"', '/Action="Install"', @@ -2429,12 +2429,12 @@ try '/Features="ADV_SSMS"' -join ' ' { Set-TargetResource @testParameters } | Should Not Throw - + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName) } -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It @@ -2454,9 +2454,57 @@ try } } } - + Assert-VerifiableMocks } + + Describe 'Join-ServiceAccountInfo' -Tag 'Helper' { + Context 'When called it should return a string with service account information appended to the original argument string' { + + $Params = @{ UsernameArgumentname = 'SQLSVCACCOUNT'; PasswordArgumentName = 'SQLSVCPASSWORD'; ArgumentString = "C:\Install\SQLSource\setup.exe" } + + $mockSetupDomainCredential = New-Object System.Management.Automation.PSCredential( 'Company\SQLServer', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) + $mockSetupMSACredential = New-Object System.Management.Automation.PSCredential( 'Company\SQLServer$', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) + $mockSetupSystemCredential = New-Object System.Management.Automation.PSCredential( 'SYSTEM', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) + $mockSetupLocalServiceCredential = New-Object System.Management.Automation.PSCredential( 'LOCALSERVICE', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) + $mockSetupNetworkServiceCredential = New-Object System.Management.Automation.PSCredential( 'NETWORKSERVICE', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) + $mockSetupNTSystemCredential = New-Object System.Management.Automation.PSCredential( 'NT AUTHORITY\SYSTEM', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) + $mockSetupNTLocalServiceCredential = New-Object System.Management.Automation.PSCredential( 'NT AUTHORITY\LOCALSERVICE', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) + $mockSetupNTNetworkServiceCredential = New-Object System.Management.Automation.PSCredential( 'NT AUTHORITY\NETWORKSERVICE', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) + + It 'Should return string with service account information and password appended Domain/Local Account' { + Join-ServiceAccountInfo @Params -User $mockSetupDomainCredential | Should BeExactly ('{0} /{1}="{2}" /{3}="{4}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupDomainCredential.UserName, $Params['PasswordArgumentname'], $mockSetupDomainCredential.GetNetworkCredential().Password) + } + + It 'Should return string service account information and no password appended for Managed Service Account' { + Join-ServiceAccountInfo @Params -User $mockSetupMSACredential | Should BeExactly ('{0} /{1}="{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupMSACredential.UserName) + } + + It 'Should return string service account information and no password appended for NT AUTHORITY\SYSTEM. Test without NT AUTHORITY prepended' { + Join-ServiceAccountInfo @Params -User $mockSetupSystemCredential | Should BeExactly ('{0} /{1}="NT AUTHORITY\{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupSystemCredential.UserName) + } + + It 'Should return string service account information and no password appended for NT AUTHORITY\LOCALSERVICE. Test without NT AUTHORITY prepended' { + Join-ServiceAccountInfo @Params -User $mockSetupLocalServiceCredential | Should BeExactly ('{0} /{1}="NT AUTHORITY\{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupLocalServiceCredential.UserName) + } + + It 'Should return string service account information and no password appended for NT AUTHORITY\NETWORKSERVICE. Test without NT AUTHORITY prepended' { + Join-ServiceAccountInfo @Params -User $mockSetupNetworkServiceCredential | Should BeExactly ('{0} /{1}="NT AUTHORITY\{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupNetworkServiceCredential.UserName) + } + + It 'Should return string service account information and no password appended for NT AUTHORITY\SYSTEM. Test with NT AUTHORITY prepended' { + Join-ServiceAccountInfo @Params -User $mockSetupNTSystemCredential | Should BeExactly ('{0} /{1}="{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupNTSystemCredential.UserName) + } + + It 'Should return string service account information and no password appended for NT AUTHORITY\LOCALSERVICE. Test with NT AUTHORITY prepended' { + Join-ServiceAccountInfo @Params -User $mockSetupNTLocalServiceCredential | Should BeExactly ('{0} /{1}="{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupNTLocalServiceCredential.UserName) + } + + It 'Should return string service account information and no password appended for NT AUTHORITY\NETWORKSERVICE. Test with NT AUTHORITY prepended' { + Join-ServiceAccountInfo @Params -User $mockSetupNTNetworkServiceCredential | Should BeExactly ('{0} /{1}="{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupNTNetworkServiceCredential.UserName) + } + } + } } } finally From 49d5e4e18f4ebf4601c4ef3b803d9e6da76d9433 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 6 Jan 2017 10:11:49 +0100 Subject: [PATCH 09/31] xSQLServerSetup: Changes to the helper function Copy-ItemWithRoboCopy (#282) - Changes to function Copy-ItemWithRoboCopy - Robocopy is now started using Start-Process and the error handling has been improved. - Robocopy now removes files at the destination path if they no longer exists at the source. - Robocopy copies using unbuffered I/O when available (recommended for large files). - Changes to xSQLServerSetup.Tests - Added test coverage for Copy-ItemWithRoboCopy - Changes to xSQLServerSetup - Added a more descriptive text for the parameter SourceCredential to further explain how the parameter work. --- CHANGELOG.md | 8 + .../MSFT_xSQLServerSetup.psm1 | 81 ++++++- .../MSFT_xSQLServerSetup.schema.mof | 2 +- README.md | 2 +- Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 | 214 +++++++++++++++++- 5 files changed, 292 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf1618244..090427001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,12 +19,20 @@ - Now it uses CIM cmdlets to get information from WMI classes. - Resolved all of the PSScriptAnalyzer warnings that was triggered in the common tests. - Improvement for service accounts to enable support for Managed Service Accounts as well as other nt authority accounts + - Changes to the helper function Copy-ItemWithRoboCopy + - Robocopy is now started using Start-Process and the error handling has been improved. + - Robocopy now removes files at the destination path if they no longer exists at the source. + - Robocopy copies using unbuffered I/O when available (recommended for large files). + - Added a more descriptive text for the parameter `SourceCredential` to further explain how the parameter work. - Changes to xSQLServerScript - All credential parameters now also has the type [System.Management.Automation.Credential()] to better work with PowerShell 4.0. - It is now possible to configure two instances on the same node, with the same script. - Added to the description text for the parameter `Credential` describing how to authenticate using Windows Authentication. - Added examples to show how to authenticate using either SQL or Windows authentication. - A recent issue showed that there is a known problem running this resource using PowerShell 4.0. For more information, see [issue #273](https://github.com/PowerShell/xSQLServer/issues/273) +- Changes to the unit test for resource + - xSQLServerSetup + - Added test coverage for helper function Copy-ItemWithRoboCopy ## 4.0.0.0 diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 index 5b6083f96..7a318a1ef 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 @@ -16,7 +16,8 @@ Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $script Credential to be used to perform the installation. .PARAMETER SourceCredential - Credential to be used to access SourcePath. + Credentials used to access the path set in the parameter `SourcePath` and `SourceFolder`. The parameter `SourceCredential` is used + to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath` and `SourceFolder`. .PARAMETER InstanceName Name of the SQL instance to be installed. @@ -322,7 +323,10 @@ function Get-TargetResource Credential to be used to perform the installation. .PARAMETER SourceCredential - Credential to be used to access SourcePath. + Credentials used to access the path set in the parameter `SourcePath` and `SourceFolder`. Using this parameter will trigger a copy + of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. + For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' + has in the path set, again, by the parameter `SourcePath` and `SourceFolder`. .PARAMETER SuppressReboot Suppressed reboot. @@ -881,7 +885,8 @@ function Set-TargetResource Credential to be used to perform the installation. .PARAMETER SourceCredential - Credential to be used to access SourcePath. + Credentials used to access the path set in the parameter `SourcePath` and `SourceFolder`. The parameter `SourceCredential` is used + to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath` and `SourceFolder`. .PARAMETER SuppressReboot Suppresses reboot. @@ -1224,16 +1229,80 @@ function Copy-ItemWithRoboCopy [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $Path, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $DestinationPath ) - & robocopy.exe $Path $DestinationPath /e + $robocopyExecutable = Get-Command -Name "Robocopy.exe" -ErrorAction Stop + + $robocopyArgumentSilent = '/njh /njs /ndl /nc /ns /nfl' + $robocopyArgumentCopySubDirectoriesIncludingEmpty = '/e' + $robocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource = '/purge' + + if ([System.Version]$robocopyExecutable.FileVersionInfo.ProductVersion -ge [System.Version]'6.3.9600.16384') + { + Write-Verbose "Robocopy is using unbuffered I/O." + $robocopyArgumentUseUnbufferedIO = '/J' + } + else + { + Write-Verbose 'Unbuffered I/O cannot be used due to incompatible version of Robocopy.' + } + + $robocopyArgumentList = '{0} {1} {2} {3} {4} {5}' -f $Path, + $DestinationPath, + $robocopyArgumentCopySubDirectoriesIncludingEmpty, + $robocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource, + $robocopyArgumentUseUnbufferedIO, + $robocopyArgumentSilent + + $robocopyStartProcessParameters = @{ + FilePath = $robocopyExecutable.Name + ArgumentList = $robocopyArgumentList + } + + Write-Verbose ('Robocopy is started with the following arguments: {0}' -f $robocopyArgumentList ) + $robocopyProcess = Start-Process @robocopyStartProcessParameters -Wait -NoNewWindow -PassThru + + switch ($($robocopyProcess.ExitCode)) + { + {$_ -in 8, 16} + { + throw "Robocopy reported errors when copying files. Error code: $_." + } + + {$_ -gt 7 } + { + throw "Robocopy reported that failures occured when copying files. Error code: $_." + } + + 1 + { + Write-Verbose 'Robocopy copied files sucessfully' + } + + 2 + { + Write-Verbose 'Robocopy found files at the destination path that is not present at the source path, these extra files was remove at the destination path.' + } + + 3 + { + Write-Verbose 'Robocopy copied files to destination sucessfully. Robocopy also found files at the destination path that is not present at the source path, these extra files was remove at the destination path.' + } + + {$_ -eq 0 -or $null -eq $_ } + { + Write-Verbose 'Robocopy reported that all files already present.' + } + } } <# diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof index 36275b7c6..1fe6a7db4 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof @@ -4,7 +4,7 @@ class MSFT_xSQLServerSetup : OMI_BaseResource [Write, Description("The path to the root of the source files for installation. I.e and UNC path to a shared resource.")] String SourcePath; [Write, Description("Folder within the source path containing the source files for installation. Default value is 'Source'.")] String SourceFolder; [Required, EmbeddedInstance("MSFT_Credential"), Description("Credential to be used to perform the installation.")] String SetupCredential; - [Write, EmbeddedInstance("MSFT_Credential"), Description("Credential to be used to access SourcePath.")] String SourceCredential; + [Write, EmbeddedInstance("MSFT_Credential"), Description("Credentials used to access the path set in the parameter 'SourcePath' and 'SourceFolder'. Using this parameter will trigger a copy of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. For any subsequent calls to the resource, the parameter 'SourceCredential' is used to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter 'SourcePath' and 'SourceFolder'.")] String SourceCredential; [Write, Description("Suppresses reboot.")] Boolean SuppressReboot; [Write, Description("Forces reboot.")] Boolean ForceReboot; [Write, Description("SQL features to be installed.")] String Features; diff --git a/README.md b/README.md index cde18c2c5..4b2096f2f 100644 --- a/README.md +++ b/README.md @@ -369,7 +369,7 @@ _Note: There is a known problem running this resource using PowerShell 4.0. See * **SourcePath**: (Required) The path to the root of the source files for installation. I.e and UNC path to a shared resource. * **SourceFolder**: Folder within the source path containing the source files for installation. Default value is 'Source'. * **SetupCredential**: (Required) Credential to be used to perform the installation. -* **SourceCredential**: Credential used to access SourcePath. +* **SourceCredential**: Credentials used to access the path set in the parameter `SourcePath` and `SourceFolder`. Using this parameter will trigger a copy of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath` and `SourceFolder`. To know how the temp folder is evaluated please read the online documentation for [System.IO.Path.GetTempPath()](https://msdn.microsoft.com/en-us/library/system.io.path.gettemppath(v=vs.110).aspx). * **SuppressReboot**: Suppresses reboot. * **ForceReboot**: Forces reboot. * **Features**: (Key) SQL features to be installed. diff --git a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 index d40071c50..87d4f8db9 100644 --- a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 @@ -461,17 +461,60 @@ try ) } + $mockRobocopyExecutableName = 'Robocopy.exe' + $mockRobocopyExectuableVersionWithoutUnbufferedIO = '6.2.9200.00000' + $mockRobocopyExectuableVersionWithUnbufferedIO = '6.3.9600.16384' + $mockRobocopyExectuableVersion = '' # Set dynamically during runtime + $mockRobocopyArgumentSilent = '/njh /njs /ndl /nc /ns /nfl' + $mockRobocopyArgumentCopySubDirectoriesIncludingEmpty = '/e' + $mockRobocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource = '/purge' + $mockRobocopyArgumentUseUnbufferedIO = '/J' + $mockRobocopyArgumentSourcePath = 'C:\Source\SQL2016' + $mockRobocopyArgumentDestinationPath = 'D:\Temp' + + $mockGetCommand = { + return @( + ( + New-Object Object | + 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 + ) ) + } -PassThru -Force + ) + ) + } + + $mockStartProcessExpectedArgument = '' # Set dynamically during runtime + $mockStartProcessExitCode = 0 # Set dynamically during runtime + + $mockStartProcess = { + if ( $ArgumentList -cne $mockStartProcessExpectedArgument ) + { + throw "Expected arguments was not the same as the arguments in the function call.`nExpected: '$mockStartProcessExpectedArgument' `n But was: '$ArgumentList'" + } + + return New-Object Object | + Add-Member -MemberType NoteProperty -Name 'ExitCode' -Value 0 -PassThru -Force + } + + $mockStartProcess_WithExitCode = { + return New-Object Object | + Add-Member -MemberType NoteProperty -Name 'ExitCode' -Value $mockStartProcessExitCode -PassThru -Force + } + $mockGetTemporaryFolder = { return $mockSourcePathUNC } <# 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 diffentent from the expected arguments. + 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). #> - $mockStartWin32ProcessExpectedArgument = '' + $mockStartWin32ProcessExpectedArgument = '' # Set dynamically during runtime $mockStartWin32Process = { if ( $Arguments -ne $mockStartWin32ProcessExpectedArgument ) { @@ -2005,7 +2048,7 @@ try } Mock -CommandName NetUse -Verifiable - Mock -CommandName Copy-ItemWithRoboCopy -Verifiable + Mock -CommandName Start-Process -Verifiable Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable @@ -2036,7 +2079,7 @@ try Assert-MockCalled -CommandName NetUse -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-TemporaryFolder -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Copy-ItemWithRoboCopy -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Start-Process -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 @@ -2158,7 +2201,7 @@ try } Mock -CommandName NetUse -Verifiable - Mock -CommandName Copy-ItemWithRoboCopy -Verifiable + Mock -CommandName Start-Process -Verifiable Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable @@ -2188,7 +2231,7 @@ try Assert-MockCalled -CommandName NetUse -Exactly -Times 4 -Scope It Assert-MockCalled -CommandName Get-TemporaryFolder -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Copy-ItemWithRoboCopy -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -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 @@ -2505,7 +2548,164 @@ 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' { + BeforeEach { + Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartProcess -Verifiable + } + + + It 'Should use Unbuffered IO when copying' { + $mockRobocopyExectuableVersion = $mockRobocopyExectuableVersionWithUnbufferedIO + + $mockStartProcessExpectedArgument = + $mockRobocopyArgumentSourcePath, + $mockRobocopyArgumentDestinationPath, + $mockRobocopyArgumentCopySubDirectoriesIncludingEmpty, + $mockRobocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource, + $mockRobocopyArgumentUseUnbufferedIO, + $mockRobocopyArgumentSilent -join ' ' + + $copyItemWithRoboCopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { 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 + + $mockStartProcessExpectedArgument = + $mockRobocopyArgumentSourcePath, + $mockRobocopyArgumentDestinationPath, + $mockRobocopyArgumentCopySubDirectoriesIncludingEmpty, + $mockRobocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource, + '', + $mockRobocopyArgumentSilent -join ' ' + + $copyItemWithRoboCopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { 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' { + BeforeEach { + $mockRobocopyExectuableVersion = $mockRobocopyExectuableVersionWithUnbufferedIO + + Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartProcess_WithExitCode -Verifiable + } + + It 'Should throw the correct error message when error code is 8' { + $mockStartProcessExitCode = 8 + + $copyItemWithRoboCopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { 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 + } + + It 'Should throw the correct error message when error code is 16' { + $mockStartProcessExitCode = 16 + + $copyItemWithRoboCopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { 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 + } + + It 'Should throw the correct error message when error code is greater than 7 (but not 8 or 16)' { + $mockStartProcessExitCode = 9 + + $copyItemWithRoboCopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { 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' { + BeforeEach { + $mockRobocopyExectuableVersion = $mockRobocopyExectuableVersionWithUnbufferedIO + + Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartProcess_WithExitCode -Verifiable + } + + It 'Should finish succesfully with exit code 1' { + $mockStartProcessExitCode = 1 + + $copyItemWithRoboCopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { 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' { + $mockStartProcessExitCode = 2 + + $copyItemWithRoboCopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { 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' { + $mockStartProcessExitCode = 3 + + $copyItemWithRoboCopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { 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 + } + } + } + } } finally { From eacf5d2e8444883d8dcd3420b08fd7dbf87c1916 Mon Sep 17 00:00:00 2001 From: Dan Reist Date: Fri, 6 Jan 2017 14:09:09 -0500 Subject: [PATCH 10/31] xSQLServerLogin: Removed ShouldProcess; Added Parameters (#249) - Changes to xSQLServerLogin - Updated get/set/test functions. - Moved the New-SqlLogin function into Set-TargetResource - Added stubs to SMO.cs for error handling. - Finished unit tests - Added examples for the xSQLServerLogin resource. - Updated the README text - Added a check for Login Mode before attempting to create a SQL Login - Changed the LoginType parameter from strongly typed to string - Converted the dynamic parameters to static parameters. - Added password checking and setting for present SQL Logins - Broke out Alter, Drop, Create, and SetPassword to functions to allow for mocking. - Formatting and style corrections - Updated error action preference in helper functions - Added differentiation to login creation failed error messages --- CHANGELOG.md | 3 + .../MSFT_xSQLServerLogin.psm1 | 621 ++++++++-- .../MSFT_xSQLServerLogin.schema.mof | 15 +- .../Resources/xSQLServerLogin/1-AddLogin.ps1 | 61 + .../xSQLServerLogin/2-RemoveLogin.ps1 | 44 + README.md | 51 +- Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 | 1033 ++++++++++++----- Tests/Unit/Stubs/SMO.cs | 215 +++- en-US/xSQLServer.strings.psd1 | 13 + 9 files changed, 1617 insertions(+), 439 deletions(-) create mode 100644 Examples/Resources/xSQLServerLogin/1-AddLogin.ps1 create mode 100644 Examples/Resources/xSQLServerLogin/2-RemoveLogin.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 090427001..c4a3b2a35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,9 @@ - Changes to the unit test for resource - xSQLServerSetup - Added test coverage for helper function Copy-ItemWithRoboCopy +- Changes to xSSQLServerLogin + - Removed ShouldProcess statements + - Added the ability to enforce password policies on SQL logins ## 4.0.0.0 diff --git a/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.psm1 b/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.psm1 index e603dabcf..3bbd95ae1 100644 --- a/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.psm1 +++ b/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.psm1 @@ -1,236 +1,539 @@ -$script:currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Write-Debug -Message "CurrentPath: $script:currentPath" +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'xSQLServerHelper.psm1') ` + -Force -# Load helper functions -Import-Module $script:currentPath\..\..\xSQLServerHelper.psm1 -Verbose:$false -ErrorAction Stop +<# + .SYNOPSIS + Gets the specified login by name. + .PARAMETER Name + The name of the login to retrieve. + + .PARAMETER SQLServer + Hostname of the SQL Server to retrieve the login from. + + .PARAMETER SQLInstanceName + Name of the SQL instance to retrieve the login from. +#> function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure, - - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $Name, - [System.Management.Automation.PSCredential] - $LoginCredential, - - [ValidateSet('SqlLogin', 'WindowsUser', 'WindowsGroup')] - [System.String] - $LoginType, - - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] - $SQLServer = $env:COMPUTERNAME, + $SQLServer, - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] - $SQLInstanceName = 'MSSQLSERVER' + $SQLInstanceName ) + + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + Write-Verbose 'Getting SQL logins' + New-VerboseMessage -Message "Getting the login '$Name' from '$SQLServer\$SQLInstanceName'" - if ($sql) - { - Write-Verbose 'Getting SQL logins' + $login = $serverObject.Logins[$Name] - $sqlLogins = $sql.Logins - if ($sqlLogins) - { - if ($sqlLogins[$Name]) - { - Write-Verbose "SQL login name $Name is present" - $Ensure = 'Present' - $LoginType = $sqlLogins[$Name].LoginType - Write-Verbose "SQL login name is of type $LoginType" - } - else - { - Write-Verbose "SQL login name $Name is absent" - $Ensure = 'Absent' - } - } - else - { - Write-Verbose 'Failed getting SQL logins' - $Ensure = 'Absent' - } + if ( $login ) + { + $Ensure = 'Present' } else { $Ensure = 'Absent' } + New-VerboseMessage -Message "The login '$Name' is $ensure from the '$SQLServer\$SQLInstanceName' instance." + $returnValue = @{ Ensure = $Ensure Name = $Name - LoginType = $LoginType + LoginType = $login.LoginType SQLServer = $SQLServer SQLInstanceName = $SQLInstanceName } - $returnValue + if ( $login.LoginType -eq 'SqlLogin' ) + { + $returnValue.Add('LoginMustChangePassword',$login.MustChangePassword) + $returnValue.Add('LoginPasswordExpirationEnabled',$login.PasswordExpirationEnabled) + $returnValue.Add('LoginPasswordPolicyEnforced',$login.PasswordPolicyEnforced) + } + + return $returnValue } +<# + .SYNOPSIS + Creates a login. + + .PARAMETER Ensure + Specifies if the login to exist. Default is 'Present'. + + .PARAMETER Name + The name of the login to retrieve. + + .PARAMETER LoginType + The type of login to create. Default is 'WindowsUser' + + .PARAMETER SQLServer + Hostname of the SQL Server to create the login on. + + .PARAMETER SQLInstanceName + Name of the SQL instance to create the login on. + + .PARAMETER LoginCredential + The credential containing the password for a SQL Login. Only applies if the login type is SqlLogin. + + .PARAMETER LoginMustChangePassword + Specifies if the login is required to have its password change on the next login. Only applies to SQL Logins. Default is $true. + + .PARAMETER LoginPasswordExpirationEnabled + Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. Default is $true. + + .PARAMETER LoginPasswordPolicyEnforced + Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to SQL Logins. Default is $true. +#> function Set-TargetResource { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding()] param ( + [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $Name, - [System.Management.Automation.PSCredential] - $LoginCredential, - - [ValidateSet('SqlLogin', 'WindowsUser', 'WindowsGroup')] + [Parameter()] + [ValidateSet( + 'WindowsUser', + 'WindowsGroup', + 'SqlLogin', + 'Certificate', + 'AsymmetricKey', + 'ExternalUser', + 'ExternalGroup' + )] [System.String] $LoginType = 'WindowsUser', - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] - $SQLServer = $env:COMPUTERNAME, + $SQLServer, - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] - $SQLInstanceName = 'MSSQLSERVER' - ) + $SQLInstanceName, - if ( ($Ensure -eq 'Present') -and - ($LoginType -eq 'SqlLogin') -and - !$PSBoundParameters.ContainsKey('LoginCredential') ) - { - throw New-TerminatingError -ErrorType FailedLogin - } + [Parameter()] + [System.Management.Automation.PSCredential] + $LoginCredential, + + [Parameter()] + [bool] + $LoginMustChangePassword = $true, - $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + [Parameter()] + [bool] + $LoginPasswordExpirationEnabled = $true, - if ($sql) + [Parameter()] + [bool] + $LoginPasswordPolicyEnforced = $true + ) + + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + switch ( $Ensure ) { - switch ($Ensure) + 'Present' { - 'Present' + if ( $serverObject.Logins[$Name] ) { - try - { - Write-Verbose "Creating SQL login $Name of type $LoginType" + $login = $serverObject.Logins[$Name] + + if ( $login.LoginType -eq 'SqlLogin' ) + { + + + if ( $login.PasswordExpirationEnabled -ne $LoginPasswordExpirationEnabled ) + { + New-VerboseMessage -Message "Setting PasswordExpirationEnabled to '$LoginPasswordExpirationEnabled' for the login '$Name' on the '$SQLServer\$SQLInstanceName' instance." + $login.PasswordExpirationEnabled = $LoginPasswordExpirationEnabled + Update-SQLServerLogin -Login $login + } - $sqlLogin = New-Object Microsoft.SqlServer.Management.Smo.Login $sql, $Name - $sqlLogin.LoginType = $LoginType - if ($LoginType -eq 'SqlLogin') + if ( $login.PasswordPolicyEnforced -ne $LoginPasswordPolicyEnforced ) { - if ( ($PSCmdlet.ShouldProcess($($sqlLogin.Name), "Create login")) ) { - $sqlLogin.Create( $LoginCredential.GetNetworkCredential().Password ) - } + New-VerboseMessage -Message "Setting PasswordPolicyEnforced to '$LoginPasswordPolicyEnforced' for the login '$Name' on the '$SQLServer\$SQLInstanceName' instance." + $login.PasswordPolicyEnforced = $LoginPasswordPolicyEnforced + Update-SQLServerLogin -Login $login } - else + + # Set the password if it is specified + if ( $LoginCredential ) { - if ( ($PSCmdlet.ShouldProcess($($sqlLogin.Name), "Create login")) ) { - $sqlLogin.Create() - } + Set-SQLServerLoginPassword -Login $login -SecureString $LoginCredential.Password } } - catch - { - Write-Verbose "Failed creating SQL login $Name of type $LoginType" - - throw $_ - } } - - 'Absent' + else { - try + # Some login types need additional work. These will need to be fleshed out more in the future + if ( @('Certificate','AsymmetricKey','ExternalUser','ExternalGroup') -contains $LoginType ) + { + throw New-TerminatingError -ErrorType LoginTypeNotImplemented -FormatArgs $LoginType -ErrorCategory NotImplemented + } + + if ( ( $LoginType -eq 'SqlLogin' ) -and ( -not $LoginCredential ) ) { - Write-Verbose "Deleting SQL login $Name" + throw New-TerminatingError -ErrorType LoginCredentialNotFound -FormatArgs $Name -ErrorCategory ObjectNotFound + } + + New-VerboseMessage -Message "Adding the login '$Name' to the '$SQLServer\$SQLInstanceName' instance." + + $login = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login -ArgumentList $serverObject,$Name + $login.LoginType = $LoginType - $sqlLogin = $($sql.Logins[$Name]) - if ($sqlLogin) + switch ($LoginType) + { + SqlLogin { - if ( ($PSCmdlet.ShouldProcess($($sqlLogin.Name), "Remove login")) ) { - Remove-SqlLogin -Login $sqlLogin + # Verify the instance is in Mixed authentication mode + if ( $serverObject.LoginMode -notmatch 'Mixed|Integrated' ) + { + throw New-TerminatingError -ErrorType IncorrectLoginMode -FormatArgs $SQLServer,$SQLInstanceName,$serverObject.LoginMode -ErrorCategory NotImplemented + } + + $login.PasswordPolicyEnforced = $LoginPasswordPolicyEnforced + $login.PasswordExpirationEnabled = $LoginPasswordExpirationEnabled + if ( $LoginMustChangePassword ) + { + $LoginCreateOptions = [Microsoft.SqlServer.Management.Smo.LoginCreateOptions]::MustChange + } + else + { + $LoginCreateOptions = [Microsoft.SqlServer.Management.Smo.LoginCreateOptions]::None } + + New-SQLServerLogin -Login $login -LoginCreateOptions $LoginCreateOptions -SecureString $LoginCredential.Password -ErrorAction Stop + } + + default + { + New-SQLServerLogin -Login $login } - } - catch - { - Write-Verbose "Failed deleting SQL login $Name" - - throw $_ } } } - } - if ( !(Test-TargetResource @PSBoundParameters) ) - { - throw New-TerminatingError -ErrorType TestFailedAfterSet -ErrorCategory InvalidResult + 'Absent' + { + if ( $serverObject.Logins[$Name] ) + { + New-VerboseMessage -Message "Dropping the login '$Name' from the '$SQLServer\$SQLInstanceName' instance." + Remove-SQLServerLogin -Login $serverObject.Logins[$Name] + } + } } } +<# + .SYNOPSIS + Tests to verify the login exists and the properties are correctly set. + + .PARAMETER Ensure + Specifies if the login is supposed to exist. Default is 'Present'. + + .PARAMETER Name + The name of the login. + + .PARAMETER LoginType + The type of login. Default is 'WindowsUser' + + .PARAMETER SQLServer + Hostname of the SQL Server. + + .PARAMETER SQLInstanceName + Name of the SQL instance. + + .PARAMETER LoginCredential + The credential containing the password for a SQL Login. Only applies if the login type is SqlLogin. + + .PARAMETER LoginMustChangePassword + Specifies if the login is required to have its password change on the next login. Only applies to SQL Logins. Default is $true. + + .PARAMETER LoginPasswordExpirationEnabled + Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. Default is $true. + + .PARAMETER LoginPasswordPolicyEnforced + Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to SQL Logins. Default is $true. +#> function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( + [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $Name, - [System.Management.Automation.PSCredential] - $LoginCredential, - - [ValidateSet('SqlLogin', 'WindowsUser', 'WindowsGroup')] + [Parameter()] + [ValidateSet('WindowsUser', + 'WindowsGroup', + 'SqlLogin', + 'Certificate', + 'AsymmetricKey', + 'ExternalUser', + 'ExternalGroup' + )] [System.String] $LoginType = 'WindowsUser', - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] - $SQLServer = $env:COMPUTERNAME, + $SQLServer, - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] - $SQLInstanceName = 'MSSQLSERVER' + $SQLInstanceName, + + [Parameter()] + [System.Management.Automation.PSCredential] + $LoginCredential, + + [Parameter()] + [bool] + $LoginMustChangePassword = $true, + + [Parameter()] + [bool] + $LoginPasswordExpirationEnabled = $true, + + [Parameter()] + [bool] + $LoginPasswordPolicyEnforced = $true ) - $sqlServerLogin = Get-TargetResource @PSBoundParameters + # Assume the test will pass + $testPassed = $true + + $getParams = @{ + Name = $Name + SQLServer = $SQLServer + SQLInstanceName = $SQLInstanceName + } + + $loginInfo = Get-TargetResource @getParams - $result = ($sqlServerLogin.Ensure -eq $Ensure) + if ( $Ensure -ne $loginInfo.Ensure ) + { + New-VerboseMessage -Message "The login '$Name' on the instance '$SQLServer\$SQLInstanceName' is $($loginInfo.Ensure) rather than $Ensure" + $testPassed = $false + } + + if ( $Ensure -eq 'Present' ) + { + if ( $LoginType -ne $loginInfo.LoginType ) + { + New-VerboseMessage -Message "The login '$Name' on the instance '$SQLServer\$SQLInstanceName' is a $($loginInfo.LoginType) rather than $LoginType" + $testPassed = $false + } + + if ( $LoginType -eq 'SqlLogin' ) + { + if ( $LoginPasswordExpirationEnabled -ne $loginInfo.LoginPasswordExpirationEnabled ) + { + New-VerboseMessage -Message "The login '$Name' on the instance '$SQLServer\$SQLInstanceName' has PasswordExpirationEnabled set to $($loginInfo.LoginPasswordExpirationEnabled) rather than $LoginPasswordExpirationEnabled" + $testPassed = $false + } + + if ( $LoginPasswordPolicyEnforced -ne $loginInfo.LoginPasswordPolicyEnforced ) + { + New-VerboseMessage -Message "The login '$Name' on the instance '$SQLServer\$SQLInstanceName' has PasswordPolicyEnforced set to $($loginInfo.LoginPasswordPolicyEnforced) rather than $LoginPasswordPolicyEnforced" + $testPassed = $false + } + + # If testPassed is still true and a login credential was specified, test the password + if ( $testPassed -and $LoginCredential ) + { + $userCred = [System.Management.Automation.PSCredential]::new($Name, $LoginCredential.Password) + + try + { + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName -SetupCredential $userCred + } + catch + { + New-VerboseMessage -Message "Password validation failed for the login '$Name'." + $testPassed = $false + } + } + } + } - $result + return $testPassed +} + +<# + .SYNOPSIS + Alters a login. + + .PARAMETER Login + The Login object to alter. + + .NOTES + This function allows us to more easily write mocks. +#> +function Update-SQLServerLogin +{ + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.Login] + $Login + ) + + try + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + $Login.Alter() + } + catch + { + throw New-TerminatingError -ErrorType AlterLoginFailed -FormatArgs $Login.Name -ErrorCategory NotSpecified + } + finally + { + $ErrorActionPreference = $originalErrorActionPreference + } } <# .SYNOPSIS - Removes a SQL login + Creates a login. .PARAMETER Login - A SQL login of the type Microsoft.SqlServer.Management.Smo.Login + The Login object to create. + + .PARAMETER LoginCreateOptions + The LoginCreateOptions object to use when creating a SQL login. + + .PARAMETER SecureString + The SecureString object that contains the password for a SQL login. .EXAMPLE - $server = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server - $login = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $server, "MyLogin" ) - Remove-SqlLogin -Login $login + CreateLogin -Login $login -LoginCreateOptions $LoginCreateOptions -SecureString $LoginCredential.Password -ErrorAction Stop + + .EXAMPLE + CreateLogin -Login $login + + .NOTES + This function allows us to more easily write mocks. #> -function Remove-SqlLogin +function New-SQLServerLogin +{ + [CmdletBinding(DefaultParameterSetName = 'WindowsLogin')] + param + ( + [Parameter(Mandatory = $true, ParameterSetName = 'WindowsLogin')] + [Parameter(Mandatory = $true, ParameterSetName = 'SqlLogin')] + [Microsoft.SqlServer.Management.Smo.Login] + $Login, + + [Parameter(Mandatory = $true, ParameterSetName = 'SqlLogin')] + [Microsoft.SqlServer.Management.Smo.LoginCreateOptions] + $LoginCreateOptions, + + [Parameter(Mandatory = $true, ParameterSetName = 'SqlLogin')] + [System.Security.SecureString] + $SecureString + ) + + switch ( $PSCmdlet.ParameterSetName ) + { + 'SqlLogin' + { + try + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + $login.Create($SecureString,$LoginCreateOptions) + } + catch [Microsoft.SqlServer.Management.Smo.FailedOperationException] + { + if ( $_.Exception.InnerException.InnerException.InnerException -match 'Password validation failed' ) + { + throw New-TerminatingError -ErrorType PasswordValidationFailed -FormatArgs $Name,$_.Exception.InnerException.InnerException.InnerException -ErrorCategory SecurityError + } + else + { + throw New-TerminatingError -ErrorType LoginCreationFailedFailedOperation -FormatArgs $Name -ErrorCategory NotSpecified + } + } + catch + { + throw New-TerminatingError -ErrorType LoginCreationFailedSqlNotSpecified -FormatArgs $Name -ErrorCategory NotSpecified + } + finally + { + $ErrorActionPreference = $originalErrorActionPreference + } + } + + 'WindowsLogin' + { + try + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + $login.Create() + } + catch + { + throw New-TerminatingError -ErrorType LoginCreationFailedWindowsNotSpecified -FormatArgs $Name -ErrorCategory NotSpecified + } + finally + { + $ErrorActionPreference = $originalErrorActionPreference + } + } + } +} + +<# + .SYNOPSIS + Drops a login. + + .PARAMETER Login + The Login object to drop. + + .NOTES + This function allows us to more easily write mocks. +#> +function Remove-SQLServerLogin { - [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] @@ -238,8 +541,74 @@ function Remove-SqlLogin $Login ) - if ( ($PSCmdlet.ShouldProcess($($sqlLogin.Name), "Drop login")) ) { - $sqlLogin.Drop() + try + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + $Login.Drop() + } + catch + { + throw New-TerminatingError -ErrorType DropLoginFailed -FormatArgs $Login.Name -ErrorCategory NotSpecified + } + finally + { + $ErrorActionPreference = $originalErrorActionPreference + } +} + +<# + .SYNOPSIS + Changes the password of a SQL Login. + + .PARAMETER Login + The Login object to change the password on. + + .PARAMETER SecureString + The SecureString object that contains the password for a SQL login. + + .NOTES + This function allows us to more easily write mocks. +#> +function Set-SQLServerLoginPassword +{ + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.Login] + $Login, + + [Parameter(Mandatory = $true)] + [System.Security.SecureString] + $SecureString + ) + + try + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + $Login.ChangePassword($SecureString) + } + catch [Microsoft.SqlServer.Management.Smo.FailedOperationException] + { + if ( $_.Exception.InnerException.InnerException.InnerException -match 'Password validation failed' ) + { + throw New-TerminatingError -ErrorType PasswordValidationFailed -FormatArgs $Name,$_.Exception.InnerException.InnerException.InnerException -ErrorCategory SecurityError + } + else + { + throw New-TerminatingError -ErrorType PasswordChangeFailed -FormatArgs $Name -ErrorCategory NotSpecified + } + } + catch + { + throw New-TerminatingError -ErrorType PasswordChangeFailed -FormatArgs $Name -ErrorCategory NotSpecified + } + finally + { + $ErrorActionPreference = $originalErrorActionPreference } } diff --git a/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.schema.mof b/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.schema.mof index 9fb650958..a8866341f 100644 --- a/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.schema.mof +++ b/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.schema.mof @@ -1,10 +1,15 @@ [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerLogin")] class MSFT_xSQLServerLogin : OMI_BaseResource { - [Write, Description("If the values should be present or absent. Valid values are 'Present' or 'Absent'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Key, Description("The name of the SQL login. If LoginType is 'WindowsUser' or 'WindowsGroup' then provide the name in the format DOMAIN\name.")] String Name; + [Write, Description("The specified login is Present or Absent. Default is Present."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Key, Description("The name of the login.")] String Name; + [Write, Description("The type of login to be created. If LoginType is 'WindowsUser' or 'WindowsGroup' then provide the name in the format DOMAIN\name. Default is WindowsUser. Unsupported login types are Certificate, AsymmetricKey, ExternalUser, and ExternalGroup."), + ValueMap{"WindowsUser","WindowsGroup","SqlLogin","Certificate","AsymmetricKey","ExternalUser","ExternalGroup"}, + Values{"WindowsUser","WindowsGroup","SqlLogin","Certificate","AsymmetricKey","ExternalUser","ExternalGroup"}] String LoginType; + [Key, Description("The hostname of the SQL Server to be configured.")] String SQLServer; + [Key, Description("Name of the SQL instance to be configured.")] String SQLInstanceName; [Write, EmbeddedInstance("MSFT_Credential"), Description("If LoginType is 'SqlLogin' then a PSCredential is needed for the password to the login.")] String LoginCredential; - [Write, Description("The SQL login type. Valid values are 'SqlLogin', 'WindowsUser' or 'WindowsGroup'."), ValueMap{"SqlLogin","WindowsUser","WindowsGroup"}, Values{"SqlLogin","WindowsUser","WindowsGroup"}] String LoginType; - [Key, Description("The SQL Server for the login.")] String SQLServer; - [Key, Description("The SQL instance for the login.")] String SQLInstanceName; + [Write, Description("Specifies if the login is required to have its password change on the next login. Only applies to SQL Logins. Default is $true.")] Boolean LoginMustChangePassword; + [Write, Description("Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. Default is $true.")] Boolean LoginPasswordExpirationEnabled; + [Write, Description("Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to SQL Logins. Default is $true.")] Boolean LoginPasswordPolicyEnforced; }; diff --git a/Examples/Resources/xSQLServerLogin/1-AddLogin.ps1 b/Examples/Resources/xSQLServerLogin/1-AddLogin.ps1 new file mode 100644 index 000000000..b844a81fd --- /dev/null +++ b/Examples/Resources/xSQLServerLogin/1-AddLogin.ps1 @@ -0,0 +1,61 @@ +<# +.EXAMPLE +This example shows how to ensure that the Windows user 'CONTOSO\WindowsUser' exists. + +.EXAMPLE +This example shows how to ensure that the Windows group 'CONTOSO\WindowsGroup' exists. + +.EXAMPLE +This example shows how to ensure that the SQL Login 'SqlLogin' exists. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount, + + [Parameter(Mandatory = $true)] + [PSCredential] + $LoginCredential + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerLogin Add_WindowsUser + { + Ensure = 'Present' + Name = 'CONTOSO\WindowsUser' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerLogin Add_WindowsGroup + { + Ensure = 'Present' + Name = 'CONTOSO\WindowsGroup' + LoginType = 'WindowsGroup' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerLogin Add_SqlLogin + { + Ensure = 'Present' + Name = 'SqlLogin' + LoginType = 'SqlLogin' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + LoginCredential = $LoginCredential + LoginMustChangePassword = $false + LoginPasswordExpirationEnabled = $true + LoginPasswordPolicyEnforced = $true + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerLogin/2-RemoveLogin.ps1 b/Examples/Resources/xSQLServerLogin/2-RemoveLogin.ps1 new file mode 100644 index 000000000..693ea39a8 --- /dev/null +++ b/Examples/Resources/xSQLServerLogin/2-RemoveLogin.ps1 @@ -0,0 +1,44 @@ +<# +.EXAMPLE +This example shows how to remove the Windows user 'CONTOSO\WindowsUser'. + +.EXAMPLE +This example shows how to remove Windows group 'CONTOSO\WindowsGroup'. + +.EXAMPLE +This example shows how to remove the SQL Login 'SqlLogin'. +#> + +Configuration Example +{ + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerLogin Remove_WindowsUser + { + Ensure = 'Absent' + Name = 'CONTOSO\WindowsUser' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + } + + xSQLServerLogin Remove_WindowsGroup + { + Ensure = 'Absent' + Name = 'CONTOSO\WindowsGroup' + LoginType = 'WindowsGroup' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + } + + xSQLServerLogin Remove_SqlLogin + { + Ensure = 'Absent' + Name = 'SqlLogin' + LoginType = 'SqlLogin' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + } + } +} diff --git a/README.md b/README.md index 4b2096f2f..177c97e6e 100644 --- a/README.md +++ b/README.md @@ -277,12 +277,53 @@ A full list of changes in each version can be found in the [change log](CHANGELO ### xSQLServerLogin -* **Ensure**: If the values should be present or absent. Valid values are 'Present' or 'Absent'. -* **Name**: (Key) The name of the SQL login. If LoginType is 'WindowsUser' or 'WindowsGroup' then provide the name in the format DOMAIN\name. +* **Ensure**: The specified login is Present or Absent. +* **Name**: (Key) The name of the login. +* **LoginType**: The type of login to be created. If LoginType is 'WindowsUser' or 'WindowsGroup' then provide the name in the format DOMAIN\name. Default is WindowsUser. Unsupported login types are Certificate, AsymmetricKey, ExternalUser, and ExternalGroup. +* **SQLServer**: (Key) The hostname of the SQL Server to be configured. +* **SQLInstanceName**: (Key) Name of the SQL instance to be configured. * **LoginCredential**: If LoginType is 'SqlLogin' then a PSCredential is needed for the password to the login. -* **LoginType**: The SQL login type. Valid values are 'SqlLogin', 'WindowsUser' or 'WindowsGroup'. -* **SQLServer**: (Key) The SQL Server for the login. -* **SQLInstanceName**: (Key) The SQL instance for the login. +* **LoginMustChangePassword**: Specifies if the login is required to have its password change on the next login. Only applies to SQL Logins. Default is $true. +* **LoginPasswordExpirationEnabled**: Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. Default is $true. +* **LoginPasswordPolicyEnforced**: Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to SQL Logins. Default is $true. + +### xSQLServerRole + +* **Name**: (Key) Name of the SQL Login to create +* **Ensure**: If the values should be present or absent. Valid values are 'Present' or 'Absent'. +* **ServerRole**: Type of SQL role to add.(bulkadmin, dbcreator, diskadmin, processadmin , public, securityadmin, serveradmin , setupadmin, sysadmin) +* **SQLServer**: SQL Server where login should be created +* **SQLInstance**: (Key) SQL Instance for the login + +### xSQLServerDatabaseRole + +* **Ensure**: If 'Present' (the default value) then the login (user) will be added to the role(s). If 'Absent' then the login (user) will be removed from the role(s). +* **Name**: (Key) The name of the login that will become a member, or removed as a member, of the role(s). +* **SQLServer**: (Key) The SQL server on which the instance exist. +* **SQLInstanceName**: (Key) The SQL instance in which the database exist. +* **Database**: (Key) The database in which the login (user) and role(s) exist. +* **Role**: One or more roles to which the login (user) will be added or removed. + +### xSQLServerDatabasePermissions + +* **Database**: (Key) The SQL Database +* **Name**: (Required) The name of permissions for the SQL database +* **Permissions**: (Required) The set of Permissions for the SQL database +* **SQLServer**: The SQL Server for the database +* **SQLInstanceName**: The SQL instance for the database + +### xSQLServerDatabaseOwner + +* **Database**: (Key) The SQL Database +* **Name**: (Required) The name of the SQL login for the owner +* **SQLServer**: The SQL Server for the database +* **SQLInstance**: The SQL instance for the database + +### xSQLDatabaseRecoveryModel + +* **DatabaseName**: (key) The SQL database name +* **SQLServerInstance**: (Required) The SQL server and instance +* **RecoveryModel**: (Required) Recovery Model (Full, Simple, BulkLogged) ### xSQLServerMaxDop diff --git a/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 index c547725ef..1ef784f43 100644 --- a/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 @@ -1,6 +1,5 @@ # Suppressing this rule because PlainText is required for one of the functions used in this test [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] -param() $script:DSCModuleName = 'xSQLServer' $script:DSCResourceName = 'MSFT_xSQLServerLogin' @@ -29,424 +28,878 @@ try { #region Pester Test Initialization - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + Import-Module -Name ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SQLPSStub.psm1 ) -Force + Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SMO.cs ) - $nodeName = 'localhost' - $instanceName = 'MSSQLSERVER' - - $mockSqlLoginUser = "dba" - $mockSqlLoginPassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force + # Create PSCredential object for SQL Logins + $mockSqlLoginUser = 'dba' + $mockSqlLoginPassword = 'P@ssw0rd-12P@ssw0rd-12' | ConvertTo-SecureString -AsPlainText -Force $mockSqlLoginCredential = New-Object System.Management.Automation.PSCredential( $mockSqlLoginUser, $mockSqlLoginPassword ) - $defaultParameters = @{ - SQLInstanceName = $instanceName - SQLServer = $nodeName + $mockSqlLoginBadPassword = 'pw' | ConvertTo-SecureString -AsPlainText -Force + $mockSqlLoginCredentialBadPassword = New-Object System.Management.Automation.PSCredential( $mockSqlLoginUser, $mockSqlLoginBadPassword ) + + $mockSqlLoginReusedPassword = 'reused' | ConvertTo-SecureString -AsPlainText -Force + $mockSqlLoginCredentialReusedPassword = New-Object System.Management.Automation.PSCredential( $mockSqlLoginUser, $mockSqlLoginReusedPassword ) + + $mockSqlLoginOtherPassword = 'other' | ConvertTo-SecureString -AsPlainText -Force + $mockSqlLoginCredentialOtherPassword = New-Object System.Management.Automation.PSCredential( $mockSqlLoginUser, $mockSqlLoginOtherPassword ) + + $instanceParameters = @{ + SQLInstanceName = 'MSSQLSERVER' + SQLServer = 'Server1' + } + + $getTargetResource_UnknownSqlLogin = $instanceParameters.Clone() + $getTargetResource_UnknownSqlLogin.Add( 'Name','UnknownSqlLogin' ) + + $getTargetResource_UnknownWindows = $instanceParameters.Clone() + $getTargetResource_UnknownWindows.Add( 'Name','Windows\UserOrGroup' ) + + $getTargetResource_KnownSqlLogin = $instanceParameters.Clone() + $getTargetResource_KnownSqlLogin.Add( 'Name','SqlLogin1' ) + + $getTargetResource_KnownWindowsUser = $instanceParameters.Clone() + $getTargetResource_KnownWindowsUser.Add( 'Name','Windows\User1' ) + + $getTargetResource_KnownWindowsGroup = $instanceParameters.Clone() + $getTargetResource_KnownWindowsGroup.Add( 'Name','Windows\Group1' ) + + $testTargetResource_WindowsUserAbsent = $instanceParameters.Clone() + $testTargetResource_WindowsUserAbsent.Add( 'Name','Windows\UserAbsent' ) + $testTargetResource_WindowsUserAbsent.Add( 'LoginType','WindowsUser' ) + + $testTargetResource_WindowsGroupAbsent = $instanceParameters.Clone() + $testTargetResource_WindowsGroupAbsent.Add( 'Name','Windows\GroupAbsent' ) + $testTargetResource_WindowsGroupAbsent.Add( 'LoginType','WindowsGroup' ) + + $testTargetResource_SqlLoginAbsent = $instanceParameters.Clone() + $testTargetResource_SqlLoginAbsent.Add( 'Name','SqlLoginAbsent' ) + $testTargetResource_SqlLoginAbsent.Add( 'LoginType','SqlLogin' ) + + $testTargetResource_WindowsUserPresent = $instanceParameters.Clone() + $testTargetResource_WindowsUserPresent.Add( 'Name','Windows\User1' ) + $testTargetResource_WindowsUserPresent.Add( 'LoginType','WindowsUser' ) + + $testTargetResource_WindowsGroupPresent = $instanceParameters.Clone() + $testTargetResource_WindowsGroupPresent.Add( 'Name','Windows\Group1' ) + $testTargetResource_WindowsGroupPresent.Add( 'LoginType','WindowsGroup' ) + + $testTargetResource_SqlLoginPresentWithDefaultValues = $instanceParameters.Clone() + $testTargetResource_SqlLoginPresentWithDefaultValues.Add( 'Name','SqlLogin1' ) + $testTargetResource_SqlLoginPresentWithDefaultValues.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_CertificateAbsent = $instanceParameters.Clone() + $setTargetResource_CertificateAbsent.Add( 'Name','Certificate' ) + $setTargetResource_CertificateAbsent.Add( 'LoginType','Certificate' ) + + $setTargetResource_WindowsUserAbsent = $instanceParameters.Clone() + $setTargetResource_WindowsUserAbsent.Add( 'Name','Windows\UserAbsent' ) + $setTargetResource_WindowsUserAbsent.Add( 'LoginType','WindowsUser' ) + + $setTargetResource_WindowsGroupAbsent = $instanceParameters.Clone() + $setTargetResource_WindowsGroupAbsent.Add( 'Name','Windows\GroupAbsent' ) + $setTargetResource_WindowsGroupAbsent.Add( 'LoginType','WindowsGroup' ) + + $setTargetResource_SqlLoginAbsent = $instanceParameters.Clone() + $setTargetResource_SqlLoginAbsent.Add( 'Name','SqlLoginAbsent' ) + $setTargetResource_SqlLoginAbsent.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_SqlLoginAbsentExisting = $instanceParameters.Clone() + $setTargetResource_SqlLoginAbsentExisting.Add( 'Name','Existing' ) + $setTargetResource_SqlLoginAbsentExisting.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_SqlLoginAbsentUnknown = $instanceParameters.Clone() + $setTargetResource_SqlLoginAbsentUnknown.Add( 'Name','Unknown' ) + $setTargetResource_SqlLoginAbsentUnknown.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_WindowsUserPresent = $instanceParameters.Clone() + $setTargetResource_WindowsUserPresent.Add( 'Name','Windows\User1' ) + $setTargetResource_WindowsUserPresent.Add( 'LoginType','WindowsUser' ) + + $setTargetResource_CertificateAbsent = $instanceParameters.Clone() + $setTargetResource_CertificateAbsent.Add( 'Name','Certificate' ) + $setTargetResource_CertificateAbsent.Add( 'LoginType','Certificate' ) + + $setTargetResource_WindowsUserAbsent = $instanceParameters.Clone() + $setTargetResource_WindowsUserAbsent.Add( 'Name','Windows\UserAbsent' ) + $setTargetResource_WindowsUserAbsent.Add( 'LoginType','WindowsUser' ) + + $setTargetResource_WindowsGroupAbsent = $instanceParameters.Clone() + $setTargetResource_WindowsGroupAbsent.Add( 'Name','Windows\GroupAbsent' ) + $setTargetResource_WindowsGroupAbsent.Add( 'LoginType','WindowsGroup' ) + + $setTargetResource_SqlLoginAbsent = $instanceParameters.Clone() + $setTargetResource_SqlLoginAbsent.Add( 'Name','SqlLoginAbsent' ) + $setTargetResource_SqlLoginAbsent.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_SqlLoginAbsentExisting = $instanceParameters.Clone() + $setTargetResource_SqlLoginAbsentExisting.Add( 'Name','Existing' ) + $setTargetResource_SqlLoginAbsentExisting.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_SqlLoginAbsentUnknown = $instanceParameters.Clone() + $setTargetResource_SqlLoginAbsentUnknown.Add( 'Name','Unknown' ) + $setTargetResource_SqlLoginAbsentUnknown.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_WindowsUserPresent = $instanceParameters.Clone() + $setTargetResource_WindowsUserPresent.Add( 'Name','Windows\User1' ) + $setTargetResource_WindowsUserPresent.Add( 'LoginType','WindowsUser' ) + + $setTargetResource_WindowsGroupPresent = $instanceParameters.Clone() + $setTargetResource_WindowsGroupPresent.Add( 'Name','Windows\Group1' ) + $setTargetResource_WindowsGroupPresent.Add( 'LoginType','WindowsGroup' ) + + $setTargetResource_SqlLoginPresent = $instanceParameters.Clone() + $setTargetResource_SqlLoginPresent.Add( 'Name','SqlLogin1' ) + $setTargetResource_SqlLoginPresent.Add( 'LoginType','SqlLogin' ) + + $mockConnectSql = { + $windowsUser = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Windows\User1' ) + $windowsUser.LoginType = 'WindowsUser' + $windowsGroup = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Windows\Group1' ) + $windowsGroup.LoginType = 'windowsGroup' + $sqlLogin = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'SqlLogin1' ) + $sqlLogin.LoginType = 'SqlLogin' + $sqlLogin.MustChangePassword = $false + $sqlLogin.PasswordPolicyEnforced = $true + $sqlLogin.PasswordExpirationEnabled = $true + + $mock = New-Object PSObject -Property @{ + LoginMode = 'Mixed' + Logins = @{ + $windowsUser.Name = $windowsUser + $windowsGroup.Name = $windowsGroup + $sqlLogin.Name = $sqlLogin + } + } + + return $mock } #endregion Pester Test Initialization Describe "$($script:DSCResourceName)\Get-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Logins { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\Stacy') -Property @{ LoginType = 'WindowsUser'} ) ) - 'John' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'John') -Property @{ LoginType = 'SqlLogin'} ) ) - 'COMPANY\SqlUsers' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\SqlUsers') -Property @{ LoginType = 'WindowsGroup'} ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Verifiable -Scope Describe - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\UnknownUser' - } + Context 'When the login is Absent' { - $result = Get-TargetResource @testParameters + It 'Should be Absent when an unknown SQL Login is provided' { + ( Get-TargetResource @getTargetResource_UnknownSqlLogin ).Ensure | Should Be 'Absent' - It 'Should not return the state as absent' { - $result.Ensure | Should Be 'Absent' - $result.LoginType | Should Be '' + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + It 'Should be Absent when an unknown Windows User or Group is provided' { + ( Get-TargetResource @getTargetResource_UnknownWindows ).Ensure | Should Be 'Absent' - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } } - - Context 'When the system is in the desired state for a Windows user' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\Stacy' - } - - $result = Get-TargetResource @testParameters - It 'Should not return the state as present' { + Context 'When the login is Present' { + It 'Should be Present when a known SQL Login is provided' { + $result = Get-TargetResource @getTargetResource_KnownSqlLogin + $result.Ensure | Should Be 'Present' - $result.LoginType | Should Be 'WindowsUser' - } + $result.LoginType | Should Be 'SqlLogin' + $result.LoginMustChangePassword | Should Not BeNullOrEmpty + $result.LoginPasswordExpirationEnabled | Should Not BeNullOrEmpty + $result.LoginPasswordPolicyEnforced | Should Not BeNullOrEmpty - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } - } + It 'Should be Present when a known Windows User is provided' { + $result = Get-TargetResource @getTargetResource_KnownWindowsUser + + $result.Ensure | Should Be 'Present' + $result.LoginType | Should Be 'WindowsUser' + $result.LoginMustChangePassword | Should BeNullOrEmpty + $result.LoginPasswordExpirationEnabled | Should BeNullOrEmpty + $result.LoginPasswordPolicyEnforced | Should BeNullOrEmpty - Context 'When the system is in the desired state for a Windows group' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\SqlUsers' + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - - $result = Get-TargetResource @testParameters - It 'Should return the state as present' { + It 'Should be Present when a known Windows User is provided' { + $result = Get-TargetResource @getTargetResource_KnownWindowsGroup + $result.Ensure | Should Be 'Present' $result.LoginType | Should Be 'WindowsGroup' + $result.LoginMustChangePassword | Should BeNullOrEmpty + $result.LoginPasswordExpirationEnabled | Should BeNullOrEmpty + $result.LoginPasswordPolicyEnforced | Should BeNullOrEmpty + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } + } + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + Context 'When the desired state is Absent' { + It 'Should return $true when the specified Windows user is Absent' { + $testTargetResource_WindowsUserAbsent_EnsureAbsent = $testTargetResource_WindowsUserAbsent.Clone() + $testTargetResource_WindowsUserAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) + + ( Test-TargetResource @testTargetResource_WindowsUserAbsent_EnsureAbsent ) | Should Be $true - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + It 'Should return $true when the specified Windows group is Absent' { + $testTargetResource_WindowsGroupAbsent_EnsureAbsent = $testTargetResource_WindowsGroupAbsent.Clone() + $testTargetResource_WindowsGroupAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) + + ( Test-TargetResource @testTargetResource_WindowsGroupAbsent_EnsureAbsent ) | Should Be $true + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - } - Context 'When the system is in the desired state for a SQL login' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'John' + It 'Should return $true when the specified SQL Login is Absent' { + $testTargetResource_SqlLoginAbsent_EnsureAbsent = $testTargetResource_SqlLoginAbsent.Clone() + $testTargetResource_SqlLoginAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) + + ( Test-TargetResource @testTargetResource_SqlLoginAbsent_EnsureAbsent ) | Should Be $true + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - - $result = Get-TargetResource @testParameters - It 'Should return the state as present' { - $result.Ensure | Should Be 'Present' - $result.LoginType | Should Be 'SqlLogin' + It 'Should return $false when the specified Windows user is Present' { + $testTargetResource_WindowsUserPresent_EnsureAbsent = $testTargetResource_WindowsUserPresent.Clone() + $testTargetResource_WindowsUserPresent_EnsureAbsent.Add( 'Ensure','Absent' ) + + ( Test-TargetResource @testTargetResource_WindowsUserPresent_EnsureAbsent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name + It 'Should return $false when the specified Windows group is Present' { + $testTargetResource_WindowsGroupPresent_EnsureAbsent = $testTargetResource_WindowsGroupPresent.Clone() + $testTargetResource_WindowsGroupPresent_EnsureAbsent.Add( 'Ensure','Absent' ) + + ( Test-TargetResource @testTargetResource_WindowsGroupPresent_EnsureAbsent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + It 'Should return $false when the specified SQL Login is Present' { + $testTargetResource_SqlLoginPresentWithDefaultValues_EnsureAbsent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithDefaultValues_EnsureAbsent.Add( 'Ensure','Absent' ) + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithDefaultValues_EnsureAbsent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } } + + Context 'When the desired state is Present' { + It 'Should return $false when the specified Windows user is Absent' { + $testTargetResource_WindowsUserAbsent_EnsurePresent = $testTargetResource_WindowsUserAbsent.Clone() + $testTargetResource_WindowsUserAbsent_EnsurePresent.Add( 'Ensure','Present' ) - Assert-VerifiableMocks - } + ( Test-TargetResource @testTargetResource_WindowsUserAbsent_EnsurePresent ) | Should Be $false - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Logins { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\Stacy') -Property @{ LoginType = 'WindowsUser'} ) ) - 'John' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'John') -Property @{ LoginType = 'SqlLogin'} ) ) - 'COMPANY\SqlUsers' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\SqlUsers') -Property @{ LoginType = 'WindowsGroup'} ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - It 'Should return the state as absent when desired windows user does not exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\UnknownUser' - } + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - $result = Test-TargetResource @testParameters - $result | Should Be $false + It 'Should return $false when the specified Windows group is Absent' { + $testTargetResource_WindowsGroupAbsent_EnsurePresent = $testTargetResource_WindowsGroupAbsent.Clone() + $testTargetResource_WindowsGroupAbsent_EnsurePresent.Add( 'Ensure','Present' ) - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + ( Test-TargetResource @testTargetResource_WindowsGroupAbsent_EnsurePresent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should return the state as present when desired login exists and login type is SQL login' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\SqlUsers' - LoginType = 'SqlLogin' - } + It 'Should return $false when the specified SQL Login is Absent' { + $testTargetResource_SqlLoginAbsent_EnsurePresent = $testTargetResource_SqlLoginAbsent.Clone() + $testTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'Ensure','Present' ) - $result = Test-TargetResource @testParameters - $result | Should Be $true + ( Test-TargetResource @testTargetResource_SqlLoginAbsent_EnsurePresent ) | Should Be $false - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should return the state as present when desired login exists and login type is Windows' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'John' - LoginType = 'WindowsUser' - } + It 'Should return $true when the specified Windows user is Present' { + $testTargetResource_WindowsUserPresent_EnsurePresent = $testTargetResource_WindowsUserPresent.Clone() + $testTargetResource_WindowsUserPresent_EnsurePresent.Add( 'Ensure','Present' ) - $result = Test-TargetResource @testParameters - $result | Should Be $true + ( Test-TargetResource @testTargetResource_WindowsUserPresent_EnsurePresent ) | Should Be $true - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - } - Context 'When the system is in the desired state' { - It 'Should return the state as present when desired windows user exists' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\Stacy' - } + It 'Should return $true when the specified Windows group is Present' { + $testTargetResource_WindowsGroupPresent_EnsurePresent = $testTargetResource_WindowsGroupPresent.Clone() + $testTargetResource_WindowsGroupPresent_EnsurePresent.Add( 'Ensure','Present' ) - $result = Test-TargetResource @testParameters - $result | Should Be $true + ( Test-TargetResource @testTargetResource_WindowsGroupPresent_EnsurePresent ) | Should Be $true - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should return the state as present when desired windows group exists' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\SqlUsers' - LoginType = 'WindowsGroup' - } + It 'Should return $true when the specified SQL Login is Present using default parameter values' { + $testTargetResource_SqlLoginPresentWithDefaultValues_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithDefaultValues_EnsurePresent.Add( 'Ensure','Present' ) - $result = Test-TargetResource @testParameters - $result | Should Be $true + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithDefaultValues_EnsurePresent ) | Should Be $true - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should return the state as present when desired sql login exists' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'John' - LoginType = 'SqlLogin' - } + It 'Should return $true when the specified SQL Login is Present and PasswordExpirationEnabled is $true' { + $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent.Add( 'Ensure','Present' ) + $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent.Add( 'LoginPasswordExpirationEnabled',$true ) - $result = Test-TargetResource @testParameters - $result | Should Be $true + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent ) | Should Be $true - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - } - Assert-VerifiableMocks - } + It 'Should return $false when the specified SQL Login is Present and PasswordExpirationEnabled is $false' { + $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent.Add( 'Ensure','Present' ) + $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent.Add( 'LoginPasswordExpirationEnabled',$false ) - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Logins { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\Stacy') -Property @{ LoginType = 'WindowsUser'} ) ) - 'John' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'John') -Property @{ LoginType = 'SqlLogin'} ) ) - 'COMPANY\SqlUsers' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\SqlUsers') -Property @{ LoginType = 'WindowsGroup'} ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent ) | Should Be $false - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'UnknownSqlLogin' - LoginType = 'SqlLogin' + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should throw an error when desired login type is a SQL login and LoginCredential parameter is not passed' { - { Set-TargetResource @testParameters } | Should Throw - Assert-MockCalled Connect-SQL -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + It 'Should return $true when the specified SQL Login is Present and PasswordPolicyEnforced is $true' { + $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent.Add( 'Ensure','Present' ) + $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent.Add( 'LoginPasswordPolicyEnforced',$true ) + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent ) | Should Be $true + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - $testParameters += @{ - LoginCredential = $mockSqlLoginCredential + It 'Should return $false when the specified SQL Login is Present and PasswordPolicyEnforced is $false' { + $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent.Add( 'Ensure','Present' ) + $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent.Add( 'LoginPasswordPolicyEnforced',$false ) + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should not throw an error when desired login type is a SQL login' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'SqlLogin' - } - } -ModuleName $script:DSCResourceName -Verifiable + It 'Should return $true when the specified SQL Login is Present using default parameter values and the password is properly configured.' { + $testTargetResource_SqlLoginPresentWithDefaultValuesGoodPw_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithDefaultValuesGoodPw_EnsurePresent.Add( 'Ensure','Present' ) + $testTargetResource_SqlLoginPresentWithDefaultValuesGoodPw_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredential ) - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithDefaultValuesGoodPw_EnsurePresent ) | Should Be $true + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 2 -Exactly } - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\UnknownUser' - LoginType = 'WindowsUser' + It 'Should return $false when the specified SQL Login is Present using default parameter values and the password is not properly configured.' { + Mock -CommandName Connect-SQL -MockWith { throw } -ModuleName $script:DSCResourceName -Scope It -Verifiable -ParameterFilter { $SetupCredential } + + $testTargetResource_SqlLoginPresentWithDefaultValuesBadPw_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithDefaultValuesBadPw_EnsurePresent.Add( 'Ensure','Present' ) + $testTargetResource_SqlLoginPresentWithDefaultValuesBadPw_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredentialBadpassword ) + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithDefaultValuesBadPw_EnsurePresent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 2 -Exactly } + } + } - It 'Should not throw an error when desired login type is a Windows User' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'WindowsUser' - } - } -ModuleName $script:DSCResourceName -Verifiable + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName + Mock -CommandName Update-SQLServerLogin -MockWith {} -ModuleName $script:DSCResourceName + Mock -CommandName New-SQLServerLogin -MockWith {} -ModuleName $script:DSCResourceName + Mock -CommandName Remove-SQLServerLogin -MockWith {} -ModuleName $script:DSCResourceName + Mock -CommandName Set-SQLServerLoginPassword -MockWith {} -ModuleName $script:DSCResourceName + + Context 'When the desired state is Absent' { + It 'Should drop the specified Windows User when it is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindowsUserPresent_EnsureAbsent = $setTargetResource_WindowsUserPresent.Clone() + $setTargetResource_WindowsUserPresent_EnsureAbsent.Add( 'Ensure','Absent' ) - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Set-TargetResource @setTargetResource_WindowsUserPresent_EnsureAbsent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\UnknownGroup' - LoginType = 'WindowsGroup' + It 'Should drop the specified Windows Group when it is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindowsGroupPresent_EnsureAbsent = $setTargetResource_WindowsGroupPresent.Clone() + $setTargetResource_WindowsGroupPresent_EnsureAbsent.Add( 'Ensure','Absent' ) + + Set-TargetResource @setTargetResource_WindowsGroupPresent_EnsureAbsent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } - It 'Should not throw an error when desired login type is a Windows Group' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'WindowsGroup' - } - } -ModuleName $script:DSCResourceName -Verifiable + It 'Should drop the specified SQL Login when it is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginPresent_EnsureAbsent = $setTargetResource_SqlLoginPresent.Clone() + $setTargetResource_SqlLoginPresent_EnsureAbsent.Add( 'Ensure','Absent' ) + + Set-TargetResource @setTargetResource_SqlLoginPresent_EnsureAbsent - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Name = 'COMPANY\Stacy' + It 'Should do nothing when the specified Windows User is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindwsUserAbsent_EnsureAbsent = $setTargetResource_WindowsUserAbsent.Clone() + $setTargetResource_WindwsUserAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) + + Set-TargetResource @setTargetResource_WindwsUserAbsent_EnsureAbsent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } - It 'Should call the function Remove-SqlLogin when desired state should be absent' { - # Mock the return value from the Get-method, because Test-method is ran at the end of the Set-method to validate that the removal (in this case) was successful. - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Absent' - } - } -ModuleName $script:DSCResourceName -Verifiable + It 'Should do nothing when the specified Windows Group is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindwsGroupAbsent_EnsureAbsent = $setTargetResource_WindowsGroupAbsent.Clone() + $setTargetResource_WindwsGroupAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) - Mock -CommandName Remove-SqlLogin -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Set-TargetResource @setTargetResource_WindwsGroupAbsent_EnsureAbsent - Set-TargetResource @testParameters + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + + It 'Should do nothing when the specified SQL Login is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginAbsent_EnsureAbsent = $setTargetResource_SqlLoginAbsent.Clone() + $setTargetResource_SqlLoginAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) + + Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsureAbsent - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Remove-SqlLogin -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } } - Context 'When the system is in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'John' - LoginType = 'SqlLogin' + Context 'When the desired state is Present' { + It 'Should add the specified Windows User when it is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindowsUserAbsent_EnsurePresent = $setTargetResource_WindowsUserAbsent.Clone() + $setTargetResource_WindowsUserAbsent_EnsurePresent.Add( 'Ensure','Present' ) + + Set-TargetResource @setTargetResource_WindowsUserAbsent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } - It 'Should throw an error when desired login type is a SQL login and LoginCredential parameter is not passed' { - { Set-TargetResource @testParameters } | Should Throw - Assert-MockCalled Connect-SQL -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + It 'Should add the specified Windows Group when it is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindowsGroupAbsent_EnsurePresent = $setTargetResource_WindowsGroupAbsent.Clone() + $setTargetResource_WindowsGroupAbsent_EnsurePresent.Add( 'Ensure','Present' ) + + Set-TargetResource @setTargetResource_WindowsGroupAbsent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } - $testParameters += @{ - LoginCredential = $mockSqlLoginCredential + It 'Should add the specified SQL Login when it is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginAbsent_EnsurePresent = $setTargetResource_SqlLoginAbsent.Clone() + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'Ensure','Present' ) + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredential ) + + Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } - It 'Should not throw an error when desired login type is a SQL login' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'SqlLogin' - } - } -ModuleName $script:DSCResourceName -Verifiable + It 'Should add the specified SQL Login when it is Absent and MustChangePassword is $false' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginAbsent_EnsurePresent = $setTargetResource_SqlLoginAbsent.Clone() + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'Ensure','Present' ) + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredential ) + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'LoginMustChangePassword',$false ) - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\Stacy' - LoginType = 'WindowsUser' + It 'Should throw the correct error when adding an unsupported login type' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_CertificateAbsent_EnsurePresent = $setTargetResource_CertificateAbsent.Clone() + $setTargetResource_CertificateAbsent_EnsurePresent.Add( 'Ensure','Present' ) + + { Set-TargetResource @setTargetResource_CertificateAbsent_EnsurePresent } | Should Throw 'LoginTypeNotImplemented' + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } - It 'Should not throw an error when desired login type is a Windows User' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'WindowsUser' - } - } -ModuleName $script:DSCResourceName -Verifiable + It 'Should throw the correct error when adding the specified SQL Login when it is Absent and is missing the LoginCredential parameter' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginAbsent_EnsurePresent_NoCred = $setTargetResource_SqlLoginAbsent.Clone() + $setTargetResource_SqlLoginAbsent_EnsurePresent_NoCred.Add( 'Ensure','Present' ) + + { Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsurePresent_NoCred } | Should Throw 'LoginCredentialNotFound' + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + + It 'Should do nothing if the specified Windows User is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindowsUserPresent_EnsurePresent = $setTargetResource_WindowsUserPresent.Clone() + $setTargetResource_WindowsUserPresent_EnsurePresent.Add( 'Ensure','Present' ) + + Set-TargetResource @setTargetResource_WindowsUserPresent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + + It 'Should do nothing if the specified Windows Group is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindowsGroupPresent_EnsurePresent = $setTargetResource_WindowsGroupPresent.Clone() + $setTargetResource_WindowsGroupPresent_EnsurePresent.Add( 'Ensure','Present' ) + + Set-TargetResource @setTargetResource_WindowsGroupPresent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + + It 'Should update the password of the specified SQL Login if it is Present and all parameters match' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginPresent_EnsurePresent = $setTargetResource_SqlLoginPresent.Clone() + $setTargetResource_SqlLoginPresent_EnsurePresent.Add( 'Ensure','Present' ) + $setTargetResource_SqlLoginPresent_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredential ) + + Set-TargetResource @setTargetResource_SqlLoginPresent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 1 -Exactly + } + + It 'Should set PasswordExpirationEnabled on the specified SQL Login if it does not match the LoginPasswordExpirationEnabled parameter' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled = $setTargetResource_SqlLoginPresent.Clone() + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled.Add( 'Ensure','Present' ) + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled.Add( 'LoginCredential',$mockSqlLoginCredential ) + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled.Add( 'LoginPasswordExpirationEnabled',$false ) + + Set-TargetResource @setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 1 -Exactly + } + + It 'Should set PasswordPolicyEnforced on the specified SQL Login if it does not match the LoginPasswordPolicyEnforced parameter' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced = $setTargetResource_SqlLoginPresent.Clone() + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced.Add( 'Ensure','Present' ) + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced.Add( 'LoginCredential',$mockSqlLoginCredential ) + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced.Add( 'LoginPasswordPolicyEnforced',$false ) + + Set-TargetResource @setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 1 -Exactly + } + + It 'Should throw the correct error when creating a SQL Login if the LoginMode is not Mixed' { + $mockConnectSQL_LoginModeNormal = { + return New-Object Object | + Add-Member ScriptProperty Logins { + return @{ + 'Windows\User1' = ( New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value 'Windows\User1' -PassThru | + Add-Member -MemberType NoteProperty -Name 'LoginType' -Value 'WindowsUser' -PassThru | + Add-Member -MemberType ScriptMethod -Name Alter -Value {} -PassThru | + Add-Member -MemberType ScriptMethod -Name Drop -Value {} -PassThru -Force + ) + 'SqlLogin1' = ( New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value 'SqlLogin1' -PassThru | + Add-Member -MemberType NoteProperty -Name 'LoginType' -Value 'SqlLogin' -PassThru | + Add-Member -MemberType NoteProperty -Name 'MustChangePassword' -Value $false -PassThru | + Add-Member -MemberType NoteProperty -Name 'PasswordExpirationEnabled' -Value $true -PassThru | + Add-Member -MemberType NoteProperty -Name 'PasswordPolicyEnforced' -Value $true -PassThru | + Add-Member -MemberType ScriptMethod -Name Alter -Value {} -PassThru | + Add-Member -MemberType ScriptMethod -Name Drop -Value {} -PassThru -Force + ) + 'Windows\Group1' = ( New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value 'Windows\Group1' -PassThru | + Add-Member -MemberType NoteProperty -Name 'LoginType' -Value 'WindowsGroup' -PassThru | + Add-Member -MemberType ScriptMethod -Name Alter -Value {} -PassThru | + Add-Member -MemberType ScriptMethod -Name Drop -Value {} -PassThru -Force + ) + } + } -PassThru | + Add-Member -MemberType NoteProperty -Name LoginMode -Value 'Normal' -PassThru -Force + } + + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL_LoginModeNormal -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginAbsent_EnsurePresent = $setTargetResource_SqlLoginAbsent.Clone() + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'Ensure','Present' ) + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredential ) + + { Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsurePresent } | Should Throw 'IncorrectLoginMode' - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } + } + } - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\SqlUsers' - LoginType = 'WindowsGroup' + InModuleScope -ModuleName $script:DSCResourceName { + Describe "$($script:DSCResourceName)\Update-SQLServerLogin" { + Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName + + Context 'When the Login is altered' { + It 'Should silently alter the login' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Domain\User' ) + $login.LoginType = 'WindowsUser' + + { Update-SQLServerLogin -Login $login } | Should Not Throw + } + + It 'Should throw the correct error when altering the login fails' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Domain\User' ) + $login.LoginType = 'WindowsUser' + $login.MockLoginType = 'SqlLogin' + + { Update-SQLServerLogin -Login $login } | Should Throw 'AlterLoginFailed' + } } + } + + Describe "$($script:DSCResourceName)\New-SQLServerLogin" { + Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName + + Context 'When the Login is created' { + It 'Should silently create a Windows login' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Domain\User' ) + $login.LoginType = 'WindowsUser' + $login.MockLoginType = 'WindowsUser' + + { New-SQLServerLogin -Login $login } | Should Not Throw + } + + It 'Should silently create a SQL login' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) + $login.LoginType = 'SqlLogin' + $login.MockLoginType = 'SqlLogin' + + $createLoginParams = @{ + Login = $login + SecureString = ConvertTo-SecureString -String 'P@ssw0rd-12P@ssw0rd-12' -AsPlainText -Force + LoginCreateOptions = 'None' + } + + { New-SQLServerLogin @createLoginParams } | Should Not Throw + } + + It 'Should throw the correct error when login creation fails' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Domain\User' ) + $login.LoginType = 'WindowsUser' + $login.MockLoginType = 'SqlLogin' + + { New-SQLServerLogin -Login $login } | Should Throw 'LoginCreationFailedWindowsNotSpecified' + } + + It 'Should throw the correct error when password validation fails when creating a SQL Login' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) + $login.LoginType = 'SqlLogin' + + $createLoginParams = @{ + Login = $login + SecureString = ConvertTo-SecureString -String 'pw' -AsPlainText -Force + LoginCreateOptions = 'None' + } + + { New-SQLServerLogin @createLoginParams } | Should Throw 'PasswordValidationFailed' + } - It 'Should not throw an error when desired login type is a Windows Group' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'WindowsGroup' + It 'Should throw the correct error when creating a SQL Login fails' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Existing' ) + $login.LoginType = 'SqlLogin' + + $createLoginParams = @{ + Login = $login + SecureString = ConvertTo-SecureString -String 'P@ssw0rd-12P@ssw0rd-12' -AsPlainText -Force + LoginCreateOptions = 'None' } - } -ModuleName $script:DSCResourceName -Verifiable - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + { New-SQLServerLogin @createLoginParams } | Should Throw 'LoginCreationFailedFailedOperation' + } + + It 'Should throw the correct error when creating a SQL Login fails with an unhandled exception' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Unknown' ) + $login.LoginType = 'SqlLogin' + + $createLoginParams = @{ + Login = $login + SecureString = ConvertTo-SecureString -String 'P@ssw0rd-12P@ssw0rd-12' -AsPlainText -Force + LoginCreateOptions = 'None' + } + + { New-SQLServerLogin @createLoginParams } | Should Throw 'LoginCreationFailedSqlNotSpecified' + } } + } - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Name = 'COMPANY\UnknownUser' - LoginType = 'SqlLogin' + Describe "$($script:DSCResourceName)\Remove-SQLServerLogin" { + Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName + + Context 'When the Login is dropped' { + It 'Should silently drop the login' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Domain\User' ) + $login.LoginType = 'WindowsUser' + + { Remove-SQLServerLogin -Login $login } | Should Not Throw + } + + It 'Should throw the correct error when dropping the login fails' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Domain\User' ) + $login.LoginType = 'WindowsUser' + $login.MockLoginType = 'SqlLogin' + + { Remove-SQLServerLogin -Login $login } | Should Throw 'DropLoginFailed' + } } + } + + Describe "$($script:DSCResourceName)\Set-SQLServerLoginPassword" { + Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName - It 'Should not call the function Remove-SqlLogin when desired state is already absent' { - # Mock the return value from the Get-method, because Test-method is ran at the end of the Set-method to validate that the removal (in this case) was successful. - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Absent' + Context 'When the password is set on an existing login' { + It 'Should silently set the password' { + $setPwParams = @{ + Login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) + SecureString = ConvertTo-SecureString -String 'P@ssw0rd-12P@ssw0rd-12' -AsPlainText -Force } - } -ModuleName $script:DSCResourceName -Verifiable + + { Set-SQLServerLoginPassword @setPwParams } | Should Not Throw + } - Mock -CommandName Remove-SqlLogin -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + It 'Should throw the correct error when password validation fails' { + $setPwParams = @{ + Login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) + SecureString = ConvertTo-SecureString -String 'pw' -AsPlainText -Force + } + + { Set-SQLServerLoginPassword @setPwParams } | Should Throw 'PasswordValidationFailed' + } - Set-TargetResource @testParameters + It 'Should throw the correct error when changing the password fails' { + $setPwParams = @{ + Login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) + SecureString = ConvertTo-SecureString -String 'reused' -AsPlainText -Force + } + + { Set-SQLServerLoginPassword @setPwParams } | Should Throw 'PasswordChangeFailed' + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Remove-SqlLogin -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + It 'Should throw the correct error when changing the password fails' { + $setPwParams = @{ + Login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) + SecureString = ConvertTo-SecureString -String 'other' -AsPlainText -Force + } + + { Set-SQLServerLoginPassword @setPwParams } | Should Throw 'PasswordChangeFailed' + } } } - - Assert-VerifiableMocks } } finally { #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Restore-TestEnvironment -TestEnvironment $TestEnvironment #endregion } diff --git a/Tests/Unit/Stubs/SMO.cs b/Tests/Unit/Stubs/SMO.cs index 84b9a1086..01f91d53e 100644 --- a/Tests/Unit/Stubs/SMO.cs +++ b/Tests/Unit/Stubs/SMO.cs @@ -2,11 +2,23 @@ using System; using System.Collections.Generic; +using System.Security; +using System.Runtime.InteropServices; namespace Microsoft.SqlServer.Management.Smo { #region Public Enums + // TypeName: Microsoft.SqlServer.Management.Smo.LoginCreateOptions + // Used by: + // MSFT_xSQLServerLogin.Tests.ps1 + public enum LoginCreateOptions + { + None = 0, + IsHashed = 1, + MustChange = 2 + } + // TypeName: Microsoft.SqlServer.Management.Smo.LoginType // BaseType: Microsoft.SqlServer.Management.Smo.ScriptNameObjectBase // Used by: @@ -184,17 +196,80 @@ public class Login { private bool _mockPasswordPassed = false; - public Login( Server server, string name ) { - this.Name = name; - } - - public Login( Object server, string name ) { - this.Name = name; - } - public string Name; public LoginType LoginType = LoginType.Unknown; + public bool MustChangePassword = false; + public bool PasswordPolicyEnforced = false; + public bool PasswordExpirationEnabled = false; + + public string MockName; + public LoginType MockLoginType; + + public Login( Server server, string name ) + { + this.Name = name; + } + + public Login( Object server, string name ) + { + this.Name = name; + } + + public void Alter() + { + if( !( String.IsNullOrEmpty(this.MockName) ) ) + { + if(this.MockName != this.Name) + { + throw new Exception(); + } + } + if( !( String.IsNullOrEmpty(this.MockLoginType.ToString()) ) ) + { + if( this.MockLoginType != this.LoginType ) + { + throw new Exception(this.MockLoginType.ToString()); + } + } + } + + public void ChangePassword( SecureString secureString ) + { + IntPtr valuePtr = IntPtr.Zero; + try + { + valuePtr = Marshal.SecureStringToGlobalAllocUnicode(secureString); + if ( Marshal.PtrToStringUni(valuePtr) == "pw" ) + { + throw new FailedOperationException ( + "FailedOperationException", + new SmoException ( + "SmoException", + new SqlServerManagementException ( + "SqlServerManagementException", + new Exception ( + "Password validation failed. The password does not meet Windows policy requirements because it is too short." + ) + ) + ) + ); + } + else if ( Marshal.PtrToStringUni(valuePtr) == "reused" ) + { + throw new FailedOperationException (); + } + else if ( Marshal.PtrToStringUni(valuePtr) == "other" ) + { + throw new Exception (); + } + } + finally + { + Marshal.ZeroFreeGlobalAllocUnicode(valuePtr); + } + } + public void Create() { if( this.LoginType == LoginType.Unknown ) { @@ -204,17 +279,90 @@ public void Create() if( this.LoginType == LoginType.SqlLogin && _mockPasswordPassed != true ) { throw new System.Exception( "Called Create() method for the LoginType 'SqlLogin' but called with the wrong overloaded method. Did not pass the password with the Create() method." ); } + + if( !( String.IsNullOrEmpty(this.MockName) ) ) + { + if(this.MockName != this.Name) + { + throw new Exception(); + } + } + + if( !( String.IsNullOrEmpty(this.MockLoginType.ToString()) ) ) + { + if( this.MockLoginType != this.LoginType ) + { + throw new Exception(this.MockLoginType.ToString()); + } + } } - public void Create( String secureString ) + public void Create( SecureString secureString ) { _mockPasswordPassed = true; this.Create(); } + public void Create( SecureString password, LoginCreateOptions options ) + { + IntPtr valuePtr = IntPtr.Zero; + try + { + valuePtr = Marshal.SecureStringToGlobalAllocUnicode(password); + if ( Marshal.PtrToStringUni(valuePtr) == "pw" ) + { + throw new FailedOperationException ( + "FailedOperationException", + new SmoException ( + "SmoException", + new SqlServerManagementException ( + "SqlServerManagementException", + new Exception ( + "Password validation failed. The password does not meet Windows policy requirements because it is too short." + ) + ) + ) + ); + } + else if ( this.Name == "Existing" ) + { + throw new FailedOperationException ( "The login already exists" ); + } + else if ( this.Name == "Unknown" ) + { + throw new Exception (); + } + else + { + _mockPasswordPassed = true; + + this.Create(); + } + } + finally + { + Marshal.ZeroFreeGlobalAllocUnicode(valuePtr); + } + } + public void Drop() { + if( !( String.IsNullOrEmpty(this.MockName) ) ) + { + if(this.MockName != this.Name) + { + throw new Exception(); + } + } + + if( !( String.IsNullOrEmpty(this.MockLoginType.ToString()) ) ) + { + if( this.MockLoginType != this.LoginType ) + { + throw new Exception(this.MockLoginType.ToString()); + } + } } } @@ -267,14 +415,16 @@ public void Drop() // xSQLServerDatabaseRole.Tests.ps1 public class User { - public User( Server server, string name ) { + public User( Server server, string name ) + { this.Name = name; } - public User( Object server, string name ) { + public User( Object server, string name ) + { this.Name = name; - } - + } + public string Name; public string Login; @@ -287,5 +437,44 @@ public void Drop() } } + // TypeName: Microsoft.SqlServer.Management.Smo.SqlServerManagementException + // BaseType: System.Exception + // Used by: + // xSqlServerLogin.Tests.ps1 + public class SqlServerManagementException : Exception + { + public SqlServerManagementException () : base () {} + + public SqlServerManagementException (string message) : base (message) {} + + public SqlServerManagementException (string message, Exception inner) : base (message, inner) {} + } + + // TypeName: Microsoft.SqlServer.Management.Smo.SmoException + // BaseType: Microsoft.SqlServer.Management.Smo.SqlServerManagementException + // Used by: + // xSqlServerLogin.Tests.ps1 + public class SmoException : SqlServerManagementException + { + public SmoException () : base () {} + + public SmoException (string message) : base (message) {} + + public SmoException (string message, SqlServerManagementException inner) : base (message, inner) {} + } + + // TypeName: Microsoft.SqlServer.Management.Smo.FailedOperationException + // BaseType: Microsoft.SqlServer.Management.Smo.SmoException + // Used by: + // xSqlServerLogin.Tests.ps1 + public class FailedOperationException : SmoException + { + public FailedOperationException () : base () {} + + public FailedOperationException (string message) : base (message) {} + + public FailedOperationException (string message, SmoException inner) : base (message, inner) {} + } + #endregion Public Classes } diff --git a/en-US/xSQLServer.strings.psd1 b/en-US/xSQLServer.strings.psd1 index 98adc8fa5..9ba278863 100644 --- a/en-US/xSQLServer.strings.psd1 +++ b/en-US/xSQLServer.strings.psd1 @@ -39,4 +39,17 @@ ConfigurationRestartRequired = Configuration option '{0}' has been updated, but # AlwaysOnService AlterAlwaysOnServiceFailed = Failed to ensure Always On is {0} on the instance '{1}'. + +# Login +PasswordValidationFailed = Creation of the login '{0}' failed due to the following error: {1} +LoginCreationFailedFailedOperation = Creation of the login '{0}' failed due to a failed operation. +LoginCreationFailedSqlNotSpecified = Creation of the SQL login '{0}' failed due to an unspecified error. +LoginCreationFailedWindowsNotSpecified = Creation of the Windows login '{0}' failed due to an unspecified error. +LoginTypeNotImplemented = The login type '{0}' is not implemented in this resource. +IncorrectLoginMode = The instance '{0}\{1}' is currently in '{2}' authentication mode. To create a SQL Login, it must be set to 'Mixed' authentication mode. +LoginCredentialNotFound = The credential for the SQL Login '{0}' was not found. +PasswordChangeFailed = Setting the password failed for the SQL Login '{0}'. +AlterLoginFailed = Altering the login '{0}' failed. +CreateLoginFailed = Creating the login '{0}' failed. +DropLoginFailed = Dropping the login '{0}' failed. '@ From ee589bb1af565855ea34f697d1c5ed39cb357b29 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 6 Jan 2017 20:19:09 +0100 Subject: [PATCH 11/31] Typo in CHANGELOG.md after recent merge (#299) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4a3b2a35..efd0a594c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,7 @@ - Changes to the unit test for resource - xSQLServerSetup - Added test coverage for helper function Copy-ItemWithRoboCopy -- Changes to xSSQLServerLogin +- Changes to xSQLServerLogin - Removed ShouldProcess statements - Added the ability to enforce password policies on SQL logins From ed852a1a11b84c413c3cbbbcdbacaa421317bfe0 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 7 Jan 2017 12:03:58 +0100 Subject: [PATCH 12/31] README.md: Changes the layout to match HQRM resources (#298) - Changes to README.md - Added more detailed text explaining what operating systemes WMF5.0 can be installed on. - Fixed alphabetical order, one resource was not in the right order. - Verified all resource schema files with the README.md and fixed any errors. - Change the layout of the README.md to closer match the one of PSDscResources. - Added examples that follow the new model. --- CHANGELOG.md | 3 + README.md | 923 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 644 insertions(+), 282 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efd0a594c..c4c6306fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ - Now the correct product name is written in the installation section, and a typo was also fixed. - Fixed a typo in the Requirements section. - Added link to Examples folder in the Examples section. + - Change the layout of the README.md to closer match the one of PSDscResources + - Added more detailed text explaining what operating systemes WMF5.0 can be installed on. + - Verified all resource schema files with the README.md and fixed some errors (descriptions was not verified). - Changes to xSQLServerSetup - The resource no longer uses Win32_Product WMI class when evaluating if SQL Server Management Studio is installed. See article [kb974524](https://support.microsoft.com/en-us/kb/974524) for more information. - Now it uses CIM cmdlets to get information from WMI classes. diff --git a/README.md b/README.md index 177c97e6e..ef20f8977 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,8 @@ Get-DscResource -Module xSQLServer ## Requirements -The minimum PowerShell version required is 4.0, which ships in Windows 8.1 or Windows Server 2012 R2 (or higher versions). But PowerShell 4.0 can also be installed on Windows Server 2008 R2. -The preferred version is PowerShell 5.0 or higher, which ships with Windows 10 or Windows Server 2016. +The minimum Windows Management Framework (PowerShell) version required is 4.0, which ships in Windows 8.1 or Windows Server 2012 R2 (or higher versions). But Windows Management Framework (PowerShell) 4.0 can also be installed on Windows Server 2008 R2. +The preferred Windows Management Framework (PowerShell) version is 5.0 or higher, which ships with Windows 10 or Windows Server 2016, but can also be installed on Windows 7 SP1, Windows 8.1, Windows Server 2008 R2 SP1, Windows Server 2012 and Windows Server 2012 R2. ## Examples @@ -66,9 +66,9 @@ A full list of changes in each version can be found in the [change log](CHANGELO * [**xSQLServerAvailabilityGroupListener**](#xsqlserveravailabilitygrouplistener) Create or remove an availability group listener. * [**xSQLServerConfiguration**](#xsqlserverconfiguration) resource to manage [SQL Server Configuration Options](https://msdn.microsoft.com/en-us/library/ms189631.aspx) * [**xSQLServerDatabase**](#xsqlserverdatabase) resource to manage ensure database is present or absent -* [**xSQLServerDatabaseRole**](#xsqlserverdatabaserole) resource to manage SQL database roles * [**xSQLServerDatabaseOwner**](#xsqlserverdatabaseowner) resource to manage SQL database owners * [**xSQLServerDatabasePermissions**](#xsqlserverdatabasepermissions) resource to manage SQL database permissions +* [**xSQLServerDatabaseRole**](#xsqlserverdatabaserole) resource to manage SQL database roles * [**xSQLServerEndpoint**](#xsqlserverendpoint) resource to ensure database endpoint is present or absent * [**xSQLServerEndpointPermission**](#xsqlserverendpointpermission) Grant or revoke permission on the endpoint. * [**xSQLServerEndpointState**](#xsqlserverendpointstate) Change state of the endpoint. @@ -89,374 +89,733 @@ A full list of changes in each version can be found in the [change log](CHANGELO ### xSQLAOGroupEnsure -* **Ensure**: (Key) Determines whether the availability group should be added or removed. -* **AvailabilityGroupName** (Key) Name for availability group. -* **AvailabilityGroupNameListener** Listener name for availability group. -* **AvailabilityGroupNameIP** List of IP addresses associated with listener. -* **AvailabilityGroupSubMask** Network subnetmask for listener. -* **AvailabilityGroupPort** Port availability group should listen on. -* **ReadableSecondary** Mode secondaries should operate under (None, ReadOnly, ReadIntent). -* **AutoBackupPreference** Where backups should be backed up from (Primary, Secondary). -* **BackupPriority** The percentage weight for backup prority (default 50). -* **EndPointPort** The TCP port for the SQL AG Endpoint (default 5022). -* **SQLServer**: The SQL Server for the database. -* **SQLInstance**: The SQL instance for the database. -* **SetupCredential**: (Required) Credential to be used to Grant Permissions on SQL Server, set this to $null to use Windows Authentication. +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2012 or later. +* Target machine must have access to the Active Directory module. +* The credentials provided in the parameter `SetupCredential` must have the right **Create Computer Object** in the origanization unit (OU) in which the Cluster Name Object (CNO) resides. + +#### Parameters + +* **[String] Ensure** _(Key)_: Determines whether the availability group should be added or removed. { Present | Absent }. +* **[String] AvailabilityGroupName**_(Key)_: Name for availability group. +* **[String] AvailabilityGroupNameListener** _(Write)_: Listener name for availability group. +* **[String[]] AvailabilityGroupNameIP** _(Write)_: List of IP addresses associated with listener. +* **[String[]] AvailabilityGroupSubMask** _(Write)_: Network subnetmask for listener. +* **[Unint32] AvailabilityGroupPort** _(Write)_: Port availability group should listen on. +* **[String] ReadableSecondary** _(Write)_: Mode secondaries should operate under (None, ReadOnly, ReadIntent). { None | *ReadOnly* | ReadIntent }. +* **[String] AutoBackupPreference** _(Write)_: Where backups should be backed up from (Primary, Secondary). { *Primary* | Secondary }. +* **[Uint32] BackupPriority** _(Write)_: The percentage weight for backup prority (default 50). +* **[Uint32] EndPointPort** _(Write)_: The TCP port for the SQL AG Endpoint (default 5022). +* **[String] SQLServer** _(Write)_: The SQL Server for the database. +* **[String] SQLInstance** _(Write)_: The SQL instance for the database. +* **[PSCredential] SetupCredential** _(Required)_: Credential to be used to Grant Permissions on SQL Server, set this to $null to use Windows Authentication. + +#### Examples + +None. ### xSQLAOGroupJoin -* **Ensure**: (key) If the replica should be joined ('Present') to the Availability Group or not joined ('Absent') to the Availability Group. -* **AvailabilityGroupName** (key) The name Availability Group to join. -* **SQLServer**: Name of the SQL server to be configured. -* **SQLInstanceName**: Name of the SQL instance to be configured. -* **SetupCredential**: (Required) Credential to be used to Grant Permissions in SQL. +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine2012 or later. + +#### Parameters + +* **[String] Ensure** _(Key)_: If the replica should be joined ('Present') to the Availability Group or not joined ('Absent') to the Availability Group. { Present | Absent }. +* **[String] AvailabilityGroupName** _(Key)_: The name Availability Group to join. +* **[String] SQLServer** _(Write)_: Name of the SQL server to be configured. +* **[String] SQLInstanceName** _(Write)_: Name of the SQL instance to be configured. +* **[PSCredential] SetupCredential** _(Required)_: Credential to be used to Grant Permissions in SQL. + +#### Examples + +None. ### xSQLDatabaseRecoveryModel -* **DatabaseName**: (key) The SQL database name -* **SQLServerInstance**: (Required) The SQL server and instance -* **RecoveryModel**: (Required) Recovery Model (Full, Simple, BulkLogged) +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] DatabaseName** _(Key)_: The SQL database name +* **[String] SQLServerInstance** _(Required)_: The SQL server and instance +* **[String] RecoveryModel** _(Required)_: Recovery Model (Full, Simple, BulkLogged). { *Full* | Simple | BulkLogged }. + +#### Examples + +None. ### xSQLServerAlias -* **Ensure**: Determines whether the alias should be added or removed. Default value is 'Present' -* **Name**: (Key) The name of Alias (e.g. svr01\inst01). -* **ServerName**: (Key) The SQL Server you are aliasing (the netbios name or FQDN). -* **Protocol**: Protocol to use when connecting. Valid values are 'TCP' or 'NP' (Named Pipes). Default value is 'TCP'. -* **TCPPort**: The TCP port SQL is listening on. Only used when protocol is set to 'TCP'. Default value is port 1433. -* **UseDynamicTcpPort**: The UseDynamicTcpPort specify that the Net-Library will determine the port dynamically. The port specified in Port number will not be used. Default value is '$false'. -* **PipeName**: (Read) Named Pipes path from the Get-TargetResource method. +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. + +#### Parameters + +* **[String] Name** _(Key)_: The name of Alias (e.g. svr01\inst01). +* **[String] ServerName** _(Key)_: The SQL Server you are aliasing (the netbios name or FQDN). +* **[String] Ensure** _(Write)_: Determines whether the alias should be added or removed. Default value is 'Present'. { *Present* | Absent }. +* **[String] Protocol** _(Write)_: Protocol to use when connecting. Valid values are 'TCP' or 'NP' (Named Pipes). Default value is 'TCP'. { *TCP* | NP }. +* **[Uint16] TCPPort** _(Write)_: The TCP port SQL is listening on. Only used when protocol is set to 'TCP'. Default value is port 1433. +* **[Boolean] UseDynamicTcpPort** _(Write)_: The UseDynamicTcpPort specify that the Net-Library will determine the port dynamically. The port specified in Port number will not be used. Default value is '$false'. + +#### Read-Only Properties from Get-TargetResource + +* **[String] PipeName** _(Read)_: Named Pipes path from the Get-TargetResource method. + +#### Examples + +* [Add an SQL Server alias](/Examples/Resources/xSQLServerAlias/1-AddSQLServerAlias.ps1) +* [Remove an SQL Server alias](/Examples/Resources/xSQLServerAlias/2-RemoveSQLServerAlias.ps1) ### xSQLServerAlwaysOnService -* **Ensure**: (Required) An enumerated value that describes if SQL server should have AlwaysOn property present or absent. -* **SQLServer**: (Key) The hostname of the SQL Server to be configured. -* **SQLInstance**: (Key) Name of the SQL instance to be configured. -* **RestartTimeout**: The length of time, in seconds, to wait for the service to restart. Default is 120 seconds. +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2012 or later. + +#### Parameters + +* **[String] SQLServer** _(Key)_: The hostname of the SQL Server to be configured. +* **[String] SQLInstance** _(Key)_: Name of the SQL instance to be configured. +* **[String] Ensure** _(Required)_: An enumerated value that describes if SQL server should have AlwaysOn property present or absent. { Present | Absent }. +* **[Sint32] RestartTimeout** _(Write)_: The length of time, in seconds, to wait for the service to restart. Default is 120 seconds. + +#### Examples + +None. ### xSQLServerAvailabilityGroupListener -*This resource requires that the CNO has been delegated the right `Create computer object` on the organizational unit (OU) in which the CNO resides.* +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2012 or later. +* Target machine must have access to the SQLPS PowerShell module or the SqlServer PowerShell module. +* Requires that the Cluster name Object (CNO) has been delegated the right _Create Computer Object_ in the organizational unit (OU) in which the Cluster Name Object (CNO) resides. + +#### Parameters -* **InstanceName** The SQL Server instance name of the primary replica. -* **NodeName** The host name or FQDN of the primary replica. -* **Ensure** If the availability group listener should be present or absent. -* **Name** The name of the availability group listener, max 15 characters. This name will be used as the Virtual Computer Object (VCO). -* **AvailabilityGroup** The name of the availability group to which the availability group listener is or will be connected. -* **IpAddress** The IP address used for the availability group listener, in the format 192.168.10.45/255.255.252.0. If using DCHP, set to the first IP-address of the DHCP subnet, in the format 192.168.8.1/255.255.252.0. Must be valid in the cluster-allowed IP range. -* **Port** The port used for the availability group listener. -* **DHCP** If DHCP should be used for the availability group listener instead of static IP address. +* **[String] InstanceName** _(Key)_: The SQL Server instance name of the primary replica. +* **[String] AvailabilityGroup** _(Key)_: The name of the availability group to which the availability group listener is or will be connected. +* **[String] NodeName** _(Write)_: The host name or FQDN of the primary replica. +* **[String] Ensure** _(Write)_: If the availability group listener should be present or absent. { Present | Absent }. +* **[String] Name** _(Write)_: The name of the availability group listener, max 15 characters. This name will be used as the Virtual Computer Object (VCO). +* **[String[]] IpAddress** _(Write)_: The IP address used for the availability group listener, in the format 192.168.10.45/255.255.252.0. If using DCHP, set to the first IP-address of the DHCP subnet, in the format 192.168.8.1/255.255.252.0. Must be valid in the cluster-allowed IP range. +* **[Uint16] Port** _(Write)_: The port used for the availability group listener. +* **[Boolean] DHCP** _(Write)_: If DHCP should be used for the availability group listener instead of static IP address. + +#### Examples + +None. ### xSQLServerConfiguration -* **SQLServer**: (Key) The hostname of the SQL Server to be configured -* **SQLInstanceName**: (Write) Name of the SQL instance to be configured. Default is 'MSSQLSERVER' -* **OptionName**: (Key) The name of the SQL configuration option to be checked. For all possible values reference [MSDN](https://msdn.microsoft.com/en-us/library/ms189631.aspx) or run sp_configure. -* **OptionValue**: (Required) The desired value of the SQL configuration option -* **RestartService**: Determines whether the instance should be restarted after updating the configuration option -* **RestartTimeout**: The length of time, in seconds, to wait for the service to restart. Default is 120 seconds. +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] SQLServer** _(Key)_: The hostname of the SQL Server to be configured +* **[String] OptionName** _(Key)_: The name of the SQL configuration option to be checked. For all possible values reference [MSDN](https://msdn.microsoft.com/en-us/library/ms189631.aspx) or run sp_configure. +* **[Sint32] OptionValue** _(Required)_: The desired value of the SQL configuration option +* **[String] SQLInstanceName** _(Write)_: Name of the SQL instance to be configured. Default is 'MSSQLSERVER' +* **[Boolean] RestartService** _(Write)_: Determines whether the instance should be restarted after updating the configuration option +* **[Sint32] RestartTimeout** _(Write)_: The length of time, in seconds, to wait for the service to restart. Default is 120 seconds. + +#### Examples + +None. ### xSQLServerDatabase -* **Database**: (key) Database to be created or dropped -* **Ensure**: (Default = 'Present') An enumerated value that describes if Database is to be present or absent. -* **SQLServer**: (key) The SQL Server for the database -* **SQLInstance**: (key) The SQL instance for the database +No description. -### xSQLServerDatabaseRole +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters -* **Ensure**: If 'Present' (the default value) then the login (user) will be added to the role(s). If 'Absent' then the login (user) will be removed from the role(s). -* **Name**: (Key) The name of the login that will become a member, or removed as a member, of the role(s). -* **SQLServer**: (Key) The SQL server on which the instance exist. -* **SQLInstanceName**: (Key) The SQL instance in which the database exist. -* **Database**: (Key) The database in which the login (user) and role(s) exist. -* **Role**: One or more roles to which the login (user) will be added or removed. +* **[String] SQLServer** _(Key)_: The SQL Server for the database +* **[String] SQLInstance** _(Key)_: The SQL instance for the database +* **[String] Database** _(Key)_: Database to be created or dropped +* **[String] Ensure** _(Write)_: If the values should be present or absent. Valid values are 'Present' or 'Absent'. Default Value is 'Present'. { *Present* | Absent }. + +#### Examples + +None. ### xSQLServerDatabaseOwner -* **Database**: (Key) The SQL Database -* **Name**: (Required) The name of the SQL login for the owner -* **SQLServer**: The SQL Server for the database -* **SQLInstance**: The SQL instance for the database +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] Database** _(Key)_: The SQL Database +* **[String] Name** _(Required)_: The name of the SQL login for the owner +* **[String] SQLServer** _(Write)_: The SQL Server for the database +* **[String] SQLInstance** _(Write)_: The SQL instance for the database + +#### Examples + +* [Set database owner](/Examples/Resources/xSQLServerDatabaseOwner/1-SetDatabaseOwner.ps1) ### xSQLServerDatabasePermissions -* **Database**: (Key) The SQL Database -* **Name**: (Required) The name of permissions for the SQL database -* **Permissions**: (Required) The set of Permissions for the SQL database -* **SQLServer**: The SQL Server for the database -* **SQLInstanceName**: The SQL instance for the database +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] Database** _(Key)_: The SQL Database +* **[String] Name** _(Required)_: The name of permissions for the SQL database +* **[String[]] Permissions** _(Required)_: The set of Permissions for the SQL database +* **[String] SQLServer** _(Write)_: The SQL Server for the database +* **[String] SQLInstanceName** _(Write)_: The SQL instance for the database + +#### Examples + +None. + +### xSQLServerDatabaseRole + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] Name** _(Key)_: The name of the login that will become a member, or removed as a member, of the role(s). +* **[String] SQLServer** _(Key)_: The SQL server on which the instance exist. +* **[String] SQLInstanceName** _(Key)_: The SQL instance in which the database exist. +* **[String] Database** _(Key)_: The database in which the login (user) and role(s) exist. +* **[String] Ensure** _(Write)_: If 'Present' (the default value) then the login (user) will be added to the role(s). If 'Absent' then the login (user) will be removed from the role(s). { *Present* | Absent }. +* **[String[]] Role**_(Required): One or more roles to which the login (user) will be added or removed. + +#### Examples + +None. ### xSQLServerEndpoint -* **EndPointName**: Name for endpoint to be created on SQL Server -* **Ensure**: (key) An enumerated value that describes if endpoint is to be present or absent on SQL Server -* **Port**: Port Endpoint should listen on -* **AuthorizedUser**: User who should have connect ability to endpoint -* **SQLServer**: The SQL Server for the database -* **SQLInstance**: The SQL instance for the database +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] EndPointName** _(Key)_: Name for endpoint to be created on SQL Server +* **[String] Ensure** _(Write)_: An enumerated value that describes if endpoint is to be present or absent on SQL Server. { Present | Absent }. +* **[Uint32] Port** _(Write)_: Port Endpoint should listen on +* **[String] AuthorizedUser** _(Write)_: User who should have connect ability to endpoint +* **[String] SQLServer** _(Write)_: The SQL Server for the database +* **[String] SQLInstance** _(Write)_: The SQL instance for the database + +#### Examples + +None. ### xSQLServerEndpointPermission -* **InstanceName** The SQL Server instance name. -* **NodeName** The host name or FQDN. -* **Ensure** If the permission should be present or absent. -* **Name** The name of the endpoint. -* **Principal** The login to which permission will be set. -* **Permission** The permission to set for the login. Valid value for permission are only CONNECT. +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. +* Target machine must have access to the SQLPS PowerShell module or the SqlServer PowerShell module. + +#### Parameters + +* **[String] InstanceName** _(Key)_: The SQL Server instance name. +* **[String] NodeName** _(Required)_: The host name or FQDN. +* **[String] Ensure** _(Write)_: If the permission should be present or absent. { Present | Absent }. +* **[String] Name** _(Required)_: The name of the endpoint. +* **[String] Principal** _(Key)_: The login to which permission will be set. +* **[String] Permission** _(Write)_: The permission to set for the login. Valid value for permission are only CONNECT. { Connect }. + +#### Examples + +None. ### xSQLServerEndpointState -* **InstanceName** The SQL Server instance name. -* **NodeName** The host name or FQDN. -* **Name** The name of the endpoint. -* **State** The state of the endpoint. Valid states are Started, Stopped or Disabled. +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. +* Target machine must have access to the SQLPS PowerShell module or the SqlServer PowerShell module. + +#### Parameters + +* **[String] InstanceName** _(Key)_: The SQL Server instance name. +* **[String] NodeName** _(Required)_: The host name or FQDN. +* **[String] Name** _(Required)_: The name of the endpoint. +* **[String] State** _(Write)_: The state of the endpoint. Valid states are Started, Stopped or Disabled. { Started | Stopped | Disabled }. + +#### Examples + +None. ### xSQLServerFailoverClusterSetup -* **Action**: (Key) { Prepare | Complete } -* **SourcePath**: (Required) UNC path to the root of the source files for installation. -* **SourceFolder**: Folder within the source path containing the source files for installation. -* **SetupCredential**: (Required) Credential to be used to perform the installation. -* **SourceCredential**: Credential to be used to access SourcePath -* **SuppressReboot**: Suppresses reboot -* **ForceReboot**: Forces Reboot -* **Features**: (Required) SQL features to be installed. -* **InstanceName**: (Key) SQL instance to be installed. -* **InstanceID**: SQL instance ID, if different from InstanceName. -* **PID**: Product key for licensed installations. -* **UpdateEnabled**: Enabled updates during installation. -* **UpdateSource**: Source of updates to be applied during installation. -* **SQMReporting**: Enable customer experience reporting. -* **ErrorReporting**: Enable error reporting. -* **FailoverClusterGroup**: Name of the resource group to be used for the SQL Server failover cluster. -* **FailoverClusterNetworkName**: (Required) Network name for the SQL Server failover cluster. -* **FailoverClusterIPAddress**: IPv4 address for the SQL Server failover cluster. -* **InstallSharedDir**: Installation path for shared SQL files. -* **InstallSharedWOWDir**: Installation path for x86 shared SQL files. -* **InstanceDir**: Installation path for SQL instance files. -* **SQLSvcAccount**: Service account for the SQL service. -* **SQLSvcAccountUsername**: Output user name for the SQL service. -* **AgtSvcAccount**: Service account for the SQL Agent service. -* **AgtSvcAccountUsername**: Output user name for the SQL Agent service. -* **SQLCollation**: Collation for SQL. -* **SQLSysAdminAccounts**: Array of accounts to be made SQL administrators. -* **SecurityMode**: SQL security mode. -* **SAPwd**: SA password, if SecurityMode=SQL. -* **InstallSQLDataDir**: Root path for SQL database files. -* **SQLUserDBDir**: Path for SQL database files. -* **SQLUserDBLogDir**: Path for SQL log files. -* **SQLTempDBDir**: Path for SQL TempDB files. -* **SQLTempDBLogDir**: Path for SQL TempDB log files. -* **SQLBackupDir**: Path for SQL backup files. -* **ASSvcAccount**: Service account for Analysis Services service. -* **ASSvcAccountUsername**: Output user name for the Analysis Services service. -* **ASCollation**: Collation for Analysis Services. -* **ASSysAdminAccounts**: Array of accounts to be made Analysis Services admins. -* **ASDataDir**: Path for Analysis Services data files. -* **ASLogDir**: Path for Analysis Services log files. -* **ASBackupDir**: Path for Analysis Services backup files. -* **ASTempDir**: Path for Analysis Services temp files. -* **ASConfigDir**: Path for Analysis Services config. -* **ISSvcAccount**: Service account for Integration Services service. -* **ISSvcAccountUsername**: Output user name for the Integration Services service. -* **ISFileSystemFolder**: File system folder for Integration Services. +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 R2 or later. + +#### Parameters + +* **[String] Action** _(Key)_: Prepare or Complete. { Prepare | Complete }. +* **[String] InstanceName** _(Key)_: SQL instance to be installed. +* **[String] Features** _(Required)_: SQL features to be installed. +* **[PSCredential] SetupCredential** _(Required)_: Credential to be used to perform the installation. +* **[String] FailoverClusterNetworkName** _(Required)_: Network name for the SQL Server failover cluster. +* **[PSCredential] SQLSvcAccount** _(Required)_: Service account for the SQL service. +* **[String] SourcePath** _(Write)_: UNC path to the root of the source files for installation. +* **[String] SourceFolder** _(Write)_: Folder within the source path containing the source files for installation. +* **[PSCredential] SourceCredential** _(Write)_: Credential to be used to access SourcePath +* **[Boolean] SuppressReboot** _(Write)_: Suppresses reboot +* **[Boolean] ForceReboot** _(Write)_: Forces Reboot +* **[String] InstanceID** _(Write)_: SQL instance ID, if different from InstanceName. +* **[String] PID** _(Write)_: Product key for licensed installations. +* **[String] UpdateEnabled** _(Write)_: Enabled updates during installation. +* **[String] UpdateSource** _(Write)_: Source of updates to be applied during installation. +* **[String] SQMReporting** _(Write)_: Enable customer experience reporting. +* **[String] ErrorReporting** _(Write)_: Enable error reporting. +* **[String] FailoverClusterGroup** _(Write)_: Name of the resource group to be used for the SQL Server failover cluster. +* **[String] FailoverClusterIPAddress** _(Write)_: IPv4 address for the SQL Server failover cluster. +* **[String] InstallSharedDir** _(Write)_: Installation path for shared SQL files. +* **[String] InstallSharedWOWDir** _(Write)_: Installation path for x86 shared SQL files. +* **[String] InstanceDir** _(Write)_: Installation path for SQL instance files. +* **[PSCredential] AgtSvcAccount** _(Write)_: Service account for the SQL Agent service. +* **[String] SQLCollation** _(Write)_: Collation for SQL. +* **[String[]] SQLSysAdminAccounts** _(Write)_: Array of accounts to be made SQL administrators. +* **[String] SecurityMode** _(Write)_: SQL security mode. +* **[PSCredential] SAPwd** _(Write)_: SA password, if SecurityMode=SQL. +* **[String] InstallSQLDataDir** _(Write)_: Root path for SQL database files. +* **[String] SQLUserDBDir** _(Write)_: Path for SQL database files. +* **[String] SQLUserDBLogDir** _(Write)_: Path for SQL log files. +* **[String] SQLTempDBDir** _(Write)_: Path for SQL TempDB files. +* **[String] SQLTempDBLogDir** _(Write)_: Path for SQL TempDB log files. +* **[String] SQLBackupDir** _(Write)_: Path for SQL backup files. +* **[PSCredential] ASSvcAccount** _(Write)_: Service account for Analysis Services service. +* **[String] ASCollation** _(Write)_: Collation for Analysis Services. +* **[String[]] ASSysAdminAccounts** _(Write)_: Array of accounts to be made Analysis Services admins. +* **[String] ASDataDir** _(Write)_: Path for Analysis Services data files. +* **[String] ASLogDir** _(Write)_: Path for Analysis Services log files. +* **[String] ASBackupDir** _(Write)_: Path for Analysis Services backup files. +* **[String] ASTempDir** _(Write)_: Path for Analysis Services temp files. +* **[String] ASConfigDir** _(Write)_: Path for Analysis Services config. +* **[PSCredential] ISSvcAccount** _(Write)_: Service account for Integration Services service. +* **[String] ISFileSystemFolder** _(Write)_: File system folder for Integration Services. + +#### Read-Only Properties from Get-TargetResource + +* **[String] SQLSvcAccountUsername** _(Read)_: Output user name for the SQL service. +* **[String] AgtSvcAccountUsername** _(Read)_: Output user name for the SQL Agent service. +* **[String] ASSvcAccountUsername** _(Read)_: Output user name for the Analysis Services service. +* **[String] ISSvcAccountUsername** _(Read)_: Output user name for the Integration Services service. + +#### Examples + +None. ### xSQLServerFirewall -* **Ensure**: (Key) Ensures that SQL firewall rules are **Present** or **Absent** on the machine. -* **SourcePath**: (Required) UNC path to the root of the source files for installation. -* **SourceFolder**: Folder within the source path containing the source files for installation. -* **Features**: (Key) SQL features to enable firewall rules for. -* **InstanceName**: (Key) SQL instance to enable firewall rules for. -* **DatabaseEngineFirewall**: Is the firewall rule for the Database Engine enabled? -* **BrowserFirewall**: Is the firewall rule for the Browser enabled? -* **ReportingServicesFirewall**: Is the firewall rule for Reporting Services enabled? -* **AnalysisServicesFirewall**: Is the firewall rule for Analysis Services enabled? -* **IntegrationServicesFirewall**: Is the firewall rule for the Integration Services enabled? +No description. -### xSQLServerLogin +#### Requirements -* **Ensure**: The specified login is Present or Absent. -* **Name**: (Key) The name of the login. -* **LoginType**: The type of login to be created. If LoginType is 'WindowsUser' or 'WindowsGroup' then provide the name in the format DOMAIN\name. Default is WindowsUser. Unsupported login types are Certificate, AsymmetricKey, ExternalUser, and ExternalGroup. -* **SQLServer**: (Key) The hostname of the SQL Server to be configured. -* **SQLInstanceName**: (Key) Name of the SQL instance to be configured. -* **LoginCredential**: If LoginType is 'SqlLogin' then a PSCredential is needed for the password to the login. -* **LoginMustChangePassword**: Specifies if the login is required to have its password change on the next login. Only applies to SQL Logins. Default is $true. -* **LoginPasswordExpirationEnabled**: Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. Default is $true. -* **LoginPasswordPolicyEnforced**: Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to SQL Logins. Default is $true. +* Target machine must be running Windows Server 2008 R2. -### xSQLServerRole +#### Parameters -* **Name**: (Key) Name of the SQL Login to create -* **Ensure**: If the values should be present or absent. Valid values are 'Present' or 'Absent'. -* **ServerRole**: Type of SQL role to add.(bulkadmin, dbcreator, diskadmin, processadmin , public, securityadmin, serveradmin , setupadmin, sysadmin) -* **SQLServer**: SQL Server where login should be created -* **SQLInstance**: (Key) SQL Instance for the login +* **[String] Features** _(Key)_: SQL features to enable firewall rules for. +* **[String] InstanceName** _(Key)_: SQL instance to enable firewall rules for. +* **[String] Ensure** _(Write)_: Ensures that SQL firewall rules are **Present** or **Absent** on the machine. { *Present* | Absent }. +* **[String] SourcePath** _(Write)_: UNC path to the root of the source files for installation. +* **[String] SourceFolder** _(Write)_: Folder within the source path containing the source files for installation. -### xSQLServerDatabaseRole +#### Read-Only Properties from Get-TargetResource -* **Ensure**: If 'Present' (the default value) then the login (user) will be added to the role(s). If 'Absent' then the login (user) will be removed from the role(s). -* **Name**: (Key) The name of the login that will become a member, or removed as a member, of the role(s). -* **SQLServer**: (Key) The SQL server on which the instance exist. -* **SQLInstanceName**: (Key) The SQL instance in which the database exist. -* **Database**: (Key) The database in which the login (user) and role(s) exist. -* **Role**: One or more roles to which the login (user) will be added or removed. +* **[Boolean] DatabaseEngineFirewall** _(Read)_: Is the firewall rule for the Database Engine enabled? +* **[Boolean] BrowserFirewall** _(Read)_: Is the firewall rule for the Browser enabled? +* **[Boolean] ReportingServicesFirewall** _(Read)_: Is the firewall rule for Reporting Services enabled? +* **[Boolean] AnalysisServicesFirewall** _(Read)_: Is the firewall rule for Analysis Services enabled? +* **[Boolean] IntegrationServicesFirewall** _(Read)_: Is the firewall rule for the Integration Services enabled? -### xSQLServerDatabasePermissions +#### Examples -* **Database**: (Key) The SQL Database -* **Name**: (Required) The name of permissions for the SQL database -* **Permissions**: (Required) The set of Permissions for the SQL database -* **SQLServer**: The SQL Server for the database -* **SQLInstanceName**: The SQL instance for the database +None. -### xSQLServerDatabaseOwner +### xSQLServerLogin -* **Database**: (Key) The SQL Database -* **Name**: (Required) The name of the SQL login for the owner -* **SQLServer**: The SQL Server for the database -* **SQLInstance**: The SQL instance for the database +No description. -### xSQLDatabaseRecoveryModel +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] SQLServer** _(Key)_:The hostname of the SQL Server to be configured. +* **[String] SQLInstanceName** _(Key)_: Name of the SQL instance to be configured. +* **[String] Name** _(Key)_: The name of the login. +* **[String] Ensure** _(Write)_: The specified login is Present or Absent. { *Present* | Absent }. +* **[PSCredential] LoginCredential** _(Write)_: If LoginType is 'SqlLogin' then a PSCredential is needed for the password to the login. +* **[String] LoginType** _(Write)_: The type of login to be created. If LoginType is 'WindowsUser' or 'WindowsGroup' then provide the name in the format DOMAIN\name. Default is WindowsUser. Unsupported login types are Certificate, AsymmetricKey, ExternalUser, and ExternalGroup. {SqlLogin | WindowsUser | WindowsGroup } +* **[Boolean] LoginMustChangePassword** _(Write)_: Specifies if the login is required to have its password change on the next login. Only applies to SQL Logins. Default is $true. +* **[Boolean] LoginPasswordExpirationEnabled** _(Write)_: Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. Default is $true. +* **[Boolean] LoginPasswordPolicyEnforced** _(Write)_: Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to SQL Logins. Default is $true. + +#### Examples -* **DatabaseName**: (key) The SQL database name -* **SQLServerInstance**: (Required) The SQL server and instance -* **RecoveryModel**: (Required) Recovery Model (Full, Simple, BulkLogged) +None. ### xSQLServerMaxDop -* **Ensure**: An enumerated value that describes if Min and Max memory is configured -* **DyamicAlloc**: Flag to indicate if MaxDop is dynamically configured -* **MaxDop**: Numeric value to configure MaxDop to -* **SQLServer**: The SQL Server where to set MaxDop -* **SQLInstance** (Key): The SQL instance where to set MaxDop +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] SQLInstance** (Key): The SQL instance where to set MaxDop +* **[String] Ensure** _(Write)_: An enumerated value that describes if Min and Max memory is configured. { *Present* | Absent }. +* **[Boolean] DyamicAlloc** _(Write)_: Flag to indicate if MaxDop is dynamically configured +* **[Sint32] MaxDop** _(Write)_: Numeric value to configure MaxDop to +* **[String] SQLServer** _(Write)_: The SQL Server where to set MaxDop + +#### Examples + +None. ### xSQLServerMemory -* **Ensure**: An enumerated value that describes if Min and Max memory is configured -* **DyamicAlloc**: (key) Flag to indicate if Memory is dynamically configured -* **MinMemory**: Minimum memory value to set SQL Server memory to -* **MaxMemory**: Maximum memory value to set SQL Server memory to -* **SQLServer**: The SQL Server for the database -* **SQLInstance**: (key) The SQL instance for the database +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] SQLInstance** _(Key)_: The SQL instance for the database +* **[Boolean] DyamicAlloc** _(Key)_: Flag to indicate if Memory is dynamically configured +* **[String] Ensure** _(Write)_: An enumerated value that describes if Min and Max memory is configured. { *Present* | Absent }. +* **[Sint32] MinMemory** _(Write)_: Minimum memory value to set SQL Server memory to +* **[Sint32] MaxMemory** _(Write)_: Maximum memory value to set SQL Server memory to +* **[String] SQLServer** _(Write)_: The SQL Server for the database + +#### Examples + +None. ### xSQLServerNetwork -* **InstanceName**: (Key) name of SQL Server instance for which network will be configured. -* **ProtocolName**: (Required) Name of network protocol to be configured. Only tcp is currently supported. -* **IsEnabled**: Enables/Disables network protocol. -* **TCPDynamicPorts**: 0 if Dynamic ports should be used otherwise empty. -* **TCPPort**: Custom TCP port. -* **RestartService**: If true will restart SQL Service instance service after update. Default false. +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] InstanceName** _(Key)_: name of SQL Server instance for which network will be configured. +* **[String] ProtocolName** _(Required)_: Name of network protocol to be configured. Only tcp is currently supported. { tcp }. +* **[Boolean] IsEnabled** _(Write)_: Enables/Disables network protocol. +* **[String] TCPDynamicPorts** _(Write)_: 0 if Dynamic ports should be used otherwise empty. { 0 }. +* **[String] TCPPort** _(Write)_: Custom TCP port. +* **[Boolean] RestartService** _(Write)_: If true will restart SQL Service instance service after update. Default false. + +#### Examples + +None. ### xSQLServerPermission -* **InstanceName** The SQL Server instance name. -* **NodeName** The host name or FQDN. -* **Ensure** If the permission should be present or absent. -* **Principal** The login to which permission will be set. -* **Permission** The permission to set for the login. Valid values are AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint. +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. +* Target machine must have access to the SQLPS PowerShell module or the SqlServer PowerShell module. + +#### Parameters + +* **[String] InstanceName** _(Key)_: The SQL Server instance name. +* **[String] NodeName** _(Required)_: The host name or FQDN. +* **[String] Principal** _(Required)_: The login to which permission will be set. +* **[String] Ensure** _(Write)_: If the permission should be present or absent. { Present | Absent }. +* **[String[]] Permission** _(Write)_: The permission to set for the login. Valid values are AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint. { AlterAnyAvailabilityGroup | AlterAnyEndPoint | ViewServerState }. + +#### Examples + +None. ### xSQLServerRole -* **Name**: (Key) Name of the SQL Login to create -* **Ensure**: If the values should be present or absent. Valid values are 'Present' or 'Absent'. -* **ServerRole**: Type of SQL role to add.(bulkadmin, dbcreator, diskadmin, processadmin , public, securityadmin, serveradmin , setupadmin, sysadmin) -* **SQLServer**: SQL Server where login should be created -* **SQLInstance**: (Key) SQL Instance for the login +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] SQLInstanceName** _(Key)_: SQL Instance for the login +* **[String] Name** _(Key)_: Name of the SQL Login to create +* **[String] SQLServer** _(Required)_: SQL Server where login should be created +* **[String[]] ServerRole** _(Required)_: Type of SQL role to add. { bulkadmin | dbcreator | diskadmin | processadmin | public | securityadmin | serveradmin | setupadmin | sysadmin }. +* **[String] Ensure** _(Write)_: If the values should be present or absent. Valid values are 'Present' or 'Absent'. { *Present* | Absent }. + +#### Examples + +* [Add a server role to a login](/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1) +* [Remove server role from a login](/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1) ### xSQLServerReplication -* **InstanceName**: (Key) SQL Server instance name where replication distribution will be configured. -* **Ensure**: (Default = 'Present') 'Present' will configure replication, 'Absent' will disable replication. -* **DistributorMode**: (Required), 'Local' - Instance will be configured as it's own distributor, 'Remote' - Instace will be configure with remote distributor (remote distributor needs to be already configured for distribution). -* **AdminLinkCredentials**: (Required) - AdminLink password to be used when setting up publisher distributor relationship. -* **DistributionDBName**: (Default = 'distribution') distribution database name. If DistributionMode='Local' this will be created, if 'Remote' needs to match distribution database on remote distributor. -* **RemoteDistributor**: (Required if DistributionMode='Remote') SQL Server network name that will be used as distributor for local instance. -* **WorkingDirectory**: (Required) Publisher working directory. -* **UseTrustedConnection**: (Default = $true) Publisher security mode. -* **UninstallWithForce**: (Default = $true) Force flag for uninstall procedure +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server 2008 or later. + +#### Parameters + +* **[String] InstanceName** _(Key)_: SQL Server instance name where replication distribution will be configured. +* **[String] Ensure** _(Write)_: (Default = 'Present') 'Present' will configure replication, 'Absent' will disable replication. +* **[String] DistributorMode** _(Required)_: 'Local' - Instance will be configured as it's own distributor, 'Remote' - Instace will be configure with remote distributor (remote distributor needs to be already configured for distribution). +* **[PSCredentials] AdminLinkCredentials** _(Required)_: - AdminLink password to be used when setting up publisher distributor relationship. +* **[String] DistributionDBName** _(Write)_: (Default = 'distribution') distribution database name. If DistributionMode='Local' this will be created, if 'Remote' needs to match distribution database on remote distributor. +* **[String] RemoteDistributor** _(Write)_: (Required if DistributionMode='Remote') SQL Server network name that will be used as distributor for local instance. +* **[String] WorkingDirectory** _(Required)_: Publisher working directory. +* **[Boolean] UseTrustedConnection** _(Write)_: (Default = $true) Publisher security mode. +* **[Boolean] UninstallWithForce** _(Write)_: (Default = $true) Force flag for uninstall procedure + +#### Examples + +None. ### xSQLServerRSConfig -* **InstanceName**: (Key) Name of the SQL Server Reporting Services instance to be configured. -* **RSSQLServer**: (Required) Name of the SQL Server to host the Reporting Service database. -* **RSSQLInstanceName**: (Required) Name of the SQL Server instance to host the Reporting Service database. -* **SQLAdminCredential**: (Required) Credential to be used to perform the configuration. -* **IsInitialized**: Output is the Reporting Services instance initialized. +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Reporting Services 2008 or later. + +#### Parameters + +* **[String] InstanceName** _(Key)_: Name of the SQL Server Reporting Services instance to be configured. +* **[String] RSSQLServer** _(Required)_: Name of the SQL Server to host the Reporting Service database. +* **[String] RSSQLInstanceName** _(Required)_: Name of the SQL Server instance to host the Reporting Service database. +* **[PSCredential] SQLAdminCredential** _(Required)_: Credential to be used to perform the configuration. + +#### Read-Only Properties from Get-TargetResource + +* **[Read] IsInitialized** _(Read)_: Output is the Reporting Services instance initialized. + +#### Examples + +None. ### xSQLServerRSSecureConnectionLevel -* **InstanceName**: (Key) SQL instance to set secure connection level for. -* **SecureConnectionLevel**: (Key) SQL Server Reporting Service secure connection level. -* **Credential**: (Required) Credential with administrative permissions to the SQL instance. +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Reporting Services 2008 or later. + +#### Parameters + +* **[String] InstanceName** _(Key)_: SQL instance to set secure connection level for. +* **[Uint16] SecureConnectionLevel** _(Key)_: SQL Server Reporting Service secure connection level. +* **[PSCredential] SQLAdminCredential** _(Required)_: Credential with administrative permissions to the SQL instance. + +#### Examples + +None. ### xSQLServerScript +Provides the means to run a user generated T-SQL script on the SQL Server instance. Three scripts are required; Get T-SQL script, Set T-SQL script and the Test T-SQL script. + +| T-SQL Script | Description | +| --- | --- | +| Get | The Get T-SQL script is used to query the status when running the cmdlet Get-DscConfiguration, and the result can be found in the property `GetResult`. | +| Test | The Test T-SQL script is used to test if the desired state is met. If Test T-SQL raises an error or returns any value other than 'null' the test fails, thus the Set T-SQL script is run. | +| Set | The Set T-SQL script performs the actual change when Test T-SQL script fails. | + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server 2008 or later. +* Target machine must have access to the SQLPS PowerShell module or the SqlServer PowerShell module. + _Note: There is a known problem running this resource using PowerShell 4.0. See [issue #273](https://github.com/PowerShell/xSQLServer/issues/273) for more information._ -* **ServerInstance**: (Key) The name of an instance of the Database Engine. For a default instance, only specify the computer name. For a named instances, use the format ComputerName\\InstanceName. -* **SetFilePath**: (Key) Path to the T-SQL file that will perform Set action. -* **GetFilePath**: (Key) Path to the T-SQL file that will perform Get action. Any values returned by the T-SQL queries will also be returned by the cmdlet Get-DscConfiguration through the `GetResult` property. -* **TestFilePath**: (Key) Path to the T-SQL file that will perform Test action. Any script that does not throw an error or returns null is evaluated to true. The cmdlet Invoke-SqlCmd treats T-SQL Print statements as verbose text, and will not cause the test to return false. -* **Credential**: The credentials to authenticate with, using SQL Authentication. To authenticate using Windows Authentication, assign the credentials to the built-in parameter `PsDscRunAsCredential`. If both parameters `Credential` and `PsDscRunAsCredential` are not assigned, then SYSTEM account will be used to authenticate using Windows Authentication. -* **Variable**: Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. Use a Windows PowerShell array to specify multiple variables and their values. For more information how to use this, please go to the help documentation for [Invoke-Sqlcmd](https://technet.microsoft.com/en-us/library/mt683370.aspx). -* **GetResult**: (Read) Contains the values returned from the T-SQL script provided in the parameter `GetFilePath` when cmdlet Get-DscConfiguration is run. +#### Parameters + +* **[String] ServerInstance** _(Key)_: The name of an instance of the Database Engine. For a default instance, only specify the computer name. For a named instances, use the format ComputerName\\InstanceName. +* **[String] SetFilePath** _(Key)_: Path to the T-SQL file that will perform Set action. +* **[String] GetFilePath** _(Key)_: Path to the T-SQL file that will perform Get action. Any values returned by the T-SQL queries will also be returned by the cmdlet Get-DscConfiguration through the `GetResult` property. +* **[String] TestFilePath** _(Key)_: Path to the T-SQL file that will perform Test action. Any script that does not throw an error or returns null is evaluated to true. The cmdlet Invoke-SqlCmd treats T-SQL Print statements as verbose text, and will not cause the test to return false. +* **[PSCredential] Credential** _(Write)_: The credentials to authenticate with, using SQL Authentication. To authenticate using Windows Authentication, assign the credentials to the built-in parameter `PsDscRunAsCredential`. If both parameters `Credential` and `PsDscRunAsCredential` are not assigned, then SYSTEM account will be used to authenticate using Windows Authentication. +* **[String[]] Variable** _(Write)_: Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. Use a Windows PowerShell array to specify multiple variables and their values. For more information how to use this, please go to the help documentation for [Invoke-Sqlcmd](https://technet.microsoft.com/en-us/library/mt683370.aspx). + +#### Read-Only Properties from Get-TargetResource + +* **GetResult** _(Read)_: Contains the values returned from the T-SQL script provided in the parameter `GetFilePath` when cmdlet Get-DscConfiguration is run. + +#### Examples + +* [Run a script using SQL Authentication](/Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1) +* [Run a script using Windows Authentication](/Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1) ### xSQLServerSetup -* **SourcePath**: (Required) The path to the root of the source files for installation. I.e and UNC path to a shared resource. -* **SourceFolder**: Folder within the source path containing the source files for installation. Default value is 'Source'. -* **SetupCredential**: (Required) Credential to be used to perform the installation. -* **SourceCredential**: Credentials used to access the path set in the parameter `SourcePath` and `SourceFolder`. Using this parameter will trigger a copy of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath` and `SourceFolder`. To know how the temp folder is evaluated please read the online documentation for [System.IO.Path.GetTempPath()](https://msdn.microsoft.com/en-us/library/system.io.path.gettemppath(v=vs.110).aspx). -* **SuppressReboot**: Suppresses reboot. -* **ForceReboot**: Forces reboot. -* **Features**: (Key) SQL features to be installed. -* **InstanceName**: (Key) SQL instance to be installed. -* **InstanceID**: SQL instance ID, if different from InstanceName. -* **PID**: Product key for licensed installations. -* **UpdateEnabled**: Enabled updates during installation. -* **UpdateSource**: Path to the source of updates to be applied during installation. -* **SQMReporting**: Enable customer experience reporting. -* **ErrorReporting**: Enable error reporting. -* **InstallSharedDir**: Installation path for shared SQL files. -* **InstallSharedWOWDir**: Installation path for x86 shared SQL files. -* **InstanceDir**: Installation path for SQL instance files. -* **SQLSvcAccount**: Service account for the SQL service. -* **SQLSvcAccountUsername**: (Read) Output user name for the SQL service. -* **AgtSvcAccount**: Service account for the SQL Agent service. -* **AgtSvcAccountUsername**: (Read) Output user name for the SQL Agent service. -* **SQLCollation**: Collation for SQL. -* **SQLSysAdminAccounts**: Array of accounts to be made SQL administrators. -* **SecurityMode**: Security mode to apply to the SQL Server instance. -* **SAPwd**: SA password, if SecurityMode is set to 'SQL'. -* **InstallSQLDataDir**: Root path for SQL database files. -* **SQLUserDBDir**: Path for SQL database files. -* **SQLUserDBLogDir**: Path for SQL log files. -* **SQLTempDBDir**: Path for SQL TempDB files. -* **SQLTempDBLogDir**: Path for SQL TempDB log files. -* **SQLBackupDir**: Path for SQL backup files. -* **FTSvcAccount**: Service account for the Full Text service. -* **FTSvcAccountUsername**: (Read) Output username for the Full Text service. -* **RSSvcAccount**: Service account for Reporting Services service. -* **RSSvcAccountUsername**: (Read) Output username for the Reporting Services service. -* **ASSvcAccount**: Service account for Analysis Services service. -* **ASSvcAccountUsername**: (Read) Output username for the Analysis Services service. -* **ASCollation**: Collation for Analysis Services. -* **ASSysAdminAccounts**: Array of accounts to be made Analysis Services admins. -* **ASDataDir**: Path for Analysis Services data files. -* **ASLogDir**: Path for Analysis Services log files. -* **ASBackupDir**: Path for Analysis Services backup files. -* **ASTempDir**: Path for Analysis Services temp files. -* **ASConfigDir**: Path for Analysis Services config. -* **ISSvcAccount**: Service account for Integration Services service. -* **ISSvcAccountUsername**: (Read) Output user name for the Integration Services service. -* **BrowserSvcStartupType**: Specifies the startup mode for SQL Server Browser service. Valid values are 'Automatic', 'Disabled' or 'Manual'. +Installs SQL Server on the target node. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. + +#### Parameters + +* **[String] InstanceName** _(Key)_: SQL instance to be installed. +* **[PSCredential] SetupCredential** _(Required)_: Credential to be used to perform the installation. +* **[String] SourcePath** _(Write)_: The path to the root of the source files for installation. I.e and UNC path to a shared resource. +* **[String] SourceFolder** _(Write)_: Folder within the source path containing the source files for installation. Default value is 'Source'. +* **[PSCredential] SourceCredential** _(Write)_: Credentials used to access the path set in the parameter `SourcePath` and `SourceFolder`. Using this parameter will trigger a copy of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath` and `SourceFolder`. To know how the temp folder is evaluated please read the online documentation for [System.IO.Path.GetTempPath()](https://msdn.microsoft.com/en-us/library/system.io.path.gettemppath(v=vs.110).aspx). +* **[Boolean] SuppressReboot** _(Write)_: Suppresses reboot. +* **[Boolean] ForceReboot** _(Write)_: Forces reboot. +* **[String] Features** _(Write)_: SQL features to be installed. +* **[String] InstanceID** _(Write)_: SQL instance ID, if different from InstanceName. +* **[String] PID** _(Write)_: Product key for licensed installations. +* **[String] UpdateEnabled** _(Write)_: Enabled updates during installation. +* **[String] UpdateSource** _(Write)_: Path to the source of updates to be applied during installation. +* **[String] SQMReporting** _(Write)_: Enable customer experience reporting. +* **[String] ErrorReporting** _(Write)_: Enable error reporting. +* **[String] InstallSharedDir** _(Write)_: Installation path for shared SQL files. +* **[String] InstallSharedWOWDir** _(Write)_: Installation path for x86 shared SQL files. +* **[String] InstanceDir** _(Write)_: Installation path for SQL instance files. +* **[PSCredential] SQLSvcAccount** _(Write)_: Service account for the SQL service. +* **[PSCredential] AgtSvcAccount** _(Write)_: Service account for the SQL Agent service. +* **[String] SQLCollation** _(Write)_: Collation for SQL. +* **[String[]] SQLSysAdminAccounts** _(Write)_: Array of accounts to be made SQL administrators. +* **[String] SecurityMode** _(Write)_: Security mode to apply to the SQL Server instance. +* **[PSCredential] SAPwd** _(Write)_: SA password, if SecurityMode is set to 'SQL'. +* **[String] InstallSQLDataDir** _(Write)_: Root path for SQL database files. +* **[String] SQLUserDBDir** _(Write)_: Path for SQL database files. +* **[String] SQLUserDBLogDir** _(Write)_: Path for SQL log files. +* **[String] SQLTempDBDir** _(Write)_: Path for SQL TempDB files. +* **[String] SQLTempDBLogDir** _(Write)_: Path for SQL TempDB log files. +* **[String] SQLBackupDir** _(Write)_: Path for SQL backup files. +* **[PSCredential] FTSvcAccount** _(Write)_: Service account for the Full Text service. +* **[PSCredential] RSSvcAccount** _(Write)_: Service account for Reporting Services service. +* **[PSCredential] ASSvcAccount** _(Write)_: Service account for Analysis Services service. +* **[String] ASCollation** _(Write)_: Collation for Analysis Services. +* **[String[]] ASSysAdminAccounts** _(Write)_: Array of accounts to be made Analysis Services admins. +* **[String] ASDataDir** _(Write)_: Path for Analysis Services data files. +* **[String] ASLogDir** _(Write)_: Path for Analysis Services log files. +* **[String] ASBackupDir** _(Write)_: Path for Analysis Services backup files. +* **[String] ASTempDir** _(Write)_: Path for Analysis Services temp files. +* **[String] ASConfigDir** _(Write)_: Path for Analysis Services config. +* **ISSvcAccount** _(Write)_: Service account for Integration Services service. +* **[String] BrowserSvcStartupType** _(Write)_: Specifies the startup mode for SQL Server Browser service. { Automatic | Disabled | 'Manual' } + +#### Read-Only Properties from Get-TargetResource + +* **SQLSvcAccountUsername** _(Read)_: Output user name for the SQL service. +* **AgtSvcAccountUsername** _(Read)_: Output user name for the SQL Agent service. +* **FTSvcAccountUsername** _(Read)_: Output username for the Full Text service. +* **RSSvcAccountUsername** _(Read)_: Output username for the Reporting Services service. +* **ASSvcAccountUsername** _(Read)_: Output username for the Analysis Services service. +* **ISSvcAccountUsername** _(Read)_: Output user name for the Integration Services service. + +#### Examples + +None. ### xWaitforAvailabilityGroup -* **Name**: (key) Name for availability group -* **RetryIntervalSec**: Interval to check for availability group -* **RetryCount**: Maximum number of retries to check availability group creation +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. + +#### Parameters + +* **[String] Name** _(Key)_: Name for availability group +* **[Uint64] RetryIntervalSec** _(Write)_: Interval to check for availability group +* **[Uint32] RetryCount** _(Write)_: Maximum number of retries to check availability group creation + +#### Read-Only Properties from Get-TargetResource + +None. + +#### Examples + +None. From d4dbdcfa603506bbd32e336f217b3586f9d07614 Mon Sep 17 00:00:00 2001 From: Darwin Corn Date: Mon, 9 Jan 2017 23:35:21 -0800 Subject: [PATCH 13/31] Fixed params for xSQLServerDatabase in README.md. (#303) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ef20f8977..634a5c4b0 100644 --- a/README.md +++ b/README.md @@ -264,8 +264,8 @@ No description. #### Parameters * **[String] SQLServer** _(Key)_: The SQL Server for the database -* **[String] SQLInstance** _(Key)_: The SQL instance for the database -* **[String] Database** _(Key)_: Database to be created or dropped +* **[String] SQLInstanceName** _(Key)_: The SQL instance for the database +* **[String] Name** _(Key)_: Database to be created or dropped * **[String] Ensure** _(Write)_: If the values should be present or absent. Valid values are 'Present' or 'Absent'. Default Value is 'Present'. { *Present* | Absent }. #### Examples From 46a8a27eb31687e17d1b1ac65904f157551bb283 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 10 Jan 2017 17:24:55 +0100 Subject: [PATCH 14/31] Changes to tests in xSQLServer module (#300) - Changes to tests in xSQLServer module - Added common test xSQLServerCommon.Tests for xSQLServer module - Now all markdown files will be style checked when tests are running in AppVeyor after sending in a pull request. - Now all [Examples](/Examples/Resources) will be tested by compiling to a .mof file after sending in a pull request. - Changes to xSQLServerDatabaseOwner - The example 'SetDatabaseOwner' can now compile, it wrongly had a `DependsOn` in the example. - Changes to SQLServerRole - The examples 'AddServerRole' and 'RemoveServerRole' can now compile, it wrongly had a `DependsOn` in the example. - Changes to xSQLServerScript - The examples 'AddServerRole' and 'RemoveServerRole' can now compile correctly. - Changes to CONTRIBUTING.md - Added section 'Tests for examples files' - Added section 'Tests for style check of Markdown files' - Added section 'Documentation with Markdown' - Added texts to section 'Tests' - Added default rules to .markdownlint.json --- .gitignore | 3 +- .markdownlint.json | 10 + CHANGELOG.md | 13 ++ CONTRIBUTING.md | 27 +++ .../xSQLServerAlias/1-AddSQLServerAlias.ps1 | 6 +- .../1-SetDatabaseOwner.ps1 | 61 +++---- .../xSQLServerRole/1-AddServerRole.ps1 | 52 +++--- .../xSQLServerRole/2-RemoveServerRole.ps1 | 41 ++--- .../1-RunScriptUsingSQLAuthentication.ps1 | 14 +- .../2-RunScriptUsingWindowsAuthentication.ps1 | 14 +- Tests/xSQLServerCommon.Tests.ps1 | 171 ++++++++++++++++++ appveyor.yml | 46 ++--- gulpfile.js | 24 +++ 13 files changed, 355 insertions(+), 127 deletions(-) create mode 100644 .markdownlint.json create mode 100644 Tests/xSQLServerCommon.Tests.ps1 create mode 100644 gulpfile.js diff --git a/.gitignore b/.gitignore index db5e4b56d..c333d23d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -DSCResource.Tests +DSCResource.Tests .vs .vscode +node_modules diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 000000000..bad263958 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,10 @@ +{ + "default": true, + "MD029": { + "style": "ordered" + }, + "MD034": false, + "MD024": false, + "MD013": false, + "no-hard-tabs": true +} diff --git a/CHANGELOG.md b/CHANGELOG.md index c4c6306fe..07ba0798c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,12 +33,25 @@ - Added to the description text for the parameter `Credential` describing how to authenticate using Windows Authentication. - Added examples to show how to authenticate using either SQL or Windows authentication. - A recent issue showed that there is a known problem running this resource using PowerShell 4.0. For more information, see [issue #273](https://github.com/PowerShell/xSQLServer/issues/273) + - The examples 'AddServerRole' and 'RemoveServerRole' can now compile correctly. - Changes to the unit test for resource - xSQLServerSetup - Added test coverage for helper function Copy-ItemWithRoboCopy - Changes to xSQLServerLogin - Removed ShouldProcess statements - Added the ability to enforce password policies on SQL logins +- Added common test (xSQLServerCommon.Tests) for xSQLServer module + - Now all markdown files will be style checked when tests are running in AppVeyor after sending in a pull request. + - Now all [Examples](/Examples/Resources) will be tested by compiling to a .mof file after sending in a pull request. +- Changes to xSQLServerDatabaseOwner + - The example 'SetDatabaseOwner' can now compile, it wrongly had a `DependsOn` in the example. +- Changes to SQLServerRole + - The examples 'AddServerRole' and 'RemoveServerRole' can now compile, it wrongly had a `DependsOn` in the example. +- Changes to CONTRIBUTING.md + - Added section 'Tests for examples files' + - Added section 'Tests for style check of Markdown files' + - Added section 'Documentation with Markdown' + - Added texts to section 'Tests' ## 4.0.0.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0663892c1..358c405df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,6 +75,28 @@ one resource, then the functions can also be placed in the common [xSQLServerHel For a review of a Pull Request (PR) to start, all tests must pass without error. If you need help to figure why some test don't pass, just write a comment in the Pull Request (PR), or submit an issue, and somebody will come along and assist. +To run all tests manually run the following. + +```powershell +Install-Module Pester +cd '\Tests' +Invoke-Pester +``` + +#### Tests for style check of Markdown files + +When sending in a Pull Request (PR) a style check will be performed on all Markdown files, and if the tests find any error the build will fail. +See the section [Documentation with Markdown](#documentation-with-markdown) how these errors kan be found before sending in the PR. + +The Markdown tests can be run locally if the packet manager 'npm' is available. To have npm available you need to install [node.js](https://nodejs.org/en/download/). +If 'npm' is not available, a warning text will print and the rest of the tests will continue run. + +#### Tests for examples files + +When sending in a Pull Request (PR) all example files will be tested so they can be compiled to a .mof file. If the tests find any errors the build will fail. +Before the test runs in AppVeyor the module will be copied to the first path of `$env:PSModulePath`. +To run this test locally, make sure you have the xSQLServer module deployed to a path where it can be used. See `$env:PSModulePath` to view the existing paths. + #### Using SMO stub classes There are [stub classes](https://github.com/PowerShell/xSQLServer/blob/dev/Tests/Unit/Stubs/SMO.cs) for the SMO classes which can be used and improved on when creating tests where SMO classes are used in the code being tested. @@ -83,3 +105,8 @@ There are [stub classes](https://github.com/PowerShell/xSQLServer/blob/dev/Tests AppVeyor is the platform where the tests is run when sending in a Pull Request (PR). All tests are run on a clean AppVeyor build worker for each push to the Pull Request (PR). The tests that are run on the build worker are common tests, unit tests and integration tests (with some limitations). + +### Documentation with Markdown + +If using Visual Studio Code to edit Markdown files it can be a good idea to install the markdownlint extension. It will help to do style checking. +The file [.markdownlint.json](/.markdownlint.json) is prepared with a default set of rules which will automatically be used by the extension. diff --git a/Examples/Resources/xSQLServerAlias/1-AddSQLServerAlias.ps1 b/Examples/Resources/xSQLServerAlias/1-AddSQLServerAlias.ps1 index 8257da583..3f42a7645 100644 --- a/Examples/Resources/xSQLServerAlias/1-AddSQLServerAlias.ps1 +++ b/Examples/Resources/xSQLServerAlias/1-AddSQLServerAlias.ps1 @@ -1,17 +1,17 @@ <# .EXAMPLE This example shows how to ensure that the SQL Alias - SQLDSC* exists with Named Pipes or TCP. + SQLDSC* exists with Named Pipes or TCP. #> -Configuration Example +Configuration Example { param( [Parameter(Mandatory = $true)] [PSCredential] $SysAdminAccount ) - + Import-DscResource -ModuleName xSqlServer node localhost { diff --git a/Examples/Resources/xSQLServerDatabaseOwner/1-SetDatabaseOwner.ps1 b/Examples/Resources/xSQLServerDatabaseOwner/1-SetDatabaseOwner.ps1 index 85fd1c2ab..189725b2f 100644 --- a/Examples/Resources/xSQLServerDatabaseOwner/1-SetDatabaseOwner.ps1 +++ b/Examples/Resources/xSQLServerDatabaseOwner/1-SetDatabaseOwner.ps1 @@ -1,42 +1,39 @@ <# .EXAMPLE This example shows how to ensure that the user account CONTOSO\SQLAdmin - is "Owner" of SQL database "AdventureWorks". + is "Owner" of SQL database "AdventureWorks". #> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) - Configuration Example - { - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - [System.Management.Automation.Credential()] - $SysAdminAccount - ) - - Import-DscResource -ModuleName xSqlServer + Import-DscResource -ModuleName xSqlServer - node localhost + node localhost + { + xSQLServerLogin Add_SqlServerLogin_SQLAdmin { - xSQLServerLogin Add_SqlServerLogin_SQLAdmin - { - DependsOn = '[xSqlServerSetup]SETUP_SqlMSSQLSERVER' - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - LoginType = 'WindowsUser' - SQLServer = 'SQLServer' - SQLInstanceName = 'DSC' - PsDscRunAsCredential = $SysAdminAccount - } + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } - xSQLServerDatabaseOwner Set_SqlDatabaseOwner_SQLAdmin - { - DependsOn = '[xSQLServerLogin]Add_SqlServerLogin_SQLAdmin' - Name = 'CONTOSO\SQLAdmin' - Database = 'AdventureWorks' - SQLServer = 'SQLServer' - SQLInstanceName = 'DSC' - PsDscRunAsCredential = $SysAdminAccount - } + xSQLServerDatabaseOwner Set_SqlDatabaseOwner_SQLAdmin + { + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorks' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount } } +} diff --git a/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1 b/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1 index ee474fb55..6ee078eb1 100644 --- a/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1 +++ b/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1 @@ -1,29 +1,39 @@ <# .EXAMPLE This example shows how to ensure that the user account CONTOSO\SQLAdmin - has "dbcreator" and "securityadmin" SQL server roles. + has "dbcreator" and "securityadmin" SQL server roles. #> - Configuration Example - { - param( - [Parameter(Mandatory = $true)] - [PSCredential] - $SysAdminAccount - ) - - Import-DscResource -ModuleName xSqlServer +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) - node localhost { - xSQLServerRole Add_SqlServerRole_SQLAdmin - { - DependsOn = '[xSQLServerLogin]Add_SqlServerLogin_SQLAdmin' - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - ServerRole = "dbcreator","securityadmin" - SQLServer = 'SQLServer' - SQLInstanceName = 'DSC' - PsDscRunAsCredential = $SysAdminAccount - } + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerLogin Add_LoginForSQLAdmin + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerRole Add_ServerRoleToLogin + { + DependsOn = '[xSQLServerLogin]Add_LoginForSQLAdmin' + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + ServerRole = "dbcreator","securityadmin" + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount } } +} diff --git a/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1 b/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1 index bf9823a61..c39e89c6b 100644 --- a/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1 +++ b/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1 @@ -1,29 +1,28 @@ <# .EXAMPLE This example shows how to ensure that the user account CONTOSO\SQLUser - does not have "setupadmin" SQL server role. + does not have "setupadmin" SQL server role. #> - Configuration Example - { - param( - [Parameter(Mandatory = $true)] - [PSCredential] - $SysAdminAccount - ) - - Import-DscResource -ModuleName xSqlServer +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) - node localhost { - xSQLServerRole Add_SqlServerRole_SQLAdmin - { - DependsOn = '[xSQLServerLogin]Add_SqlServerLogin_SQLAdmin' - Ensure = 'Absent' - Name = 'CONTOSO\SQLUser' - ServerRole = "setupadmin" - SQLServer = 'SQLServer' - SQLInstanceName = 'DSC' - PsDscRunAsCredential = $SysAdminAccount - } + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerRole Remove_ServerRoleFromLogin + { + Ensure = 'Absent' + Name = 'CONTOSO\SQLUser' + ServerRole = "setupadmin" + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount } } +} diff --git a/Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1 b/Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1 index 5fdf998ec..11d572f74 100644 --- a/Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1 +++ b/Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1 @@ -3,7 +3,7 @@ This example shows how to run SQL script using SQL Authentication. #> -Configuration Example +Configuration Example { param( [Parameter(Mandatory = $true)] @@ -27,15 +27,3 @@ Configuration Example } } } - -$configurationData = @{ - AllNodes = @( - @{ - NodeName = 'localhost' - } - ) -} - -Example -SqlCredential (Get-Credential) -ConfigurationData $configurationData -OutputPath 'C:\DSCTemp\Configuration' - -Start-DscConfiguration -Path 'C:\DSCTemp\Configuration' -Wait -Verbose -Force diff --git a/Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1 b/Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1 index 49ab909b5..098e8fa77 100644 --- a/Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1 +++ b/Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1 @@ -4,7 +4,7 @@ First example shows how the resource is run as account SYSTEM. And the second example shows how the resource is run with a user account. #> -Configuration Example +Configuration Example { param( [Parameter(Mandatory = $true)] @@ -39,15 +39,3 @@ Configuration Example } } } - -$configurationData = @{ - AllNodes = @( - @{ - NodeName = 'localhost' - } - ) -} - -Example -SqlCredential (Get-Credential) -ConfigurationData $configurationData -OutputPath 'C:\DSCTemp\Configuration' - -Start-DscConfiguration -Path 'C:\DSCTemp\Configuration' -Wait -Verbose -Force diff --git a/Tests/xSQLServerCommon.Tests.ps1 b/Tests/xSQLServerCommon.Tests.ps1 new file mode 100644 index 000000000..e8a8355f3 --- /dev/null +++ b/Tests/xSQLServerCommon.Tests.ps1 @@ -0,0 +1,171 @@ +[CmdletBinding()] +# Suppressing this because we need to generate a mocked credentials that will be passed along to the examples that are needed in the tests. +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] +param() + +$script:moduleRoot = Split-Path $PSScriptRoot -Parent + +Describe 'xSQLServer module common tests' { + Context -Name 'When there are example file for resource' { + <# + For Appveyor builds copy the module to the system modules directory so it falls + in to a PSModulePath folder and is picked up correctly. + #> + if ($env:APPVEYOR) + { + $powershellModulePath = Join-Path -Path (($env:PSModulePath -split ';')[0]) -ChildPath 'xSQLServer' + Copy-item -Path $env:APPVEYOR_BUILD_FOLDER -Destination $powershellModulePath -Recurse -Force + } + + $mockPassword = ConvertTo-SecureString '&iPm%M5q3K$Hhq=wcEK' -AsPlainText -Force + $mockCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @('username', $mockPassword) + $mockConfigData = @{ + AllNodes = @( + @{ + NodeName = "localhost" + PSDscAllowPlainTextPassword = $true + } + ) + } + + $exampleFile = Get-ChildItem -Path "$script:moduleRoot\Examples\Resources" -Filter "*.ps1" -Recurse + + foreach ($exampleToValidate in $exampleFile) + { + $exampleDescriptiveName = Join-Path -Path (Split-Path $exampleToValidate.Directory -Leaf) -ChildPath (Split-Path $exampleToValidate -Leaf) + + It "Should compile MOFs for example '$exampleDescriptiveName' correctly" { + { + . $exampleToValidate.FullName + + $exampleCommand = Get-Command Example -ErrorAction SilentlyContinue + if ($exampleCommand) + { + try + { + $params = @{} + $exampleCommand.Parameters.Keys | Where-Object { $_ -like '*Account' -or ($_ -like '*Credential' -and $_ -ne 'PsDscRunAsCredential') } | ForEach-Object -Process { + $params.Add($_, $mockCredential) + } + + Example @params -ConfigurationData $mockConfigData -OutputPath 'TestDrive:\' -ErrorAction Continue -WarningAction SilentlyContinue | Out-Null + } + finally + { + # Remove the function we dot-sourced so next example file doesn't use the previous Example-function. + Remove-Item function:Example + } + } + else + { + throw "The example '$exampleDescriptiveName' does not contain a function 'Example'." + } + } | Should Not Throw + } + } + + if ($env:APPVEYOR -eq $true) + { + Remove-item -Path $powershellModulePath -Recurse -Force -Confirm:$false + + # Restore the module in 'memory' to ensure other tests after this test have access to it + Import-Module -Name "$script:moduleRoot\xSQLServer.psd1" -Global -Force + } + } + + Context -Name 'When there are Markdown files in the module' { + if (Get-Command npm) + { + It 'Should not throw an error when installing dependencies' { + { + <# + gulp; gulp is a toolkit that helps you automate painful or time-consuming tasks in your development workflow. + gulp must be installed globally to be able to be called through Start-Process + #> + Start-Process -FilePath "npm" -ArgumentList "install -g gulp" -WorkingDirectory $script:moduleRoot -Wait -WindowStyle Hidden + + # gulp must also be installed locally to be able to be referenced in the javascript file. + Start-Process -FilePath "npm" -ArgumentList "install gulp" -WorkingDirectory $script:moduleRoot -Wait -WindowStyle Hidden + + # Used in gulpfile.js; A tiny wrapper around Node streams2 Transform to avoid explicit subclassing noise + Start-Process -FilePath "npm" -ArgumentList "install through2" -WorkingDirectory $script:moduleRoot -Wait -WindowStyle Hidden + + # Used in gulpfile.js; A Node.js style checker and lint tool for Markdown/CommonMark files. + Start-Process -FilePath "npm" -ArgumentList "install markdownlint" -WorkingDirectory $script:moduleRoot -Wait -WindowStyle Hidden + + # gulp-concat is installed as devDependencies. Used in gulpfile.js; Concatenates files + Start-Process -FilePath "npm" -ArgumentList "install gulp-concat -D" -WorkingDirectory $script:moduleRoot -Wait -WindowStyle Hidden + } | Should Not Throw + } + + It 'Should not have an error in any Markdown files' { + $markdownError = 0 + + try + { + # This executes the gulpfile.js in the root folder of the module. + Start-Process -FilePath "gulp" -ArgumentList "test-mdsyntax --silent" -WorkingDirectory $script:moduleRoot -Wait -NoNewWindow + + # Wait 3 seconds so the locks on file 'markdownerror.txt' has been released. + Start-Sleep -Seconds 3 + + $markdownFoundErrorPath = Join-Path -Path $script:moduleRoot -ChildPath "markdownerror.txt" + + if ((Test-Path -Path $markdownFoundErrorPath)) + { + Get-Content -Path $markdownFoundErrorPath | ForEach-Object -Process { + if (-not [string]::IsNullOrEmpty($_)) + { + Write-Warning -Message $_ + + $markdownError++ + } + } + } + + <# + When running in AppVeyor. Wait 5 seconds so the output have time to be sent to AppVeyor Console. + If there are many errors, the AppVeyor Console doesn't have time to print all warning messages + and messages comes out of order. + #> + if( $env:APPVEYOR ) + { + Start-Sleep -Seconds 5 + } + } + catch [System.Exception] + { + Write-Warning -Message "Unable to run gulp to test Markdown files. Please be sure that you have installed node.js. Error: $_" + } + + # Removes the 'markdownerror.txt' file from the module root so it is not shipped. + Remove-Item -Path $markdownFoundErrorPath -Force -ErrorAction SilentlyContinue + + # The actual test that will fail the build if it is not zero. + $markdownError | Should Be 0 + } + + It 'Should not throw an error when uninstalling dependencies' { + { + # Uninstalled npm packages in reverse order + Start-Process -FilePath "npm" -ArgumentList "uninstall gulp-concat -D" -WorkingDirectory $script:moduleRoot -Wait -PassThru -WindowStyle Hidden + Start-Process -FilePath "npm" -ArgumentList "uninstall markdownlint" -WorkingDirectory $script:moduleRoot -Wait -PassThru -WindowStyle Hidden + Start-Process -FilePath "npm" -ArgumentList "uninstall through2" -WorkingDirectory $script:moduleRoot -Wait -PassThru -WindowStyle Hidden + Start-Process -FilePath "npm" -ArgumentList "uninstall gulp" -WorkingDirectory $script:moduleRoot -Wait -PassThru -WindowStyle Hidden + Start-Process -FilePath "npm" -ArgumentList "uninstall -g gulp" -WorkingDirectory $script:moduleRoot -Wait -PassThru -WindowStyle Hidden + + # Remove folder node_modules that npm created. + $npmNpdeModulesPath = (Join-Path -Path $script:moduleRoot -ChildPath 'node_modules') + if( Test-Path -Path $npmNpdeModulesPath) + { + Remove-Item -Path $npmNpdeModulesPath -Recurse -Force + } + } | Should Not Throw + } + } + else + { + Write-Warning -Message 'Cannot find npm to install dependencies needed to test markdown files. Skipping this test!' + } + } +} diff --git a/appveyor.yml b/appveyor.yml index d2312e9c6..57d38bb5e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,23 +1,23 @@ -#---------------------------------# -# environment configuration # -#---------------------------------# +#---------------------------------# +# environment configuration # +#---------------------------------# version: 4.0.{build}.0 -install: +install: - git clone https://github.com/PowerShell/DscResource.Tests - ps: | Import-Module -Name .\DscResource.Tests\TestHelper.psm1 -Force Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force Install-Module -Name Pester -Repository PSGallery -Force -#---------------------------------# -# build configuration # -#---------------------------------# +#---------------------------------# +# build configuration # +#---------------------------------# build: false -#---------------------------------# -# test configuration # -#---------------------------------# +#---------------------------------# +# test configuration # +#---------------------------------# test_script: - ps: | @@ -26,20 +26,20 @@ test_script: Write-Host 'Modules that are being removed:' Get-Module -ListAvailable -Name 'sql*' | ForEach-Object -Process { Write-Host $_.Path; Remove-Item $_.Path -Force; } - # Start the + # Start the tests $testResultsFile = ".\TestsResults.xml" $res = Invoke-Pester -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile)) - if ($res.FailedCount -gt 0) { + if ($res.FailedCount -gt 0) { throw "$($res.FailedCount) tests failed." } - -#---------------------------------# -# deployment configuration # -#---------------------------------# -# scripts to run before deployment -deploy_script: +#---------------------------------# +# deployment configuration # +#---------------------------------# + +# scripts to run before deployment +deploy_script: - ps: | # Creating project artifact $stagingDirectory = (Resolve-Path ..).Path @@ -48,23 +48,23 @@ deploy_script: $zipFilePath = Join-Path $stagingDirectory "$(Split-Path $pwd -Leaf).zip" Add-Type -assemblyname System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::CreateFromDirectory($pwd, $zipFilePath) - + # Creating NuGet package artifact New-Nuspec -packageName $env:APPVEYOR_PROJECT_NAME -version $env:APPVEYOR_BUILD_VERSION -author "Microsoft" -owners "Microsoft" -licenseUrl "https://github.com/PowerShell/DscResources/blob/master/LICENSE" -projectUrl "https://github.com/$($env:APPVEYOR_REPO_NAME)" -packageDescription $env:APPVEYOR_PROJECT_NAME -tags "DesiredStateConfiguration DSC DSCResourceKit" -destinationPath . nuget pack ".\$($env:APPVEYOR_PROJECT_NAME).nuspec" -outputdirectory . $nuGetPackageName = $env:APPVEYOR_PROJECT_NAME + "." + $env:APPVEYOR_BUILD_VERSION + ".nupkg" $nuGetPackagePath = (Get-ChildItem $nuGetPackageName).FullName - + @( # You can add other artifacts here $zipFilePath, $nuGetPackagePath - ) | % { + ) | % { Write-Host "Pushing package $_ as Appveyor artifact" Push-AppveyorArtifact $_ } - - + + diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 000000000..43c257d0b --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,24 @@ +var gulp = require("gulp"); +var concat = require("gulp-concat"); +var through2 = require("through2"); +var markdownlint = require("markdownlint"); + +gulp.task("test-mdsyntax", function task() { + return gulp.src(["./DSCResources/**/*.md","./*.md"], { "read": false }) + .pipe(through2.obj(function obj(file, enc, next) { + markdownlint( + { + "files": [ file.path ], + "config": require("./.markdownlint.json") + }, + function callback(err, result) { + var resultString = (result || "").toString(); + if (resultString) { + file.contents = new Buffer(resultString); + } + next(err, file); + }); + })) + .pipe(concat("markdownerror.txt", { newLine: "\r\n" })) + .pipe(gulp.dest(".")); +}); From 60e942769fa274a3691189af5851d37bae8c6b8b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 12 Jan 2017 09:46:22 +0100 Subject: [PATCH 15/31] Changes to PULL_REQUEST_TEMPLATE and ISSUE_TEMPLATE (#305) - Changes to TEMPLATES - Changes to ISSUE_TEMPLATE.md - Changes to PULL_REQUEST_TEMPLATE.md - Typo in the README.md fixed. --- .github/ISSUE_TEMPLATE.md | 14 ++++++-------- .github/PULL_REQUEST_TEMPLATE.md | 19 ++++++++----------- README.md | 2 +- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 602571445..5a8f59e62 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,9 @@ -_Please provide the following information regarding your issue (place N/A if certain fields don't apply in your case:_ + +_Your feedback and support is greatly appreciated, thanks for contributing! If you like to contribute more please feel free to read the [contributing section](https://github.com/PowerShell/xSQLServer#contributing)._ + +Please prefix the issue title with the resource name, i.e. 'xSQLServerSetup: Short description of my issue' + +Please provide the following information regarding your issue (place N/A if the fields that don't apply to your issue): ---- DELETE THIS LINE AND ABOVE ---- @@ -9,10 +14,3 @@ _Please provide the following information regarding your issue (place N/A if cer **Version of the Operating System, SQL Server and PowerShell the DSC Target Node is running:** **Version of the DSC module you're using, or 'dev' if you're using current dev branch:** - ----- DELETE THIS LINE AND BELOW ---- - -_Your feedback and support is greatly appreciated._ - -_If you are able to resolve this issue or add new features, you may submit a Pull Request against this project._ -_Please see the [Contribution Guideliness](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md) for information on how to contribute._ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9f27e0cf7..8556ec240 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,25 +1,22 @@ -Thanks for submitting a Pull Request (PR) to this project. -This template will help you create your pull request (PR). +_Thanks for submitting a Pull Request (PR) to this project. Your contribution to this project is greatly appreciated!_ -Please make sure you have read the [Contribution Guidelines](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md). +Please make sure you have read the [contributing section](https://github.com/PowerShell/xSQLServer#contributing) -To aid community reviewers in reviewing and merging your pull request (PR), please take the time to run through the below checklist +Please prefix the PR title with the resource name, i.e. 'xSQLServerSetup: My short description' +If this is a breaking change, then also prefix the PR title with 'BREAKING CHANGE:', i.e. 'BREAKING CHANGE: xSQLServerSetup: My short description' -Your contribution to this project is greatly appreciated! +To aid community reviewers in reviewing and merging your PR, please take the time to run through the below checklist. +Change to [x] for each task in the task list that applies to this PR. ----- DELETE THIS LINE AND ABOVE ---- +---- DELETE THIS LINE AND EVERYTHING ABOVE ---- [Replace this with a description of your pull request] This Pull Request (PR) fixes the following issues: -[Replace this with the list of issues. Use format: Fixes #123] +[Replace this with the list of issues or n/a. Use format: Fixes #123] - [ ] Change details added to Unreleased section of CHANGELOG.md? - [ ] Added/updated documentation, comment-based help and descriptions in .schema.mof files where appropriate? - [ ] Examples appropriately updated? - [ ] New/changed code adheres to [Style Guidelines](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md)? - [ ] [Unit and (optional) Integration tests](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md) created/updated where possible? - ----- DELETE THIS LINE AND BELOW ---- - -Your contribution to this project is greatly appreciated. diff --git a/README.md b/README.md index 634a5c4b0..ec95ad168 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ You can also improve the resources and tests, or even create new resources, by s * If you want to submit an issue or take part in discussions, please browse the list of [issues](https://github.com/PowerShell/xSQLServer/issues). Please check out [Contributing to the DSC Resource Kit](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md) on how to work with issues. * If you want to review pull requests, please first check out the [Review Pull Request guidelines](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md#reviewing-pull-requests), and the browse the list of [pull requests](https://github.com/PowerShell/xSQLServer/pulls) and look for those pull requests with label 'needs review'. -* If you want to improve the resources or tests, or create a new resource, then please check out the follwing guidelines. +* If you want to improve the resources or tests, or create a new resource, then please check out the following guidelines. * The [Contributing to the DSC Resource Kit](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md) guidelines. * The specific [Contributing to xSQLServer](https://github.com/PowerShell/xSQLServer/blob/dev/CONTRIBUTING.md) guidelines. * The common [Style Guidelines](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md). From 471f80aba2c9b9d5a3af4298b936a9a7b9867a50 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 12 Jan 2017 16:24:26 +0100 Subject: [PATCH 16/31] README.md: Changes to README.md (#304) - Changes to README.md - Added security requirements section for resource xSQLServerEndpoint (issue #271). - Added security requirements section for resource xSQLAOGroupEnsure (issue #271). --- CHANGELOG.md | 1 + README.md | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07ba0798c..2966c4a42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Change the layout of the README.md to closer match the one of PSDscResources - Added more detailed text explaining what operating systemes WMF5.0 can be installed on. - Verified all resource schema files with the README.md and fixed some errors (descriptions was not verified). + - Added security requirements section for resource xSQLServerEndpoint and xSQLAOGroupEnsure. - Changes to xSQLServerSetup - The resource no longer uses Win32_Product WMI class when evaluating if SQL Server Management Studio is installed. See article [kb974524](https://support.microsoft.com/en-us/kb/974524) for more information. - Now it uses CIM cmdlets to get information from WMI classes. diff --git a/README.md b/README.md index ec95ad168..a7e378e83 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,9 @@ No description. * Target machine must be running Windows Server 2008 R2. * Target machine must be running SQL Server Database Engine 2012 or later. * Target machine must have access to the Active Directory module. + +#### Security Requirements + * The credentials provided in the parameter `SetupCredential` must have the right **Create Computer Object** in the origanization unit (OU) in which the Cluster Name Object (CNO) resides. #### Parameters @@ -344,6 +347,10 @@ No description. * Target machine must be running Windows Server 2008 R2. * Target machine must be running SQL Server Database Engine 2008 or later. +#### Security Requirements + +* The built-in parameter `PsDscRunAsCredential` must be set to the credentials of an account with the permission to enumerate logins, create the endpoint, and alter the permission on an endpoint. + #### Parameters * **[String] EndPointName** _(Key)_: Name for endpoint to be created on SQL Server From 290db3280c6f43243413b873a7fa8da56448fb23 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 14 Jan 2017 14:29:26 +0100 Subject: [PATCH 17/31] BREAKING CHANGE: xSQLServerSetup: Removed SourceFolder & default value - Changes to xSQLServerSetup - Removed default value "$PSScriptRoot\..\..\" from parameter SourcePath. - Removed parameter SourceFolder. - Function `ResolvePath` has been replaced with `[Environment]::ExpandEnvironmentVariables($SourcePath)` so that environment variables still can be used in Source Path. - Function `NetUse` has been replaced with `New-SmbMapping` and `Remove-SmbMapping`. - Renamed function `GetSQLVersion` to `Get-SqlMajorVersion`. - Resolved all the white space in xSQLServerHelper.psm1 --- CHANGELOG.md | 6 + .../MSFT_xSQLServerSetup.psm1 | 124 ++--- .../MSFT_xSQLServerSetup.schema.mof | 5 +- README.md | 5 +- Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 | 278 +++++++++-- xSQLServerHelper.psm1 | 430 +++++++++--------- 6 files changed, 524 insertions(+), 324 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2966c4a42..d84c54651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,12 @@ - Robocopy now removes files at the destination path if they no longer exists at the source. - Robocopy copies using unbuffered I/O when available (recommended for large files). - Added a more descriptive text for the parameter `SourceCredential` to further explain how the parameter work. + - BREAKING CHANGE: Removed parameter SourceFolder. + - BREAKING CHANGE: Removed default value "$PSScriptRoot\..\..\" from parameter SourcePath. + - Old code, that no longer filled any function, has been replaced. + - Function `ResolvePath` has been replaced with `[Environment]::ExpandEnvironmentVariables($SourcePath)` so that environment variables still can be used in Source Path. + - Function `NetUse` has been replaced with `New-SmbMapping` and `Remove-SmbMapping`. + - Renamed function `GetSQLVersion` to `Get-SqlMajorVersion`. - Changes to xSQLServerScript - All credential parameters now also has the type [System.Management.Automation.Credential()] to better work with PowerShell 4.0. - It is now possible to configure two instances on the same node, with the same script. diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 index 7a318a1ef..6c263d03e 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 @@ -7,17 +7,19 @@ Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $script Returns the current state of the SQL Server features. .PARAMETER SourcePath - The path to the root of the source files for installation. I.e and UNC path to a shared resource. - - .PARAMETER SourceFolder - Folder within the source path containing the source files for installation. Default value is 'Source'. + The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. .PARAMETER SetupCredential Credential to be used to perform the installation. .PARAMETER SourceCredential - Credentials used to access the path set in the parameter `SourcePath` and `SourceFolder`. The parameter `SourceCredential` is used - to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath` and `SourceFolder`. + Credentials used to access the path set in the parameter `SourcePath`. Using this parameter will trigger a copy + of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. + For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' + has in the path set, again, by the parameter `SourcePath`. + If the path, that is assigned to parameter `SourcePath`, contains a leaf folder, for example '\\server\share\folder', then that leaf + folder will be used as the name of the temporary folder. If the path, that is assigned to parameter `SourcePath`, does not have a + leaf folder, for example '\\server\share', then a unique guid will be used as the name of the temporary folder. .PARAMETER InstanceName Name of the SQL instance to be installed. @@ -30,11 +32,7 @@ function Get-TargetResource ( [Parameter()] [System.String] - $SourcePath = "$PSScriptRoot\..\..\", - - [Parameter()] - [System.String] - $SourceFolder = 'Source', + $SourcePath, [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] @@ -51,21 +49,28 @@ function Get-TargetResource $InstanceName = $InstanceName.ToUpper() + $SourcePath = [Environment]::ExpandEnvironmentVariables($SourcePath) + if ($SourceCredential) { - NetUse -SourcePath $SourcePath -Credential $SourceCredential -Ensure 'Present' + $newSmbMappingParameters = @{ + RemotePath = $SourcePath + UserName = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" + Password = $($SourceCredential.GetNetworkCredential().Password) + } + + $null = New-SmbMapping @newSmbMappingParameters } - $path = Join-Path -Path (Join-Path -Path $SourcePath -ChildPath $SourceFolder) -ChildPath 'setup.exe' - $path = ResolvePath -Path $path + $path = Join-Path -Path $SourcePath -ChildPath 'setup.exe' New-VerboseMessage -Message "Using path: $path" - $sqlVersion = GetSQLVersion -Path $path + $sqlVersion = Get-SqlMajorVersion -Path $path if ($SourceCredential) { - NetUse -SourcePath $SourcePath -Credential $SourceCredential -Ensure 'Absent' + Remove-SmbMapping -RemotePath $SourcePath -Force } if ($InstanceName -eq 'MSSQLSERVER') @@ -277,7 +282,6 @@ function Get-TargetResource return @{ SourcePath = $SourcePath - SourceFolder = $SourceFolder Features = $features InstanceName = $InstanceName InstanceID = $instanceID @@ -314,19 +318,19 @@ function Get-TargetResource Installs the SQL Server features to the node. .PARAMETER SourcePath - The path to the root of the source files for installation. I.e and UNC path to a shared resource. - - .PARAMETER SourceFolder - Folder within the source path containing the source files for installation. Default value is 'Source'. + The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. .PARAMETER SetupCredential Credential to be used to perform the installation. .PARAMETER SourceCredential - Credentials used to access the path set in the parameter `SourcePath` and `SourceFolder`. Using this parameter will trigger a copy + Credentials used to access the path set in the parameter `SourcePath`. Using this parameter will trigger a copy of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' - has in the path set, again, by the parameter `SourcePath` and `SourceFolder`. + has in the path set, again, by the parameter `SourcePath`. + If the path, that is assigned to parameter `SourcePath`, contains a leaf folder, for example '\\server\share\folder', then that leaf + folder will be used as the name of the temporary folder. If the path, that is assigned to parameter `SourcePath`, does not have a + leaf folder, for example '\\server\share', then a unique guid will be used as the name of the temporary folder. .PARAMETER SuppressReboot Suppressed reboot. @@ -447,10 +451,7 @@ function Set-TargetResource param ( [System.String] - $SourcePath = "$PSScriptRoot\..\..\", - - [System.String] - $SourceFolder = 'Source', + $SourcePath, [parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] @@ -575,38 +576,49 @@ function Set-TargetResource $parameters = @{ SourcePath = $SourcePath - SourceFolder = $SourceFolder SetupCredential = $SetupCredential SourceCredential = $SourceCredential InstanceName = $InstanceName } - $sqlData = Get-TargetResource @parameters + $getTargetResourceResult = Get-TargetResource @parameters $InstanceName = $InstanceName.ToUpper() - $mediaSourcePath = (Join-Path -Path $SourcePath -ChildPath $SourceFolder) + $SourcePath = [Environment]::ExpandEnvironmentVariables($SourcePath) if ($SourceCredential) { - NetUse -SourcePath $SourcePath -Credential $SourceCredential -Ensure 'Present' + $newSmbMappingParameters = @{ + RemotePath = $SourcePath + UserName = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" + Password = $($SourceCredential.GetNetworkCredential().Password) + } + + $null = New-SmbMapping @newSmbMappingParameters + + # Create a destination folder so the media files aren't written to the root of the Temp folder. + $mediaDestinationFolder = Split-Path -Path $SourcePath -Leaf + if (-not $mediaDestinationFolder ) + { + $mediaDestinationFolder = New-Guid | Select-Object -ExpandProperty Guid + } - $tempPath = Get-TemporaryFolder - $mediaDestinationPath = (Join-Path -Path $tempPath -ChildPath $SourceFolder) + $mediaDestinationPath = Join-Path -Path (Get-TemporaryFolder) -ChildPath $mediaDestinationFolder - New-VerboseMessage -Message "Robocopy is copying media from source '$mediaSourcePath' to destination '$mediaDestinationPath'" - Copy-ItemWithRoboCopy -Path $mediaSourcePath -DestinationPath $mediaDestinationPath + New-VerboseMessage -Message "Robocopy is copying media from source '$SourcePath' to destination '$mediaDestinationPath'" + Copy-ItemWithRoboCopy -Path $SourcePath -DestinationPath $mediaDestinationPath - NetUse -SourcePath $SourcePath -Credential $SourceCredential -Ensure 'Absent' + Remove-SmbMapping -RemotePath $SourcePath -Force - $mediaSourcePath = $mediaDestinationPath + $SourcePath = $mediaDestinationPath } - $path = ResolvePath (Join-Path -Path $mediaSourcePath -ChildPath 'setup.exe') + $path = ResolvePath (Join-Path -Path $SourcePath -ChildPath 'setup.exe') New-VerboseMessage -Message "Using path: $path" - $sqlVersion = GetSQLVersion -Path $path + $sqlVersion = Get-SqlMajorVersion -Path $path # Determine features to install $featuresToInstall = "" @@ -620,7 +632,7 @@ function Set-TargetResource Throw New-TerminatingError -ErrorType FeatureNotSupported -FormatArgs @($feature) -ErrorCategory InvalidData } - if (!($sqlData.Features.Contains($feature))) + if (!($getTargetResourceResult.Features.Contains($feature))) { $featuresToInstall += "$feature," } @@ -876,17 +888,19 @@ function Set-TargetResource Tests if the SQL Server features are installed on the node. .PARAMETER SourcePath - The path to the root of the source files for installation. I.e and UNC path to a shared resource. - - .PARAMETER SourceFolder - Folder within the source path containing the source files for installation. Default value is 'Source'. + The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. .PARAMETER SetupCredential Credential to be used to perform the installation. .PARAMETER SourceCredential - Credentials used to access the path set in the parameter `SourcePath` and `SourceFolder`. The parameter `SourceCredential` is used - to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath` and `SourceFolder`. + Credentials used to access the path set in the parameter `SourcePath`. Using this parameter will trigger a copy + of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. + For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' + has in the path set, again, by the parameter `SourcePath`. + If the path, that is assigned to parameter `SourcePath`, contains a leaf folder, for example '\\server\share\folder', then that leaf + folder will be used as the name of the temporary folder. If the path, that is assigned to parameter `SourcePath`, does not have a + leaf folder, for example '\\server\share', then a unique guid will be used as the name of the temporary folder. .PARAMETER SuppressReboot Suppresses reboot. @@ -1006,10 +1020,7 @@ function Test-TargetResource param ( [System.String] - $SourcePath = "$PSScriptRoot\..\..\", - - [System.String] - $SourceFolder = 'Source', + $SourcePath, [parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] @@ -1134,17 +1145,16 @@ function Test-TargetResource $parameters = @{ SourcePath = $SourcePath - SourceFolder = $SourceFolder SetupCredential = $SetupCredential SourceCredential = $SourceCredential InstanceName = $InstanceName } - $sqlData = Get-TargetResource @parameters + $getTargetResourceResult = Get-TargetResource @parameters New-VerboseMessage -Message "Features found: '$($SQLData.Features)'" $result = $false - if ($sqlData.Features ) + if ($getTargetResourceResult.Features ) { $result = $true @@ -1153,9 +1163,9 @@ function Test-TargetResource # Given that all the returned features are uppercase, make sure that the feature to search for is also uppercase $feature = $feature.ToUpper(); - if(!($sqlData.Features.Contains($feature))) + if(!($getTargetResourceResult.Features.Contains($feature))) { - New-VerboseMessage -Message "Unable to find feature '$feature' among the installed features: '$($sqlData.Features)'" + New-VerboseMessage -Message "Unable to find feature '$feature' among the installed features: '$($getTargetResourceResult.Features)'" $result = $false } } @@ -1171,7 +1181,7 @@ function Test-TargetResource .PARAMETER Path String containing the path to the SQL Server setup.exe executable. #> -function GetSQLVersion +function Get-SqlMajorVersion { [CmdletBinding()] param @@ -1325,7 +1335,7 @@ function Get-TemporaryFolder function Join-ServiceAccountInfo { <# - Suppressing this rule because there are parameters that contain the text 'UserName' and 'Password' + Suppressing this rule because there are parameters that contain the text 'UserName' and 'Password' but they are not actually used to pass any credentials. Instead the parameters are used to provide the argument that should be evaluated for setup.exe. #> diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof index 1fe6a7db4..f4b028d81 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof @@ -1,10 +1,9 @@ [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerSetup")] class MSFT_xSQLServerSetup : OMI_BaseResource { - [Write, Description("The path to the root of the source files for installation. I.e and UNC path to a shared resource.")] String SourcePath; - [Write, Description("Folder within the source path containing the source files for installation. Default value is 'Source'.")] String SourceFolder; + [Write, Description("The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path.")] String SourcePath; [Required, EmbeddedInstance("MSFT_Credential"), Description("Credential to be used to perform the installation.")] String SetupCredential; - [Write, EmbeddedInstance("MSFT_Credential"), Description("Credentials used to access the path set in the parameter 'SourcePath' and 'SourceFolder'. Using this parameter will trigger a copy of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. For any subsequent calls to the resource, the parameter 'SourceCredential' is used to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter 'SourcePath' and 'SourceFolder'.")] String SourceCredential; + [Write, EmbeddedInstance("MSFT_Credential"), Description("Credentials used to access the path set in the parameter 'SourcePath'.")] String SourceCredential; [Write, Description("Suppresses reboot.")] Boolean SuppressReboot; [Write, Description("Forces reboot.")] Boolean ForceReboot; [Write, Description("SQL features to be installed.")] String Features; diff --git a/README.md b/README.md index a7e378e83..01e8303b7 100644 --- a/README.md +++ b/README.md @@ -752,9 +752,8 @@ Installs SQL Server on the target node. * **[String] InstanceName** _(Key)_: SQL instance to be installed. * **[PSCredential] SetupCredential** _(Required)_: Credential to be used to perform the installation. -* **[String] SourcePath** _(Write)_: The path to the root of the source files for installation. I.e and UNC path to a shared resource. -* **[String] SourceFolder** _(Write)_: Folder within the source path containing the source files for installation. Default value is 'Source'. -* **[PSCredential] SourceCredential** _(Write)_: Credentials used to access the path set in the parameter `SourcePath` and `SourceFolder`. Using this parameter will trigger a copy of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath` and `SourceFolder`. To know how the temp folder is evaluated please read the online documentation for [System.IO.Path.GetTempPath()](https://msdn.microsoft.com/en-us/library/system.io.path.gettemppath(v=vs.110).aspx). +* **[String] SourcePath** _(Write)_: The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. +* **[PSCredential] SourceCredential** _(Write)_: Credentials used to access the path set in the parameter `SourcePath`. Using this parameter will trigger a copy of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath`. To know how the temp folder is evaluated please read the online documentation for [System.IO.Path.GetTempPath()](https://msdn.microsoft.com/en-us/library/system.io.path.gettemppath(v=vs.110).aspx). If the path, that is assigned to parameter `SourcePath`, contains a leaf folder, for example '\\server\share\folder', then that leaf folder will be used as the name of the temporary folder. If the path, that is assigned to parameter `SourcePath`, does not have a leaf folder, for example '\\server\share', then a unique guid will be used as the name of the temporary folder. * **[Boolean] SuppressReboot** _(Write)_: Suppresses reboot. * **[Boolean] ForceReboot** _(Write)_: Forces reboot. * **[String] Features** _(Write)_: SQL features to be installed. diff --git a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 index 87d4f8db9..0f50b1da0 100644 --- a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 @@ -91,10 +91,8 @@ try $mockSqlServiceAccount = 'COMPANY\SqlAccount' $mockAgentServiceAccount = 'COMPANY\AgentAccount' - $mockSourceFolder = 'Source' # The parameter SourceFolder has a default value of 'Source', so lets mock that as well. - #region Function mocks - $mockGetSQLVersion = { + $mockGetSqlMajorVersion = { return $mockSqlMajorVersion } @@ -504,6 +502,13 @@ try Add-Member -MemberType NoteProperty -Name 'ExitCode' -Value $mockStartProcessExitCode -PassThru -Force } + $mockSourcePathUNCWithoutLeaf = '\\server\share' + $mockSourcePathGuid = 'cc719562-0f46-4a16-8605-9f8a47c70402' + $mockNewGuid = { + return New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Guid' -Value $mockSourcePathGuid -PassThru -Force + } + $mockGetTemporaryFolder = { return $mockSourcePathUNC } @@ -537,22 +542,16 @@ try # Local path to TestDrive:\ $mockSourcePath = $TestDrive.FullName - $mockSqlMediaPath = Join-Path -Path $mockSourcePath -ChildPath $mockSourceFolder # UNC path to TestDrive:\ $testDrive_DriveShare = (Split-Path -Path $mockSourcePath -Qualifier) -replace ':','$' $mockSourcePathUNC = Join-Path -Path "\\localhost\$testDrive_DriveShare" -ChildPath (Split-Path -Path $mockSourcePath -NoQualifier) - $mockSqlMediaPathUNC = Join-Path -Path $mockSourcePathUNC -ChildPath $mockSourceFolder - - # Mocking folder structure and mocking setup.exe - New-Item -Path $mockSqlMediaPath -ItemType Directory - Set-Content (Join-Path -Path $mockSqlMediaPath -ChildPath 'setup.exe') -Value 'Mock exe file' #endregion Setting up TestDrive:\ BeforeEach { # General mocks - Mock -CommandName GetSQLVersion -MockWith $mockGetSQLVersion -Verifiable + Mock -CommandName Get-SqlMajorVersion -MockWith $mockGetSqlMajorVersion -Verifiable Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable Mock -CommandName Connect-SQLAnalysis -MockWith $mockConnectSQLAnalysis -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -634,7 +633,8 @@ try } -MockWith $mockEmptyHashtable -Verifiable } - Mock -CommandName NetUse -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -650,7 +650,8 @@ try $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - Assert-MockCalled -CommandName NetUse -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Remove-SmbMapping -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 @@ -682,7 +683,6 @@ try It 'Should return the correct values in the hash table' { $result = Get-TargetResource @testParameters $result.SourcePath | Should Be $mockSourcePath - $result.SourceFolder | Should Be $mockSourceFolder $result.InstanceName | Should Be $mockDefaultInstance_InstanceName $result.InstanceID | Should BeNullOrEmpty $result.InstallSharedDir | Should BeNullOrEmpty @@ -741,7 +741,8 @@ try } -MockWith $mockEmptyHashtable -Verifiable } - Mock -CommandName NetUse -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" @@ -756,7 +757,8 @@ try $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - Assert-MockCalled -CommandName NetUse -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Remove-SmbMapping -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 @@ -788,7 +790,6 @@ try It 'Should return the correct values in the hash table' { $result = Get-TargetResource @testParameters $result.SourcePath | Should Be $mockSourcePathUNC - $result.SourceFolder | Should Be $mockSourceFolder $result.InstanceName | Should Be $mockDefaultInstance_InstanceName $result.InstanceID | Should BeNullOrEmpty $result.InstallSharedDir | Should BeNullOrEmpty @@ -847,7 +848,8 @@ try } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable } - Mock -CommandName NetUse -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable #region Mock Get-CimInstance @@ -900,7 +902,8 @@ try $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - Assert-MockCalled -CommandName NetUse -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -977,7 +980,6 @@ try It 'Should return the correct values in the hash table' { $result = Get-TargetResource @testParameters $result.SourcePath | Should Be $mockSourcePath - $result.SourceFolder | Should Be $mockSourceFolder $result.InstanceName | Should Be $mockDefaultInstance_InstanceName $result.InstanceID | Should Be $mockDefaultInstance_InstanceName $result.InstallSharedDir | Should Be $mockSqlSharedDirectory @@ -1036,7 +1038,8 @@ try } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable } - Mock -CommandName NetUse -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable #region Mock Get-CimInstance @@ -1089,7 +1092,8 @@ try $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - Assert-MockCalled -CommandName NetUse -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -1166,7 +1170,6 @@ try It 'Should return the correct values in the hash table' { $result = Get-TargetResource @testParameters $result.SourcePath | Should Be $mockSourcePathUNC - $result.SourceFolder | Should Be $mockSourceFolder $result.InstanceName | Should Be $mockDefaultInstance_InstanceName $result.InstanceID | Should Be $mockDefaultInstance_InstanceName $result.InstallSharedDir | Should Be $mockSqlSharedDirectory @@ -1280,7 +1283,6 @@ try It 'Should return the correct values in the hash table' { $result = Get-TargetResource @testParameters $result.SourcePath | Should Be $mockSourcePath - $result.SourceFolder | Should Be $mockSourceFolder $result.InstanceName | Should Be $mockNamedInstance_InstanceName $result.InstanceID | Should BeNullOrEmpty $result.InstallSharedDir | Should BeNullOrEmpty @@ -1467,7 +1469,6 @@ try It 'Should return the correct values in the hash table' { $result = Get-TargetResource @testParameters $result.SourcePath | Should Be $mockSourcePath - $result.SourceFolder | Should Be $mockSourceFolder $result.InstanceName | Should Be $mockNamedInstance_InstanceName $result.InstanceID | Should Be $mockNamedInstance_InstanceName $result.InstallSharedDir | Should Be $mockSqlSharedDirectory @@ -1504,22 +1505,16 @@ try # Local path to TestDrive:\ $mockSourcePath = $TestDrive.FullName - $mockSqlMediaPath = Join-Path -Path $mockSourcePath -ChildPath $mockSourceFolder # UNC path to TestDrive:\ $testDrive_DriveShare = (Split-Path -Path $mockSourcePath -Qualifier) -replace ':','$' $mockSourcePathUNC = Join-Path -Path "\\localhost\$testDrive_DriveShare" -ChildPath (Split-Path -Path $mockSourcePath -NoQualifier) - $mockSqlMediaPathUNC = Join-Path -Path $mockSourcePathUNC -ChildPath $mockSourceFolder - - # Mocking folder structure and mocking setup.exe - New-Item -Path $mockSqlMediaPath -ItemType Directory - Set-Content (Join-Path -Path $mockSqlMediaPath -ChildPath 'setup.exe') -Value 'Mock exe file' #endregion Setting up TestDrive:\ BeforeEach { # General mocks - Mock -CommandName GetSQLVersion -MockWith $mockGetSQLVersion -Verifiable + Mock -CommandName Get-SqlMajorVersion -MockWith $mockGetSqlMajorVersion -Verifiable Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable Mock -CommandName Connect-SQLAnalysis -MockWith $mockConnectSQLAnalysis -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -1604,7 +1599,7 @@ try Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable $result = Test-TargetResource @testParameters - $result| Should Be $false + $result | Should Be $false Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It @@ -1673,7 +1668,7 @@ try $testParameters.Features = 'SSMS' $result = Test-TargetResource @testParameters - $result| Should Be $false + $result | Should Be $false Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It @@ -1772,7 +1767,7 @@ try $testParameters.Features = 'ADV_SSMS' $result = Test-TargetResource @testParameters - $result| Should Be $false + $result | Should Be $false Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It @@ -1902,7 +1897,7 @@ try It 'Should return that the desired state is present' { $result = Test-TargetResource @testParameters - $result| Should Be $true + $result | Should Be $true Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It @@ -1966,22 +1961,49 @@ try # Local path to TestDrive:\ $mockSourcePath = $TestDrive.FullName - $mockSqlMediaPath = Join-Path -Path $mockSourcePath -ChildPath $mockSourceFolder # UNC path to TestDrive:\ $testDrive_DriveShare = (Split-Path -Path $mockSourcePath -Qualifier) -replace ':','$' $mockSourcePathUNC = Join-Path -Path "\\localhost\$testDrive_DriveShare" -ChildPath (Split-Path -Path $mockSourcePath -NoQualifier) - $mockSqlMediaPathUNC = Join-Path -Path $mockSourcePathUNC -ChildPath $mockSourceFolder - # Mocking folder structure and mocking setup.exe - New-Item -Path $mockSqlMediaPath -ItemType Directory - Set-Content (Join-Path -Path $mockSqlMediaPath -ChildPath 'setup.exe') -Value 'Mock exe file' + <# + Mocking folder structure. This takes the leaf from the $mockSourcePath (the Guid in this case) and + uses that to create a mock folder to mimic the temp folder that would be created in, for example, + temp folder 'C:\Windows\Temp'. + This folder is used when SourcePath is called with a leaf, for example '\\server\share\folder'. + #> + $mediaTempSourcePathWithLeaf = (Join-Path -Path $mockSourcePath -ChildPath (Split-Path -Path $mockSourcePath -Leaf)) + if (-not (Test-Path $mediaTempSourcePathWithLeaf)) + { + New-Item -Path $mediaTempSourcePathWithLeaf -ItemType Directory + } + + <# + Mocking folder structure. This takes the leaf from the New-Guid mock and uses that + to create a mock folder to mimic the temp folder that would be created in, for example, + temp folder 'C:\Windows\Temp'. + This folder is used when SourcePath is called without a leaf, for example '\\server\share'. + #> + $mediaTempSourcePathWithoutLeaf = (Join-Path -Path $mockSourcePath -ChildPath $mockSourcePathGuid) + if (-not (Test-Path $mediaTempSourcePathWithoutLeaf)) + { + New-Item -Path $mediaTempSourcePathWithoutLeaf -ItemType Directory + } + + # Mocking executable setup.exe which will be used for tests without parameter SourceCredential + Set-Content (Join-Path -Path $mockSourcePath -ChildPath 'setup.exe') -Value 'Mock exe file' + + # Mocking executable setup.exe which will be used for tests with parameter SourceCredential and an UNC path with leaf + Set-Content (Join-Path -Path $mediaTempSourcePathWithLeaf -ChildPath 'setup.exe') -Value 'Mock exe file' + + # Mocking executable setup.exe which will be used for tests with parameter SourceCredential and an UNC path without leaf + Set-Content (Join-Path -Path $mediaTempSourcePathWithoutLeaf -ChildPath 'setup.exe') -Value 'Mock exe file' #endregion Setting up TestDrive:\ BeforeEach { # General mocks - Mock -CommandName GetSQLVersion -MockWith $mockGetSQLVersion -Verifiable + Mock -CommandName Get-SqlMajorVersion -MockWith $mockGetSqlMajorVersion -Verifiable Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable Mock -CommandName Connect-SQLAnalysis -MockWith $mockConnectSQLAnalysis -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -2047,9 +2069,11 @@ try SourcePath = $mockSourcePath } - Mock -CommandName NetUse -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable Mock -CommandName Start-Process -Verifiable Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable + Mock -CommandName New-Guid -MockWith $mockNewGuid -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -2077,8 +2101,10 @@ try { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled -CommandName NetUse -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 0 -Scope It + 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 Connect-SQL -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It @@ -2191,7 +2217,7 @@ try } } - Context "When using SourceCredential parameter and SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for a default instance" { + Context "When using SourceCredential parameter, and using a UNC path with a leaf, and SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for a default instance" { BeforeEach { $testParameters = $mockDefaultParameters $testParameters += @{ @@ -2200,9 +2226,167 @@ try SourcePath = $mockSourcePathUNC } - Mock -CommandName NetUse -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Start-Process -Verifiable + Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable + Mock -CommandName New-Guid -MockWith $mockNewGuid -Verifiable + Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable + + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) + } -MockWith $mockEmptyHashtable -Verifiable + } + + It 'Should set the system in the desired state when feature is SQLENGINE' { + $mockStartWin32ProcessExpectedArgument = + '/Quiet="True"', + '/IAcceptSQLServerLicenseTerms="True"', + '/Action="Install"', + '/AGTSVCSTARTUPTYPE=Automatic', + '/InstanceName="MSSQLSERVER"', + '/Features="SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS"', + '/SQLSysAdminAccounts="COMPANY\sqladmin"', + '/ASSysAdminAccounts="COMPANY\sqladmin"' -join ' ' + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 2 -Scope It + 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 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 + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName) + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $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 Test-TargetResource -Exactly -Times 1 -Scope It + } + + if( $mockSqlMajorVersion -eq 13 ) { + It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { + $testParameters.Features = 'SSMS' + $mockStartWin32ProcessExpectedArgument = '' + + { 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 = '' + + { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." + } + } else { + It 'Should set the system in the desired state when feature is SSMS' { + $testParameters.Features = 'SSMS' + + $mockStartWin32ProcessExpectedArgument = + '/Quiet="True"', + '/IAcceptSQLServerLicenseTerms="True"', + '/Action="Install"', + '/InstanceName="MSSQLSERVER"', + '/Features="SSMS"' -join ' ' + + { Set-TargetResource @testParameters } | Should Not Throw + + 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 + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName) + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $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 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 = + '/Quiet="True"', + '/IAcceptSQLServerLicenseTerms="True"', + '/Action="Install"', + '/InstanceName="MSSQLSERVER"', + '/Features="ADV_SSMS"' -join ' ' + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and + ($Name -eq $mockDefaultInstance_InstanceName) + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or + $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or + $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 Test-TargetResource -Exactly -Times 1 -Scope It + } + } + } + + Context "When using SourceCredential parameter, and using a UNC path without a leaf, and SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for a default instance" { + BeforeEach { + $testParameters = $mockDefaultParameters + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $mockSetupCredential + SourcePath = $mockSourcePathUNCWithoutLeaf + } + + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable Mock -CommandName Start-Process -Verifiable Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable + Mock -CommandName New-Guid -MockWith $mockNewGuid -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable @@ -2229,8 +2413,10 @@ try { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled -CommandName NetUse -Exactly -Times 4 -Scope It + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 2 -Scope It + 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 Connect-SQL -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It diff --git a/xSQLServerHelper.psm1 b/xSQLServerHelper.psm1 index 620e21d45..2f0c08cce 100644 --- a/xSQLServerHelper.psm1 +++ b/xSQLServerHelper.psm1 @@ -1,8 +1,8 @@ # Set Global Module Verbose -$VerbosePreference = 'Continue' +$VerbosePreference = 'Continue' -# Load Localization Data -Import-LocalizedData LocalizedData -filename xSQLServer.strings.psd1 -ErrorAction SilentlyContinue +# Load Localization Data +Import-LocalizedData LocalizedData -filename xSQLServer.strings.psd1 -ErrorAction SilentlyContinue Import-LocalizedData USLocalizedData -filename xSQLServer.strings.psd1 -UICulture en-US -ErrorAction SilentlyContinue <# @@ -12,11 +12,11 @@ Import-LocalizedData USLocalizedData -filename xSQLServer.strings.psd1 -UICultur .PARAMETER SQLServer String containing the host name of the SQL Server to connect to. - .PARAMETER SQLInstanceName - String containing the SQL Server Database Engine instance to connect to. + .PARAMETER SQLInstanceName + String containing the SQL Server Database Engine instance to connect to. .PARAMETER SetupCredential - PSCredential object with the credentials to use to impersonate a user when connecting. + PSCredential object with the credentials to use to impersonate a user when connecting. If this is not provided then the current user will be used to connect to the SQL Server Database Engine instance. #> function Connect-SQL @@ -24,21 +24,21 @@ function Connect-SQL [CmdletBinding()] param ( - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $SQLServer = $env:COMPUTERNAME, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $SQLInstanceName = "MSSQLSERVER", - [ValidateNotNull()] + [ValidateNotNull()] [System.Management.Automation.PSCredential] $SetupCredential ) - + $null = [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.Smo') - + if ($SQLInstanceName -eq "MSSQLSERVER") { $connectSql = $SQLServer @@ -47,13 +47,13 @@ function Connect-SQL { $connectSql = "$SQLServer\$SQLInstanceName" } - + if ($SetupCredential) { $sql = New-Object Microsoft.SqlServer.Management.Smo.Server $sql.ConnectionContext.ConnectAsUser = $true $sql.ConnectionContext.ConnectAsUserPassword = $SetupCredential.GetNetworkCredential().Password - $sql.ConnectionContext.ConnectAsUserName = $SetupCredential.GetNetworkCredential().UserName + $sql.ConnectionContext.ConnectAsUserName = $SetupCredential.GetNetworkCredential().UserName $sql.ConnectionContext.ServerInstance = $connectSQL $sql.ConnectionContext.connect() } @@ -79,11 +79,11 @@ function Connect-SQL .PARAMETER SQLServer String containing the host name of the SQL Server to connect to. - .PARAMETER SQLInstanceName - String containing the SQL Server Analysis Service instance to connect to. + .PARAMETER SQLInstanceName + String containing the SQL Server Analysis Service instance to connect to. .PARAMETER SetupCredential - PSCredential object with the credentials to use to impersonate a user when connecting. + PSCredential object with the credentials to use to impersonate a user when connecting. If this is not provided then the current user will be used to connect to the SQL Server Analysis Service instance. #> function Connect-SQLAnalysis @@ -91,21 +91,21 @@ function Connect-SQLAnalysis [CmdletBinding()] param ( - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $SQLServer = $env:COMPUTERNAME, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $SQLInstanceName = "MSSQLSERVER", - [ValidateNotNull()] + [ValidateNotNull()] [System.Management.Automation.PSCredential] $SetupCredential ) - + $null = [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.AnalysisServices') - + if ($SQLInstanceName -eq "MSSQLSERVER") { $connectSql = $SQLServer @@ -114,12 +114,12 @@ function Connect-SQLAnalysis { $connectSql = "$SQLServer\$SQLInstanceName" } - + $sql = New-Object Microsoft.AnalysisServices.Server if ($SetupCredential) { - $userName = $SetupCredential.GetNetworkCredential().UserName + $userName = $SetupCredential.GetNetworkCredential().UserName $password = $SetupCredential.GetNetworkCredential().Password $sql.Connect("Data Source=$connectSql;User ID=$userName;Password=$password") @@ -151,15 +151,15 @@ function Connect-SQLAnalysis .PARAMETER ErrorCategory The category to use for the error message. Default value is 'OperationStopped'. - Valid values are a value from the enumeration System.Management.Automation.ErrorCategory. + Valid values are a value from the enumeration System.Management.Automation.ErrorCategory. .PARAMETER TargetObject - The object that was being operated on when the error occurred. + The object that was being operated on when the error occurred. .PARAMETER InnerException - Exception object that was thorwn when the error occured, which will be added to the final error message. + Exception object that was thorwn when the error occured, which will be added to the final error message. #> -function New-TerminatingError +function New-TerminatingError { [CmdletBinding()] [OutputType([System.Management.Automation.ErrorRecord])] @@ -177,7 +177,7 @@ function New-TerminatingError [Parameter(Mandatory = $false)] [System.Management.Automation.ErrorCategory] $ErrorCategory = [System.Management.Automation.ErrorCategory]::OperationStopped, - + [Parameter(Mandatory = $false)] [Object] $TargetObject = $null, @@ -186,9 +186,9 @@ function New-TerminatingError [System.Exception] $InnerException = $null ) - + $errorMessage = $LocalizedData.$ErrorType - + if(!$errorMessage) { $errorMessage = ($LocalizedData.NoKeyFound -f $ErrorType) @@ -200,13 +200,13 @@ function New-TerminatingError } $errorMessage = ($errorMessage -f $FormatArgs) - + if( $InnerException ) { $errorMessage += " InnerException: $($InnerException.Message)" } - - $callStack = Get-PSCallStack + + $callStack = Get-PSCallStack # Get Name of calling script if($callStack[1] -and $callStack[1].ScriptName) @@ -214,7 +214,7 @@ function New-TerminatingError $scriptPath = $callStack[1].ScriptName $callingScriptName = $scriptPath.Split('\')[-1].Split('.')[0] - + $errorId = "$callingScriptName.$ErrorType" } else @@ -224,7 +224,7 @@ function New-TerminatingError Write-Verbose -Message "$($USLocalizedData.$ErrorType -f $FormatArgs) | ErrorType: $errorId" - $exception = New-Object System.Exception $errorMessage, $InnerException + $exception = New-Object System.Exception $errorMessage, $InnerException $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $ErrorCategory, $TargetObject return $errorRecord @@ -236,7 +236,7 @@ function New-TerminatingError .PARAMETER WarningType String containing the key of the localized warning message. - + .PARAMETER FormatArgs Collection of strings to replace format objects in warning message. #> @@ -268,7 +268,7 @@ function New-WarningMessage } ## Raise an error indicating the localization data is not present - throw New-TerminatingError @errorParams + throw New-TerminatingError @errorParams } ## Apply formatting @@ -305,27 +305,27 @@ function New-VerboseMessage .PARAMETER CurrentValues This is hashtable of the current values that are applied to the resource. - .PARAMETER DesiredValues + .PARAMETER DesiredValues This is a PSBoundParametersDictionary of the desired values for the resource. .PARAMETER ValuesToCheck This is a list of which properties in the desired values list should be checked. If this is empty then all values in DesiredValues are checked. #> -function Test-SQLDscParameterState +function Test-SQLDscParameterState { [CmdletBinding()] param ( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [HashTable] $CurrentValues, - - [Parameter(Mandatory = $true)] + + [Parameter(Mandatory = $true)] [Object] $DesiredValues, - [Parameter()] + [Parameter()] [Array] $ValuesToCheck ) @@ -334,66 +334,66 @@ function Test-SQLDscParameterState if (($DesiredValues.GetType().Name -ne "HashTable") ` -and ($DesiredValues.GetType().Name -ne "CimInstance") ` - -and ($DesiredValues.GetType().Name -ne "PSBoundParametersDictionary")) + -and ($DesiredValues.GetType().Name -ne "PSBoundParametersDictionary")) { throw "Property 'DesiredValues' in Test-SQLDscParameterState must be either a " + ` "Hashtable or CimInstance. Type detected was $($DesiredValues.GetType().Name)" } - if (($DesiredValues.GetType().Name -eq "CimInstance") -and ($null -eq $ValuesToCheck)) + if (($DesiredValues.GetType().Name -eq "CimInstance") -and ($null -eq $ValuesToCheck)) { throw "If 'DesiredValues' is a CimInstance then property 'ValuesToCheck' must contain a value" } - if (($null -eq $ValuesToCheck) -or ($ValuesToCheck.Count -lt 1)) + if (($null -eq $ValuesToCheck) -or ($ValuesToCheck.Count -lt 1)) { $keyList = $DesiredValues.Keys - } - else + } + else { $keyList = $ValuesToCheck } $keyList | ForEach-Object -Process { - if (($_ -ne "Verbose")) + if (($_ -ne "Verbose")) { if (($CurrentValues.ContainsKey($_) -eq $false) ` -or ($CurrentValues.$_ -ne $DesiredValues.$_) ` - -or (($DesiredValues.ContainsKey($_) -eq $true) -and ($DesiredValues.$_.GetType().IsArray))) + -or (($DesiredValues.ContainsKey($_) -eq $true) -and ($DesiredValues.$_.GetType().IsArray))) { if ($DesiredValues.GetType().Name -eq "HashTable" -or ` - $DesiredValues.GetType().Name -eq "PSBoundParametersDictionary") + $DesiredValues.GetType().Name -eq "PSBoundParametersDictionary") { - + $checkDesiredValue = $DesiredValues.ContainsKey($_) - } - else + } + else { $checkDesiredValue = Test-SPDSCObjectHasProperty $DesiredValues $_ } - if ($checkDesiredValue) + if ($checkDesiredValue) { $desiredType = $DesiredValues.$_.GetType() $fieldName = $_ - if ($desiredType.IsArray -eq $true) + if ($desiredType.IsArray -eq $true) { if (($CurrentValues.ContainsKey($fieldName) -eq $false) ` - -or ($null -eq $CurrentValues.$fieldName)) + -or ($null -eq $CurrentValues.$fieldName)) { New-VerboseMessage -Message ("Expected to find an array value for " + ` "property $fieldName in the current " + ` "values, but it was either not present or " + ` "was null. This has caused the test method " + ` "to return false.") - + $returnValue = $false - } - else + } + else { $arrayCompare = Compare-Object -ReferenceObject $CurrentValues.$fieldName ` -DifferenceObject $DesiredValues.$fieldName - if ($null -ne $arrayCompare) + if ($null -ne $arrayCompare) { New-VerboseMessage -Message ("Found an array for property $fieldName " + ` "in the current values, but this array " + ` @@ -402,14 +402,14 @@ function Test-SQLDscParameterState $arrayCompare | ForEach-Object -Process { New-VerboseMessage -Message "$($_.InputObject) - $($_.SideIndicator)" } - + $returnValue = $false } } - } - else + } + else { - switch ($desiredType.Name) + switch ($desiredType.Name) { "String" { if (-not [String]::IsNullOrEmpty($CurrentValues.$fieldName) -or ` @@ -418,46 +418,46 @@ function Test-SQLDscParameterState New-VerboseMessage -Message ("String value for property $fieldName does not match. " + ` "Current state is '$($CurrentValues.$fieldName)' " + ` "and Desired state is '$($DesiredValues.$fieldName)'") - + $returnValue = $false } } "Int32" { if (-not ($DesiredValues.$fieldName -eq 0) -or ` -not ($null -eq $CurrentValues.$fieldName)) - { + { New-VerboseMessage -Message ("Int32 value for property " + "$fieldName does not match. " + ` "Current state is " + "'$($CurrentValues.$fieldName)' " + ` "and desired state is " + "'$($DesiredValues.$fieldName)'") - + $returnValue = $false } } "Int16" { if (-not ($DesiredValues.$fieldName -eq 0) -or ` -not ($null -eq $CurrentValues.$fieldName)) - { + { New-VerboseMessage -Message ("Int32 value for property " + "$fieldName does not match. " + ` "Current state is " + "'$($CurrentValues.$fieldName)' " + ` "and desired state is " + "'$($DesiredValues.$fieldName)'") - + $returnValue = $false } } default { New-VerboseMessage -Message ("Unable to compare property $fieldName " + ` "as the type ($($desiredType.Name)) is " + ` - "not handled by the Test-SQLDscParameterState cmdlet") - + "not handled by the Test-SQLDscParameterState cmdlet") + $returnValue = $false } } } - } + } } - } + } } - + return $returnValue } @@ -468,11 +468,11 @@ function Test-SQLDscParameterState .PARAMETER SQLServer String containing the host name of the SQL Server to connect to. - .PARAMETER SQLInstanceName - String containing the SQL Server Database Engine instance to connect to. + .PARAMETER SQLInstanceName + String containing the SQL Server Database Engine instance to connect to. .PARAMETER SetupCredential - PSCredential object with the credentials to use to impersonate a user when connecting. + PSCredential object with the credentials to use to impersonate a user when connecting. If this is not provided then the current user will be used to connect to the SQL Server Database Engine instance. .PARAMETER AuthorizedUser @@ -483,25 +483,25 @@ function Grant-ServerPerms [CmdletBinding()] param ( - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $SQLServer = $env:COMPUTERNAME, - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $SQLInstanceName= "MSSQLSERVER", - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $SetupCredential, - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] [System.String] $AuthorizedUser ) - + if(!$SQL) { $SQL = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName -SetupCredential $SetupCredential @@ -524,20 +524,20 @@ function Grant-ServerPerms .PARAMETER AvailabilityGroupNameListener String containing the name of the Availabilty Group's Virtual Computer Object (VCO). - .PARAMETER CNO - String containing the name of the Cluster Name Object (CNO) for the failover cluster. + .PARAMETER CNO + String containing the name of the Cluster Name Object (CNO) for the failover cluster. #> function Grant-CNOPerms { [CmdletBinding()] Param ( - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] [System.String] $AvailabilityGroupNameListener, - - [ValidateNotNullOrEmpty()] + + [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] [System.String] $CNO @@ -551,26 +551,26 @@ function Grant-CNOPerms else{Import-Module ActiveDirectory -ErrorAction Stop -Verbose:$false} Try{ $AG = Get-ADComputer $AvailabilityGroupNameListener - + $comp = $AG.DistinguishedName # input AD computer distinguishedname - $acl = Get-Acl "AD:\$comp" + $acl = Get-Acl "AD:\$comp" $u = Get-ADComputer $CNO # get the AD user object given full control to computer $SID = [System.Security.Principal.SecurityIdentifier] $u.SID - + $identity = [System.Security.Principal.IdentityReference] $SID $adRights = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll" $type = [System.Security.AccessControl.AccessControlType] "Allow" $inheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "All" $ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $identity,$adRights,$type,$inheritanceType - - $acl.AddAccessRule($ace) + + $acl.AddAccessRule($ace) Set-Acl -AclObject $acl "AD:\$comp" New-VerboseMessage -Message "Granted privileges on $comp to $CNO" } Catch{ Throw "Failed to grant Permissions on $comp." Exit - } + } } <# @@ -583,11 +583,11 @@ function Grant-CNOPerms .PARAMETER SQLServer String containing the host name of the SQL Server to connect to. - .PARAMETER SQLInstanceName - String containing the SQL Server Database Engine instance to connect to. + .PARAMETER SQLInstanceName + String containing the SQL Server Database Engine instance to connect to. .PARAMETER SetupCredential - PSCredential object with the credentials to use to impersonate a user when connecting. + PSCredential object with the credentials to use to impersonate a user when connecting. If this is not provided then the current user will be used to connect to the SQL Server Database Engine instance. #> function New-ListenerADObject @@ -595,20 +595,20 @@ function New-ListenerADObject [CmdletBinding()] Param ( - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] [System.String] $AvailabilityGroupNameListener, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $SQLServer = $env:COMPUTERNAME, - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $SQLInstanceName = "MSSQLSERVER", - - [ValidateNotNullOrEmpty()] + + [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $SetupCredential @@ -620,7 +620,7 @@ function New-ListenerADObject } $CNO= $SQL.ClusterName - + #Verify Active Directory Tools are installed, if they are load if not Throw Error If (!(Get-Module -ListAvailable | Where-Object {$_.Name -eq "ActiveDirectory"})){ Throw "Active Directory Module is not installed and is Required." @@ -640,10 +640,10 @@ function New-ListenerADObject Throw ": Failed to find Computer in AD" exit } - - + + $m = Get-ADComputer -Filter {Name -eq $AvailabilityGroupNameListener} -Server $env:USERDOMAIN | Select-Object -Property * | Measure-Object - + If ($m.Count -eq 0) { Try{ @@ -655,16 +655,16 @@ function New-ListenerADObject Throw "Failed to Create $AvailabilityGroupNameListener in $OUPath" Exit } - + $SucccessChk =0 - - #Check for AD Object Validate at least three successful attempts + + #Check for AD Object Validate at least three successful attempts $i=1 While ($i -le 5) { Try{ $ListChk = Get-ADComputer -filter {Name -like $AvailabilityGroupNameListener} If ($ListChk){$SuccessChk++} - Start-Sleep -Seconds 10 + Start-Sleep -Seconds 10 If($SuccesChk -eq 3){break} } Catch{ @@ -672,7 +672,7 @@ function New-ListenerADObject Exit } $i++ - } + } } Try{ Grant-CNOPerms -AvailabilityGroupNameListener $AvailabilityGroupNameListener -CNO $CNO @@ -692,21 +692,21 @@ function Import-SQLPSModule { [CmdletBinding()] param() - + <# If SQLPS is not removed between resources (if it was started by another DSC resource) getting objects with the SQL PS provider will fail in some instances because of some sort of inconsistency. Uncertain why this happens. #> if( (Get-Module SQLPS).Count -ne 0 ) { Write-Debug "Unloading SQLPS module." Remove-Module -Name SQLPS -Force -Verbose:$False } - + Write-Debug "SQLPS module changes CWD to SQLSERVER:\ when loading, pushing location to pop it when module is loaded." Push-Location try { New-VerboseMessage -Message "Importing SQLPS module." Import-Module -Name SQLPS -DisableNameChecking -Verbose:$False -ErrorAction Stop # SQLPS has unapproved verbs, disable checking to ignore Warnings. - Write-Debug "SQLPS module imported." + Write-Debug "SQLPS module imported." } catch { throw New-TerminatingError -ErrorType FailedToImportSQLPSModule -ErrorCategory InvalidOperation -InnerException $_.Exception @@ -726,8 +726,8 @@ function Import-SQLPSModule { The SQLPS Provider doesn't use the default instance name of MSSQLSERVER, instead it uses DEFAULT. This function make sure the correct default instance name is returned. - .PARAMETER InstanceName - String containing the SQL Server Database Engine instance to validate. + .PARAMETER InstanceName + String containing the SQL Server Database Engine instance to validate. #> function Get-SQLPSInstanceName { @@ -741,9 +741,9 @@ function Get-SQLPSInstanceName ) if( $InstanceName -eq "MSSQLSERVER" ) { - $InstanceName = "DEFAULT" + $InstanceName = "DEFAULT" } - + return $InstanceName } @@ -751,11 +751,11 @@ function Get-SQLPSInstanceName .SYNOPSIS Returns the SQL Server SQLPS provider server object. - .PARAMETER InstanceName - String containing the SQL Server Database Engine instance to connect to. + .PARAMETER InstanceName + String containing the SQL Server Database Engine instance to connect to. - .PARAMETER NodeName - String containing the host name of the SQL Server to connect to. + .PARAMETER NodeName + String containing the host name of the SQL Server to connect to. #> function Get-SQLPSInstance { @@ -769,17 +769,17 @@ function Get-SQLPSInstance [Parameter(Mandatory = $true)] [System.String] - $NodeName + $NodeName ) - $InstanceName = Get-SQLPSInstanceName -InstanceName $InstanceName + $InstanceName = Get-SQLPSInstanceName -InstanceName $InstanceName $Path = "SQLSERVER:\SQL\$NodeName\$InstanceName" - + New-VerboseMessage -Message "Connecting to $Path as $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" Import-SQLPSModule $instance = Get-Item $Path - + return $instance } @@ -787,14 +787,14 @@ function Get-SQLPSInstance .SYNOPSIS Returns the SQL Server SQLPS provider endpoint object. - .PARAMETER Name - String containing the name of the endpoint to return. + .PARAMETER Name + String containing the name of the endpoint to return. - .PARAMETER InstanceName - String containing the SQL Server Database Engine instance to connect to. + .PARAMETER InstanceName + String containing the SQL Server Database Engine instance to connect to. - .PARAMETER NodeName - String containing the host name of the SQL Server to connect to. + .PARAMETER NodeName + String containing the host name of the SQL Server to connect to. #> function Get-SQLAlwaysOnEndpoint { @@ -812,21 +812,21 @@ function Get-SQLAlwaysOnEndpoint [Parameter(Mandatory = $true)] [System.String] - $NodeName + $NodeName ) $instance = Get-SQLPSInstance -InstanceName $InstanceName -NodeName $NodeName $Path = "$($instance.PSPath)\Endpoints" Write-Debug "Connecting to $Path as $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" - + [String[]] $presentEndpoint = Get-ChildItem $Path if( $presentEndpoint.Count -ne 0 -and $presentEndpoint.Contains("[$Name]") ) { Write-Debug "Connecting to endpoint $Name as $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" $endpoint = Get-Item "$Path\$Name" } else { $endpoint = $null - } + } return $endpoint } @@ -835,26 +835,26 @@ function Get-SQLAlwaysOnEndpoint .SYNOPSIS Create a new database in the SQL Server instance provided. - .PARAMETER SQL - An object returned from Connect-SQL function in which the database will be created. + .PARAMETER SQL + An object returned from Connect-SQL function in which the database will be created. - .PARAMETER Name - String containing the database name to be created. + .PARAMETER Name + String containing the database name to be created. #> function New-SqlDatabase { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] $SQL, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $Name ) - + $newDatabase = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database -ArgumentList $SQL,$Name if ($newDatabase) { @@ -864,33 +864,33 @@ function New-SqlDatabase else { New-VerboseMessage -Message "Failed to adding the database $Name" - } + } } <# .SYNOPSIS Remove a database in the SQL Server instance provided. - .PARAMETER SQL - An object returned from Connect-SQL function in which a database will be removed. + .PARAMETER SQL + An object returned from Connect-SQL function in which a database will be removed. - .PARAMETER Name - String containing the database name to be removed. + .PARAMETER Name + String containing the database name to be removed. #> function Remove-SqlDatabase { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] $Sql, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $Name ) - + $getDatabase = $Sql.Databases[$Name] if ($getDatabase) { @@ -900,7 +900,7 @@ function Remove-SqlDatabase else { New-VerboseMessage -Message "Failed to deleting the database $Name" - } + } } <# @@ -908,33 +908,33 @@ function Remove-SqlDatabase Add a user to a server role in the SQL Server instance provided. .PARAMETER Sql - An object returned from Connect-SQL function. + An object returned from Connect-SQL function. - .PARAMETER LoginName - String containing the login (user) which should be added as a member to the server role. + .PARAMETER LoginName + String containing the login (user) which should be added as a member to the server role. - .PARAMETER ServerRole - String containing the name of the server role which the user will be added as a member to. + .PARAMETER ServerRole + String containing the name of the server role which the user will be added as a member to. #> function Add-SqlServerRoleMember { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] $Sql, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $LoginName, - [ValidateNotNull()] + [ValidateNotNull()] [System.String[]] $ServerRole ) - + $sqlRole = $Sql.Roles if ($sqlRole) { @@ -961,34 +961,34 @@ function Add-SqlServerRoleMember .SYNOPSIS Remove a user in a server role in the SQL Server instance provided. - .PARAMETER Sql - An object returned from Connect-SQL function. + .PARAMETER Sql + An object returned from Connect-SQL function. - .PARAMETER LoginName - String containing the login (user) which should be removed as a member in the server role. + .PARAMETER LoginName + String containing the login (user) which should be removed as a member in the server role. - .PARAMETER ServerRole - String containing the name of the server role for which the user will be removed as a member. + .PARAMETER ServerRole + String containing the name of the server role for which the user will be removed as a member. #> function Remove-SqlServerRoleMember { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] $Sql, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $LoginName, - [ValidateNotNull()] + [ValidateNotNull()] [System.String[]] $ServerRole ) - + $sqlRole = $Sql.Roles if ($sqlRole) { @@ -1017,34 +1017,34 @@ function Remove-SqlServerRoleMember The function returns $true is the login (user) is a member in the provided server role. It will return $false if the user is not member of the provided server role. - .PARAMETER SQL - An object returned from Connect-SQL function. + .PARAMETER SQL + An object returned from Connect-SQL function. - .PARAMETER LoginName - String containing the login (user) which should be verified as a member in the server role. + .PARAMETER LoginName + String containing the login (user) which should be verified as a member in the server role. - .PARAMETER ServerRole - String containing the name of the server role which the user will be verified if a member of. + .PARAMETER ServerRole + String containing the name of the server role which the user will be verified if a member of. #> function Confirm-SqlServerRoleMember { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] $Sql, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $LoginName, - [ValidateNotNull()] + [ValidateNotNull()] [System.String[]] $ServerRole ) - + $sqlRole = $Sql.Roles if ($sqlRole) { @@ -1052,7 +1052,7 @@ function Confirm-SqlServerRoleMember { if ($sqlRole[$currentServerRole]) { - $membersInRole = $sqlRole[$currentServerRole].EnumMemberNames() + $membersInRole = $sqlRole[$currentServerRole].EnumMemberNames() if ($membersInRole.Contains($Name)) { $confirmServerRole = $true @@ -1092,18 +1092,18 @@ function Confirm-SqlServerRoleMember #> function Get-SqlDatabaseOwner { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] $SQL, - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $Database ) - + Write-Verbose -Message 'Getting SQL Databases' $sqlDatabase = $SQL.Databases if ($sqlDatabase) @@ -1134,7 +1134,7 @@ function Get-SqlDatabaseOwner .PARAMETER SQL This is an object of the SQL server that contains the result of Connect-SQL. - .PARAMETER Name + .PARAMETER Name This is the name of the desired owner for the SQL database. .PARAMETER Database @@ -1142,22 +1142,22 @@ function Get-SqlDatabaseOwner #> function Set-SqlDatabaseOwner { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] $SQL, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $Name, - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $Database ) - + Write-Verbose -Message 'Getting SQL Databases' $sqlDatabase = $SQL.Databases $sqlLogins = $SQL.Logins @@ -1200,7 +1200,7 @@ function Set-SqlDatabaseOwner .PARAMETER SQLServer Hostname of the SQL Server to be configured - + .PARAMETER SQLInstanceName Name of the SQL instance to be configued. Default is 'MSSQLSERVER' @@ -1240,8 +1240,8 @@ function Restart-SqlService if ($serverObject.IsClustered) { ## Get the cluster resources - New-VerboseMessage -Message 'Getting cluster resource for SQL Server' - $sqlService = Get-CimInstance -Namespace root/MSCluster -ClassName MSCluster_Resource -Filter "Type = 'SQL Server'" | + New-VerboseMessage -Message 'Getting cluster resource for SQL Server' + $sqlService = Get-CimInstance -Namespace root/MSCluster -ClassName MSCluster_Resource -Filter "Type = 'SQL Server'" | Where-Object { $_.PrivateProperties.InstanceName -eq $serverObject.ServiceName } New-VerboseMessage -Message 'Getting active cluster resource SQL Server Agent' From 07262f9d183331ed11753bc7892169e1de2c0a69 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 15 Jan 2017 15:25:10 +0100 Subject: [PATCH 18/31] AppVeyor: Adding codecoverage after tests are ran (#309) - Changes to AppVeyor - Now after the tests has ran, code coverage from Pester will be written to AppVeyor Console - Adding warning message says hat it takes a little longer to start the tests with code coverage. --- appveyor.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 57d38bb5e..6c5d1f65e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -27,8 +27,9 @@ test_script: Get-Module -ListAvailable -Name 'sql*' | ForEach-Object -Process { Write-Host $_.Path; Remove-Item $_.Path -Force; } # Start the tests - $testResultsFile = ".\TestsResults.xml" - $res = Invoke-Pester -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru + Write-Warning -Message 'Code coverage statistics are being calculated. This will slow the start of the tests while the code matrix is built. Please be patient.' + $testResultsFile = '.\TestsResults.xml' + $res = Invoke-Pester -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru -CodeCoverage @("$env:APPVEYOR_BUILD_FOLDER\*.psm1","$env:APPVEYOR_BUILD_FOLDER\DSCResources\**\*.psm1") (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile)) if ($res.FailedCount -gt 0) { throw "$($res.FailedCount) tests failed." From fc22f35f5da098059a36e9849018413a8236fcec Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 16 Jan 2017 12:21:51 +0100 Subject: [PATCH 19/31] xSQLServerSetup: Code cleanup, misplaced entry in the CHANGELOG (#312) - Changes to xSQLServerSetup - Code cleanup. A function was used, that wasn't needed. And change a variable name to align more against HQRM. - Changes to README.md - A duplicated and out of order row was removed. --- CHANGELOG.md | 1 - .../MSFT_xSQLServerSetup.psm1 | 16 ++++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d84c54651..77c47d5a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,6 @@ - Added to the description text for the parameter `Credential` describing how to authenticate using Windows Authentication. - Added examples to show how to authenticate using either SQL or Windows authentication. - A recent issue showed that there is a known problem running this resource using PowerShell 4.0. For more information, see [issue #273](https://github.com/PowerShell/xSQLServer/issues/273) - - The examples 'AddServerRole' and 'RemoveServerRole' can now compile correctly. - Changes to the unit test for resource - xSQLServerSetup - Added test coverage for helper function Copy-ItemWithRoboCopy diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 index 6c263d03e..a64ce672c 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 @@ -62,11 +62,11 @@ function Get-TargetResource $null = New-SmbMapping @newSmbMappingParameters } - $path = Join-Path -Path $SourcePath -ChildPath 'setup.exe' + $pathToSetupExecutable = Join-Path -Path $SourcePath -ChildPath 'setup.exe' - New-VerboseMessage -Message "Using path: $path" + New-VerboseMessage -Message "Using path: $pathToSetupExecutable" - $sqlVersion = Get-SqlMajorVersion -Path $path + $sqlVersion = Get-SqlMajorVersion -Path $pathToSetupExecutable if ($SourceCredential) { @@ -614,11 +614,11 @@ function Set-TargetResource $SourcePath = $mediaDestinationPath } - $path = ResolvePath (Join-Path -Path $SourcePath -ChildPath 'setup.exe') + $pathToSetupExecutable = Join-Path -Path $SourcePath -ChildPath 'setup.exe' - New-VerboseMessage -Message "Using path: $path" + New-VerboseMessage -Message "Using path: $pathToSetupExecutable" - $sqlVersion = Get-SqlMajorVersion -Path $path + $sqlVersion = Get-SqlMajorVersion -Path $pathToSetupExecutable # Determine features to install $featuresToInstall = "" @@ -861,9 +861,9 @@ function Set-TargetResource New-VerboseMessage -Message "Starting setup using arguments: $log" - $process = StartWin32Process -Path $path -Arguments $arguments + $process = StartWin32Process -Path $pathToSetupExecutable -Arguments $arguments New-VerboseMessage -Message $process - WaitForWin32ProcessEnd -Path $path -Arguments $arguments + WaitForWin32ProcessEnd -Path $pathToSetupExecutable -Arguments $arguments if ($ForceReboot -or ($null -ne (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name 'PendingFileRenameOperations' -ErrorAction SilentlyContinue))) { From 67e02be5a1a3f899df2695e31054409ea331891f Mon Sep 17 00:00:00 2001 From: Jean-Cyril Drouhin Date: Tue, 17 Jan 2017 11:57:11 +0100 Subject: [PATCH 20/31] BREAKING CHANGE: Changes to xSQLDatabaseRecoveryModel (#247) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Changes to xSQLServerDatabaseRecoveryModel - BREAKING CHANGE: Renamed xSQLDatabaseRecoveryModel to xSQLServerDatabaseRecoveryModel to align wíth naming convention. - BREAKING CHANGE: The mandatory parameters now include SQLServer, and SQLInstanceName. - Changes to xSQLServerHelper - added functions - Get-SqlDatabaseRecoveryModel - Set-SqlDatabaseRecoveryModel - Added tests for Get-SqlDatabaseRecoveryModel and Set-SqlDatabaseRecoveryModel in xSQLServerHelper.Tests - Examples - xSQLServerDatabaseRecoveryModel - 1-SetDatabaseRecoveryModel.ps1 - Added tests for resources - xSQLServerDatabaseRecoveryModel --- CHANGELOG.md | 12 + .../MSFT_xSQLDatabaseRecoveryModel.psm1 | 108 -------- .../MSFT_xSQLDatabaseRecoveryModel.schema.mof | 8 - .../MSFT_xSQLServerDatabaseRecoveryModel.psm1 | 185 ++++++++++++++ ...xSQLServerDatabaseRecoveryModel.schema.mof | 8 + .../1-SetDatabaseRecoveryModel.ps1 | 57 +++++ README.md | 43 ++-- ..._xSQLServerDatabaseRecoveryModel.Tests.ps1 | 236 ++++++++++++++++++ Tests/Unit/xSQLServerHelper.Tests.ps1 | 81 ++++++ xSQLServerHelper.psm1 | 103 ++++++++ 10 files changed, 705 insertions(+), 136 deletions(-) delete mode 100644 DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.psm1 delete mode 100644 DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.schema.mof create mode 100644 DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.psm1 create mode 100644 DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.schema.mof create mode 100644 Examples/Resources/xSQLServerDatabaseRecoveryModel/1-SetDatabaseRecoveryModel.ps1 create mode 100644 Tests/Unit/MSFT_xSQLServerDatabaseRecoveryModel.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 77c47d5a4..a6cc44c59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,18 @@ - Added section 'Tests for style check of Markdown files' - Added section 'Documentation with Markdown' - Added texts to section 'Tests' +- Changes to xSQLServerHelper + - added functions + - Get-SqlDatabaseRecoveryModel + - Set-SqlDatabaseRecoveryModel +- Examples + - xSQLServerDatabaseRecoveryModel + - 1-SetDatabaseRecoveryModel.ps1 +- Added tests for resources + - xSQLServerDatabaseRecoveryModel +- Changes to xSQLServerDatabaseRecoveryModel + - BREAKING CHANGE: Renamed xSQLDatabaseRecoveryModel to xSQLServerDatabaseRecoveryModel to align wíth naming convention. + - BREAKING CHANGE: The mandatory parameters now include SQLServer, and SQLInstanceName. ## 4.0.0.0 diff --git a/DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.psm1 b/DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.psm1 deleted file mode 100644 index 567195c87..000000000 --- a/DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.psm1 +++ /dev/null @@ -1,108 +0,0 @@ -$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Write-Verbose -Message "CurrentPath: $currentPath" - -# Load Common Code -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -Verbose:$false -ErrorAction Stop - -function Get-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [parameter(Mandatory = $true)] - [ValidateSet("Full","Simple","BulkLogged")] - [System.String] - $RecoveryModel = "Full", - - [parameter(Mandatory = $true)] - [System.String] - $SqlServerInstance, - - [parameter(Mandatory = $true)] - [System.String] - $DatabaseName - ) - - $SqlServerInstance = $SqlServerInstance.Replace('\MSSQLSERVER','') - New-VerboseMessage -Message "Checking Database $DatabaseName recovery mode for $RecoveryModel" - - $db = Get-SqlDatabase -ServerInstance $SqlServerInstance -Name $DatabaseName - $value = ($db.RecoveryModel -eq $RecoveryModel) - New-VerboseMessage -Message "Database $DatabaseName recovery mode comparison $value." - - $returnValue = @{ - RecoveryModel = $db.RecoveryModel - SqlServerInstance = $SqlServerInstance - DatabaseName = $DatabaseName - } - - $returnValue -} - - -function Set-TargetResource -{ - [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateSet("Full","Simple","BulkLogged")] - [System.String] - $RecoveryModel = "Full", - - [parameter(Mandatory = $true)] - [System.String] - $SqlServerInstance, - - [parameter(Mandatory = $true)] - [System.String] - $DatabaseName - ) - - $SqlServerInstance = $SqlServerInstance.Replace('\MSSQLSERVER','') - $db = Get-SqlDatabase -ServerInstance $SqlServerInstance -Name $DatabaseName - New-VerboseMessage -Message "Database $DatabaseName recovery mode is $db.RecoveryModel." - - if($db.RecoveryModel -ne $RecoveryModel) - { - $db.RecoveryModel = $RecoveryModel; - $db.Alter(); - New-VerboseMessage -Message "DB $DatabaseName recovery mode is changed to $RecoveryModel." - } - - if(!(Test-TargetResource @PSBoundParameters)) - { - throw New-TerminatingError -ErrorType TestFailedAfterSet -ErrorCategory InvalidResult - } -} - - -function Test-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [parameter(Mandatory = $true)] - [ValidateSet("Full","Simple","BulkLogged")] - [System.String] - $RecoveryModel = "Full", - - [parameter(Mandatory = $true)] - [System.String] - $SqlServerInstance, - - [parameter(Mandatory = $true)] - [System.String] - $DatabaseName - ) - $SqlServerInstance = $SqlServerInstance.Replace('\MSSQLSERVER','') - $result = ((Get-TargetResource @PSBoundParameters).RecoveryModel -eq $RecoveryModel) - - $result -} - - -Export-ModuleMember -Function *-TargetResource - diff --git a/DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.schema.mof b/DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.schema.mof deleted file mode 100644 index 557166e42..000000000 --- a/DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.schema.mof +++ /dev/null @@ -1,8 +0,0 @@ - -[ClassVersion("1.0"), FriendlyName("xSQLDatabaseRecoveryModel")] -class MSFT_xSQLDatabaseRecoveryModel : OMI_BaseResource -{ - [Key, Description("The SQL database name")] String DatabaseName; - [Required, Description("The SQL server and instance.")] String SqlServerInstance; - [Required, Description("Recovery Model"), ValueMap{"Full","Simple","BulkLogged"}, Values{"Full","Simple","BulkLogged"}] String RecoveryModel; -}; diff --git a/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.psm1 b/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.psm1 new file mode 100644 index 000000000..4b9d90cde --- /dev/null +++ b/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.psm1 @@ -0,0 +1,185 @@ +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -ChildPath 'xSQLServerHelper.psm1') -Force + +<# + .SYNOPSIS + This function gets all Key properties defined in the resource schema file + + .PARAMETER Database + This is the SQL database + + .PARAMETER RecoveryModel + This is the RecoveryModel of the SQL database + + .PARAMETER SQLServer + This is a the SQL Server for the database + + .PARAMETER SQLInstanceName + This is a the SQL instance for the database +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [parameter(Mandatory = $true)] + [ValidateSet('Full','Simple','BulkLogged')] + [System.String] + $RecoveryModel, + + [parameter(Mandatory = $true)] + [System.String] + $SQLServer, + + [parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName, + + [parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + if ($sql) + { + Write-Verbose -Message "Getting RecoveryModel of SQL database '$Name'" + $sqlDatabase = $sql.Databases + + if ($sqlDatabase) + { + if ($sqlDatabase[$Name]) + { + $getSqlDatabaseRecoveryModel = Get-SqlDatabaseRecoveryModel -SqlServerObject $sql -DatabaseName $Name + New-VerboseMessage -Message "RecoveryModel of SQL Database name $Name is $getSqlDatabaseRecoveryModel" + } + else + { + New-VerboseMessage -Message "SQL Database name $Name does not exist" + $null = $getSqlDatabaseRecoveryModel + } + } + else + { + New-WarningMessage -Message 'Failed getting SQL databases' + $null = $getSqlDatabaseRecoveryModel + } + } + + $returnValue = @{ + Name = $Name + RecoveryModel = $getSqlDatabaseRecoveryModel + SQLServer = $SQLServer + SQLInstanceName = $SQLInstanceName + } + + $returnValue +} + +<# + .SYNOPSIS + This function gets all Key properties defined in the resource schema file + + .PARAMETER Database + This is the SQL database + + .PARAMETER RecoveryModel + This is the RecoveryModel of the SQL database + + .PARAMETER SQLServer + This is a the SQL Server for the database + + .PARAMETER SQLInstanceName + This is a the SQL instance for the database +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateSet('Full','Simple','BulkLogged')] + [System.String] + $RecoveryModel, + + [parameter(Mandatory = $true)] + [System.String] + $SQLServer, + + [parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName, + + [parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + if ($sql) + { + $sqlDatabase = $Sql.Databases[$Name] + if ($sqlDatabase) + { + Write-Verbose -Message "Setting database '$Name' with RecoveryModel '$RecoveryModel'" + Set-SqlDatabaseRecoveryModel -SqlServerObject $sql -DatabaseName $Name -RecoveryModel $RecoveryModel + } + else + { + New-VerboseMessage -Message "SQL Database name $Name does not exist" + } + } +} + +<# + .SYNOPSIS + This function gets all Key properties defined in the resource schema file + + .PARAMETER Database + This is the SQL database + + .PARAMETER RecoveryModel + This is the RecoveryModel of the SQL database + + .PARAMETER SQLServer + This is a the SQL Server for the database + + .PARAMETER SQLInstanceName + This is a the SQL instance for the database +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [parameter(Mandatory = $true)] + [ValidateSet('Full','Simple','BulkLogged')] + [System.String] + $RecoveryModel, + + [parameter(Mandatory = $true)] + [System.String] + $SQLServer, + + [parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName, + + [parameter(Mandatory = $true)] + [System.String] + $Name + ) + + Write-Verbose -Message "Testing RecoveryModel of database '$Name'" + + $currentValues = Get-TargetResource @PSBoundParameters + + return Test-SQLDscParameterState -CurrentValues $CurrentValues ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck @('Name','RecoveryModel') +} + +Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.schema.mof b/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.schema.mof new file mode 100644 index 000000000..0ffb09a92 --- /dev/null +++ b/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.schema.mof @@ -0,0 +1,8 @@ +[ClassVersion("1.0"), FriendlyName("xSQLServerDatabaseRecoveryModel")] +class MSFT_xSQLServerDatabaseRecoveryModel : OMI_BaseResource +{ + [Key, Description("The SQL database name")] String Name; + [Required, Description("The recovery model to use for the database."), ValueMap{"Full","Simple","BulkLogged"}, Values{"Full","Simple","BulkLogged"}] String RecoveryModel; + [Key, Description("The host name of the SQL Server to be configured.")] String SQLServer; + [Key, Description("The name of the SQL instance to be configured.")] String SQLInstanceName; +}; diff --git a/Examples/Resources/xSQLServerDatabaseRecoveryModel/1-SetDatabaseRecoveryModel.ps1 b/Examples/Resources/xSQLServerDatabaseRecoveryModel/1-SetDatabaseRecoveryModel.ps1 new file mode 100644 index 000000000..518047e16 --- /dev/null +++ b/Examples/Resources/xSQLServerDatabaseRecoveryModel/1-SetDatabaseRecoveryModel.ps1 @@ -0,0 +1,57 @@ +<# +.EXAMPLE + This example shows how to set the Recovery Model + to "Full" for SQL database "AdventureWorks". +#> + +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerDatabase Add_SqlDatabaseAdventureworks + { + Ensure = 'Present' + Name = 'Adventureworks' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabase Add_SqlDatabaseAdventureWorks2012 + { + Ensure = 'Present' + Name = 'AdventureWorks2012' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabaseRecoveryModel Set_SqlDatabaseRecoveryModel_Adventureworks + { + Name = 'Adventureworks' + RecoveryModel = 'Full' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabaseRecoveryModel Set_SqlDatabaseRecoveryModel_AdventureWorks2012 + { + Name = 'AdventureWorks2012' + RecoveryModel = 'Simple' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/README.md b/README.md index 01e8303b7..2ed463cee 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ A full list of changes in each version can be found in the [change log](CHANGELO * [**xSQLAOGroupEnsure**](#xsqlaogroupensure) resource to ensure availability group is present or absent * [**xSQLAOGroupJoin**](#xsqlaogroupjoin) resource to join a replica to an existing availability group -* [**xSQLDatabaseRecoveryModel**](#xsqldatabaserecoverymodel) resource to manage database recovery model * [**xSQLServerAlias**](#xsqlserveralias) resource to manage SQL Server client Aliases * [**xSQLServerAlwaysOnService**](#xsqlserveralwaysonservice) resource to enable always on on a SQL Server * [**xSQLServerAvailabilityGroupListener**](#xsqlserveravailabilitygrouplistener) Create or remove an availability group listener. @@ -68,6 +67,7 @@ A full list of changes in each version can be found in the [change log](CHANGELO * [**xSQLServerDatabase**](#xsqlserverdatabase) resource to manage ensure database is present or absent * [**xSQLServerDatabaseOwner**](#xsqlserverdatabaseowner) resource to manage SQL database owners * [**xSQLServerDatabasePermissions**](#xsqlserverdatabasepermissions) resource to manage SQL database permissions +* [**xSQLServerDatabaseRecoveryModel**](#xSQLServerDatabaseRecoveryModel) resource to manage database recovery model * [**xSQLServerDatabaseRole**](#xsqlserverdatabaserole) resource to manage SQL database roles * [**xSQLServerEndpoint**](#xsqlserverendpoint) resource to ensure database endpoint is present or absent * [**xSQLServerEndpointPermission**](#xsqlserverendpointpermission) Grant or revoke permission on the endpoint. @@ -142,25 +142,6 @@ No description. None. -### xSQLDatabaseRecoveryModel - -No description. - -#### Requirements - -* Target machine must be running Windows Server 2008 R2. -* Target machine must be running SQL Server Database Engine 2008 or later. - -#### Parameters - -* **[String] DatabaseName** _(Key)_: The SQL database name -* **[String] SQLServerInstance** _(Required)_: The SQL server and instance -* **[String] RecoveryModel** _(Required)_: Recovery Model (Full, Simple, BulkLogged). { *Full* | Simple | BulkLogged }. - -#### Examples - -None. - ### xSQLServerAlias No description. @@ -316,6 +297,28 @@ No description. None. +### xSQLServerDatabaseRecoveryModel + +This resource set the recovery model for a database. The recovery model controls how transactions are logged, whether the transaction log requires (and allows) backing up, and what kinds of restore operations are available. +Three recovery models exist: full, simple, and bulk-logged. +Read more about recovery model in this article [View or Change the Recovery Model of a Database](https://msdn.microsoft.com/en-us/library/ms189272.aspx) + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] Name** _(Key)_: The SQL database name. +* **[String] SQLServer** _(Key)_: The host name of the SQL Server to be configured. +* **[String] SQLInstanceName** _(Key)_: The name of the SQL instance to be configured. +* **[String] RecoveryModel** _(Required)_: The recovery model to use for the database. { Full | Simple | BulkLogged }. + +#### Examples + +* [Set the RecoveryModel of a database](/Examples/Resources/xSQLServerDatabaseRecoveryModel/1-SetDatabaseRecoveryModel.ps1) + ### xSQLServerDatabaseRole No description. diff --git a/Tests/Unit/MSFT_xSQLServerDatabaseRecoveryModel.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerDatabaseRecoveryModel.Tests.ps1 new file mode 100644 index 000000000..91abe6df2 --- /dev/null +++ b/Tests/Unit/MSFT_xSQLServerDatabaseRecoveryModel.Tests.ps1 @@ -0,0 +1,236 @@ +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerDatabaseRecoveryModel' + +#region HEADER + +# Unit Test Template Version: 1.1.0 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Test Initialization + + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + + $nodeName = 'localhost' + $instanceName = 'MSSQLSERVER' + + $defaultParameters = @{ + SQLInstanceName = $instanceName + SQLServer = $nodeName + } + + #endregion Pester Test Initialization + + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Mock -CommandName Get-SqlDatabaseRecoveryModel -MockWith { + return 'Simple' + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the database does not exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownDatabase' + RecoveryModel = 'Full' + } + + $result = Get-TargetResource @testParameters + + It 'Should return null for RecoveryModel' { + $result.RecoveryModel | Should Be $null + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + It 'Should not call the mock function Get-SqlDatabaseRecoveryModel' { + Assert-MockCalled Get-SqlDatabaseRecoveryModel -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Full' + } + + $result = Get-TargetResource @testParameters + + It 'Should return wrong RecoveryModel' { + $result.RecoveryModel | Should Not Be $testParameters.RecoveryModel + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } + + It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseRecoveryModel' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabaseRecoveryModel -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state for a database' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Simple' + } + + $result = Get-TargetResource @testParameters + + It 'Should return the correct RecoveryModel' { + $result.RecoveryModel | Should Be $testParameters.RecoveryModel + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } + + It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseRecoveryModel' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabaseRecoveryModel -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Mock -CommandName Get-SqlDatabaseRecoveryModel -MockWith { + return 'Simple' + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + It 'Should return the state as false when desired recovery model is not correct' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Full' + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + + Context 'When the system is in the desired state' { + It 'Should return the state as true when desired recovery model is correct' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Simple' + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When desired database does not exist' { + It 'Should not call the mock function Set-SqlDatabaseRecoveryModel' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownDatabase' + RecoveryModel = 'Simple' + } + + Mock -CommandName Set-SqlDatabaseRecoveryModel -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlDatabaseRecoveryModel -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + } + + Context 'When the desired recovery model is not set' { + It 'Should call the function Set-SqlDatabaseRecoveryModel when desired recovery model should be present' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Simple' + } + + Mock -CommandName Set-SqlDatabaseRecoveryModel -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlDatabaseRecoveryModel -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + } +} +finally +{ + #region FOOTER + + Restore-TestEnvironment -TestEnvironment $TestEnvironment + + #endregion +} diff --git a/Tests/Unit/xSQLServerHelper.Tests.ps1 b/Tests/Unit/xSQLServerHelper.Tests.ps1 index 078e07091..fdcb908fa 100644 --- a/Tests/Unit/xSQLServerHelper.Tests.ps1 +++ b/Tests/Unit/xSQLServerHelper.Tests.ps1 @@ -201,4 +201,85 @@ InModuleScope $script:moduleName { } } } + + Describe "Testing Get-SqlDatabaseRecoveryModel" { + $mockSqlServerObject = [pscustomobject]@{ + InstanceName = 'MSSQLSERVER' + ComputerNamePhysicalNetBIOS = 'SQL01' + Databases = @{ + AdventureWorks = @{ + RecoveryModel = 'Full' + } + } + } + + Context 'When the specified database does not exist' { + $testParameters = @{ + SqlServerObject = $mockSqlServerObject + DatabaseName = 'UnknownDatabase' + } + + It 'Should throw the correct error' { + { Get-SqlDatabaseRecoveryModel @testParameters } | Should Throw "Database 'UnknownDatabase' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Context 'When the specified database exist' { + $testParameters = @{ + SqlServerObject = $mockSqlServerObject + DatabaseName = 'AdventureWorks' + } + + It 'Should return the current RecoveryModel' { + $recoveryModel = Get-SqlDatabaseRecoveryModel @testParameters + $recoveryModel | Should Be $testParameters.SqlServerObject.Databases.AdventureWorks.RecoveryModel + } + } + + Assert-VerifiableMocks + } + + Describe "Testing Set-SqlDatabaseRecoveryModel" { + $mockSqlServerObject = [pscustomobject]@{ + InstanceName = 'MSSQLSERVER' + ComputerNamePhysicalNetBIOS = 'SQL01' + Databases = @{ + AdventureWorks = @{ + RecoveryModel = 'Full' + } | Add-Member -MemberType ScriptMethod -Name Alter -Value { + if ( $this.RecoveryModel -ne $mockExpectedRecoveryModelForAlterMethod ) + { + throw "Called mocked Alter() method without setting the right RecoveryModel. Expected '{0}'. But was '{1}'." -f $mockExpectedRecoveryModelForAlterMethod, $this.RecoveryModel + } + } -PassThru -Force + } + } + + Context 'When the specified database does not exist' { + $testParameters = @{ + SqlServerObject = $mockSqlServerObject + DatabaseName = 'UnknownDatabase' + RecoveryModel = 'Simple' + } + + It 'Should throw the correct error' { + { Set-SqlDatabaseRecoveryModel @testParameters } | Should Throw "Database 'UnknownDatabase' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Context 'When the specified database and the system is not in desired state' { + $testParameters = @{ + SqlServerObject = $mockSqlServerObject + DatabaseName = 'AdventureWorks' + RecoveryModel = 'Simple' + } + + It 'Should set the correct RecoveryModel without throwing an error' { + $mockExpectedRecoveryModelForAlterMethod = $testParameters.RecoveryModel + { Set-SqlDatabaseRecoveryModel @testParameters } | Should Not Throw + } + } + + Assert-VerifiableMocks + } } diff --git a/xSQLServerHelper.psm1 b/xSQLServerHelper.psm1 index 2f0c08cce..6f03050ec 100644 --- a/xSQLServerHelper.psm1 +++ b/xSQLServerHelper.psm1 @@ -1286,3 +1286,106 @@ function Restart-SqlService } } } + +<# + .SYNOPSIS + This cmdlet is used to return the recovery model of a SQL database + + .PARAMETER SqlServerObject + This is the SQL Server object returned by Connect-SQL + + .PARAMETER DatabaseName + This is the name of the SQL database +#> +function Get-SqlDatabaseRecoveryModel +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [System.Object] + $SqlServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $DatabaseName + ) + + Write-Verbose -Message "Getting the recovery model used by the database $DatabaseName" + $sqlDatabase = $SqlServerObject.Databases[$DatabaseName] + $sqlInstanceName = $SqlServerObject.InstanceName + $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS + + if ($sqlDatabase) + { + $sqlDatabaseRecoveryModel = $sqlDatabase.RecoveryModel + Write-Verbose -Message "The current recovery model used by database $Name is '$sqlDatabaseRecoveryModel'" + } + else + { + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($DatabaseName,$sqlServer,$sqlInstanceName) ` + -ErrorCategory InvalidResult + } + + $sqlDatabaseRecoveryModel +} + +<# + .SYNOPSIS + This cmdlet is used to set the recovery model of a SQL database + + .PARAMETER SqlServerObject + This is the SQL Server object returned by Connect-SQL + + .PARAMETER DatabaseName + This is the name of the SQL database + + .PARAMETER RecoveryModel + The recovery model to set on the databases. Valid values are 'Simple','Full' and 'BulkLogged' +#> +function Set-SqlDatabaseRecoveryModel +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [System.Object] + $SqlServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $DatabaseName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Full','Simple','BulkLogged')] + [System.String] + $RecoveryModel + ) + + Write-Verbose -Message "Setting the recovery model for the database $DatabaseName" + $sqlDatabase = $SqlServerObject.Databases[$DatabaseName] + $sqlInstanceName = $SqlServerObject.InstanceName + $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS + + if ($sqlDatabase) + { + if($sqlDatabase.RecoveryModel -ne $RecoveryModel) + { + $sqlDatabase.RecoveryModel = $RecoveryModel + $sqlDatabase.Alter() + New-VerboseMessage -Message "The recovery model for the database $DatabaseName is changed to '$RecoveryModel'." + } + } + else + { + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($DatabaseName,$sqlServer,$sqlInstanceName) ` + -ErrorCategory InvalidResult + } +} From 462309ff5b9d00f9c0c7511572d49b5cac510fb3 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 18 Jan 2017 21:21:59 +0100 Subject: [PATCH 21/31] Changes to README.md (#318) - Change anchor link for xSQLServerDatabaseRecoveryModel to lower-case. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ed463cee..34f0d63a6 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ A full list of changes in each version can be found in the [change log](CHANGELO * [**xSQLServerDatabase**](#xsqlserverdatabase) resource to manage ensure database is present or absent * [**xSQLServerDatabaseOwner**](#xsqlserverdatabaseowner) resource to manage SQL database owners * [**xSQLServerDatabasePermissions**](#xsqlserverdatabasepermissions) resource to manage SQL database permissions -* [**xSQLServerDatabaseRecoveryModel**](#xSQLServerDatabaseRecoveryModel) resource to manage database recovery model +* [**xSQLServerDatabaseRecoveryModel**](#xsqlserverdatabaserecoverymodel) resource to manage database recovery model * [**xSQLServerDatabaseRole**](#xsqlserverdatabaserole) resource to manage SQL database roles * [**xSQLServerEndpoint**](#xsqlserverendpoint) resource to ensure database endpoint is present or absent * [**xSQLServerEndpointPermission**](#xsqlserverendpointpermission) Grant or revoke permission on the endpoint. From d35c7e5392a1acc4a903b9a369454cf1710d570f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 19 Jan 2017 18:18:20 +0100 Subject: [PATCH 22/31] Resolved bugs in tests for xSQLServerSetup --- Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 index 0f50b1da0..49646a28b 100644 --- a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 @@ -636,7 +636,7 @@ try Mock -CommandName New-SmbMapping -Verifiable Mock -CommandName Remove-SmbMapping -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable @@ -743,6 +743,7 @@ try Mock -CommandName New-SmbMapping -Verifiable Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" @@ -886,7 +887,7 @@ try # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error Mock -CommandName Get-CimInstance -MockWith { throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" - } + } -Verifiable #endregion Mock Get-CimInstance Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -1076,7 +1077,7 @@ try # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error Mock -CommandName Get-CimInstance -MockWith { throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" - } + } -Verifiable #endregion Mock Get-CimInstance Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -1238,7 +1239,7 @@ try } Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable @@ -1377,7 +1378,7 @@ try # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error Mock -CommandName Get-CimInstance -MockWith { throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" - } + } -Verifiable #endregion Mock Get-CimInstance Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -1661,7 +1662,7 @@ try # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error Mock -CommandName Get-CimInstance -MockWith { throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" - } + } -Verifiable #endregion Mock Get-CimInstance # Change the default features for this test. @@ -1760,7 +1761,7 @@ try # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error Mock -CommandName Get-CimInstance -MockWith { throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" - } + } -Verifiable #endregion Mock Get-CimInstance # Change the default features for this test. @@ -1883,7 +1884,7 @@ try # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error Mock -CommandName Get-CimInstance -MockWith { throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" - } + } -Verifiable #endregion Mock Get-CimInstance Mock -CommandName Get-ItemProperty -ParameterFilter { From 97adcde034ef735df4c704884a5168dbda2a5a20 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 19 Jan 2017 18:23:07 +0100 Subject: [PATCH 23/31] Resolved bug in test for xSQLServerScript --- Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 index d6db145cc..03166829b 100644 --- a/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 @@ -80,7 +80,7 @@ try $result.SetFilePath | Should Be $testParameters.SetFilePath $result.GetFilePath | Should Be $testParameters.GetFilePath $result.TestFilePath | Should Be $testParameters.TestFilePath - $result.GetType() | Should Be "hashtable" + $result | Should BeOfType Hashtable } } From eb756020daedc7c313e33b125b2b754fa1d750c5 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 19 Jan 2017 18:24:51 +0100 Subject: [PATCH 24/31] Resolved bug in test for xSQLServerDatabaseOwner --- .../MSFT_xSQLServerDatabaseOwner.Tests.ps1 | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/Tests/Unit/MSFT_xSQLServerDatabaseOwner.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerDatabaseOwner.Tests.ps1 index a96023522..a4e1bc2ed 100644 --- a/Tests/Unit/MSFT_xSQLServerDatabaseOwner.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerDatabaseOwner.Tests.ps1 @@ -15,7 +15,7 @@ Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\ $TestEnvironment = Initialize-TestEnvironment -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` - -TestType Unit + -TestType Unit #endregion HEADER # Begin Testing @@ -35,12 +35,12 @@ try Describe "$($script:DSCResourceName)\Get-TargetResource" { Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | + return New-Object Object | Add-Member ScriptProperty Databases { return @{ 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) } - } -PassThru -Force + } -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable Context 'When the system is not in the desired state' { @@ -50,7 +50,7 @@ try Name = 'CONTOSO\SqlServiceAcct' } - Mock -CommandName Get-SqlDatabaseOwner -MockWith { + Mock -CommandName Get-SqlDatabaseOwner -MockWith { return $null } -ModuleName $script:DSCResourceName -Verifiable @@ -79,7 +79,7 @@ try Name = 'CONTOSO\SqlServiceAcct' } - Mock -CommandName Get-SqlDatabaseOwner -MockWith { + Mock -CommandName Get-SqlDatabaseOwner -MockWith { return $null } -ModuleName $script:DSCResourceName -Verifiable @@ -133,12 +133,12 @@ try Describe "$($script:DSCResourceName)\Test-TargetResource" { Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | + return New-Object Object | Add-Member ScriptProperty Databases { return @{ 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) } - } -PassThru -Force + } -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable Context 'When the system is not in the desired state' { @@ -147,9 +147,9 @@ try $testParameters += @{ Database = 'AdventureWorks' Name = 'CONTOSO\SqlServiceAcct' - } + } - Mock -CommandName Get-SqlDatabaseOwner -MockWith { + Mock -CommandName Get-SqlDatabaseOwner -MockWith { return $null } -ModuleName $script:DSCResourceName -Verifiable @@ -167,10 +167,10 @@ try $testParameters += @{ Database = 'AdventureWorks' Name = 'CONTOSO\SqlServiceAcct' - } + } - Mock -CommandName Get-SqlDatabaseOwner -MockWith { - 'CONTOSO\SqlServiceAcct' + Mock -CommandName Get-SqlDatabaseOwner -MockWith { + 'CONTOSO\SqlServiceAcct' } -ModuleName $script:DSCResourceName -Verifiable $result = Test-TargetResource @testParameters @@ -186,12 +186,12 @@ try Describe "$($script:DSCResourceName)\Set-TargetResource" { Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | + return New-Object Object | Add-Member ScriptProperty Databases { return @{ 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) } - } -PassThru -Force + } -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable Context 'When the system is not in the desired state' { @@ -203,22 +203,22 @@ try It 'Should call the function Set-SqlDatabaseOwner when desired login is not the database owner' { Mock -CommandName Set-SqlDatabaseOwner -MockWith { } -ModuleName $script:DSCResourceName -Verifiable - + Set-TargetResource @testParameters - + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - + $testParameters.Database = 'UnknownDatabase' It 'Should throw an error when desired database does not exist' { Mock -CommandName Set-SqlDatabaseOwner -MockWith { return Throw } -ModuleName $script:DSCResourceName -Verifiable - + { Set-TargetResource @testParameters } | Should Throw "Failed to setting the owner of database UnknownDatabase" - + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } @@ -232,12 +232,16 @@ try } It 'Should not call the function Set-SqlDatabaseOwner when desired login is the database owner' { - Mock -CommandName Get-SqlDatabaseOwner -MockWith { - 'CONTOSO\SqlServiceAcct' + Mock -CommandName Get-SqlDatabaseOwner -MockWith { + 'CONTOSO\SqlServiceAcct' } -ModuleName $script:DSCResourceName -Verifiable - + + Mock -CommandName Set-SqlDatabaseOwner -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable + $result = Get-TargetResource @testParameters - + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It @@ -251,7 +255,7 @@ finally { #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Restore-TestEnvironment -TestEnvironment $TestEnvironment #endregion } From c6c0a7c5f2648cc2026490fe22ece585084c09fc Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 21 Jan 2017 09:37:33 +0100 Subject: [PATCH 25/31] AppVeyor: Resolves deploy step failing (#325) - Changes to AppVeyor - Deploy step was failing when merging code into Dev, or pushing code to a fork. --- CHANGELOG.md | 1 + appveyor.yml | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6cc44c59..be747a068 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Improvements how tests are initiated in AppVeyor - Removed previous workaround (issue #201) from unit tests. - Changes in appveyor.yml so that SQL modules are removed before common test is run. + - Now the deploy step are no longer failing when merging code into Dev. Neither is the deploy step failing if a contributor had AppVeyor connected to the fork of xSQLServer and pushing code to the fork. - Changes to README.md - Changed the contributing section to help new contributors. - Added links for each resource so it is easier to navigate to the parameter list for each resource. diff --git a/appveyor.yml b/appveyor.yml index 6c5d1f65e..c8880e62b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,6 +4,7 @@ version: 4.0.{build}.0 install: - git clone https://github.com/PowerShell/DscResource.Tests + - appveyor DownloadFile https://dist.nuget.org/win-x86-commandline/v3.4.4/NuGet.exe - ps: | Import-Module -Name .\DscResource.Tests\TestHelper.psm1 -Force Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force @@ -52,7 +53,15 @@ deploy_script: # Creating NuGet package artifact New-Nuspec -packageName $env:APPVEYOR_PROJECT_NAME -version $env:APPVEYOR_BUILD_VERSION -author "Microsoft" -owners "Microsoft" -licenseUrl "https://github.com/PowerShell/DscResources/blob/master/LICENSE" -projectUrl "https://github.com/$($env:APPVEYOR_REPO_NAME)" -packageDescription $env:APPVEYOR_PROJECT_NAME -tags "DesiredStateConfiguration DSC DSCResourceKit" -destinationPath . - nuget pack ".\$($env:APPVEYOR_PROJECT_NAME).nuspec" -outputdirectory . + + # Force to use the downloaded Nuget v3.4.4, because newer versions of Nuget are failing the deploy step. + # See issue https://github.com/PowerShell/xNetworking/issues/177 + Start-Process -FilePath "$($env:APPVEYOR_BUILD_FOLDER)\nuget" -Wait -ArgumentList @( + "pack", + ".\$($env:APPVEYOR_PROJECT_NAME).nuspec", + "-outputdirectory $env:APPVEYOR_BUILD_FOLDER" + ) + $nuGetPackageName = $env:APPVEYOR_PROJECT_NAME + "." + $env:APPVEYOR_BUILD_VERSION + ".nupkg" $nuGetPackagePath = (Get-ChildItem $nuGetPackageName).FullName From 1f5b916efd5d0803afc2b0e469c1f0e3f4bb1d71 Mon Sep 17 00:00:00 2001 From: Jean-Cyril Drouhin Date: Mon, 23 Jan 2017 19:06:17 +0100 Subject: [PATCH 26/31] BREAKING CHANGE: Changes to xSQLServerDatabasePermissions (#140) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Changes to xSQLServerDatabasePermission - BREAKING CHANGE: Renamed xSQLServerDatabasePermissions to xSQLServerDatabasePermission to align wíth naming convention. - BREAKING CHANGE: The mandatory parameters now include PermissionState, SQLServer, and SQLInstanceName. - Added tests for resources - xSQLServerDatabasePermissions - Examples - xSQLServerDatabasePermission - 1-GrantDatabasePermissions.ps1 - 2-RevokeDatabasePermissions.ps1 - 3-DenyDatabasePermissions.ps1 --- CHANGELOG.md | 8 + .../MSFT_xSQLServerDatabasePermission.psm1 | 278 +++++++++++++ ...FT_xSQLServerDatabasePermission.schema.mof | 11 + .../MSFT_xSQLServerDatabasePermissions.psm1 | 201 ---------- ...T_xSQLServerDatabasePermissions.schema.mof | 9 - .../1-GrantDatabasePermissions.ps1 | 74 ++++ .../2-RevokeDatabasePermissions.ps1 | 42 ++ .../3-DenyDatabasePermissions.ps1 | 74 ++++ Examples/SQLPush_SingleServer.ps1 | 2 +- README.md | 23 +- ...SFT_xSQLServerDatabasePermission.Tests.ps1 | 359 +++++++++++++++++ Tests/Unit/Stubs/SMO.cs | 80 ++++ Tests/Unit/xSQLServerHelper.Tests.ps1 | 245 +++++++++++- xSQLServerHelper.psm1 | 367 +++++++++++++++++- 14 files changed, 1543 insertions(+), 230 deletions(-) create mode 100644 DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.psm1 create mode 100644 DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.schema.mof delete mode 100644 DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.psm1 delete mode 100644 DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.schema.mof create mode 100644 Examples/Resources/xSQLServerDatabasePermission/1-GrantDatabasePermissions.ps1 create mode 100644 Examples/Resources/xSQLServerDatabasePermission/2-RevokeDatabasePermissions.ps1 create mode 100644 Examples/Resources/xSQLServerDatabasePermission/3-DenyDatabasePermissions.ps1 create mode 100644 Tests/Unit/MSFT_xSQLServerDatabasePermission.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index be747a068..ec8ecf61a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,11 +66,19 @@ - Examples - xSQLServerDatabaseRecoveryModel - 1-SetDatabaseRecoveryModel.ps1 + - xSQLServerDatabasePermission + - 1-GrantDatabasePermissions.ps1 + - 2-RevokeDatabasePermissions.ps1 + - 3-DenyDatabasePermissions.ps1 - Added tests for resources - xSQLServerDatabaseRecoveryModel + - xSQLServerDatabasePermissions - Changes to xSQLServerDatabaseRecoveryModel - BREAKING CHANGE: Renamed xSQLDatabaseRecoveryModel to xSQLServerDatabaseRecoveryModel to align wíth naming convention. - BREAKING CHANGE: The mandatory parameters now include SQLServer, and SQLInstanceName. +- Changes to xSQLServerDatabasePermission + - BREAKING CHANGE: Renamed xSQLServerDatabasePermissions to xSQLServerDatabasePermission to align wíth naming convention. + - BREAKING CHANGE: The mandatory parameters now include PermissionState, SQLServer, and SQLInstanceName. ## 4.0.0.0 diff --git a/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.psm1 b/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.psm1 new file mode 100644 index 000000000..f0614f322 --- /dev/null +++ b/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.psm1 @@ -0,0 +1,278 @@ +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -ChildPath 'xSQLServerHelper.psm1') -Force + +<# + .SYNOPSIS + Returns the current permissions for the user in the database + + .PARAMETER Ensure + This is The Ensure if the permission should be granted (Present) or revoked (Absent) + Not used in Get-TargetResource + + .PARAMETER Database + This is the SQL database + + .PARAMETER Name + This is the name of the SQL login for the permission set + + .PARAMETER PermissionState + This is the state of permission set. Valid values are 'Grant' or 'Deny' + + .PARAMETER Permissions + This is a list that represents a SQL Server set of database permissions + + .PARAMETER SQLServer + This is the SQL Server for the database + + .PARAMETER SQLInstanceName + This is the SQL instance for the database +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [ValidateSet('Present','Absent')] + [System.String] + $Ensure, + + [parameter(Mandatory = $true)] + [System.String] + $Database, + + [parameter(Mandatory = $true)] + [System.String] + $Name, + + [parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny')] + [System.String] + $PermissionState, + + [parameter(Mandatory = $true)] + [System.String[]] + $Permissions, + + [parameter(Mandatory = $true)] + [System.String] + $SQLServer = $env:COMPUTERNAME, + + [parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName = 'MSSQLSERVER' + ) + + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + if ($sqlServerObject) + { + Write-Verbose -Message "Getting permissions for user '$Name' in database '$Database'" + $getSqlDatabasePermissionResult = Get-SqlDatabasePermission -SqlServerObject $sqlServerObject ` + -Name $Name ` + -Database $Database ` + -PermissionState $PermissionState + + if ($getSqlDatabasePermissionResult) + { + $resultOfPermissionCompare = Compare-Object -ReferenceObject $Permissions ` + -DifferenceObject $getSqlDatabasePermissionResult + if ($null -eq $resultOfPermissionCompare) + { + $Ensure = 'Present' + } + else + { + $Ensure = 'Absent' + } + } + else + { + $Ensure = 'Absent' + } + } + else + { + throw New-TerminatingError -ErrorType ConnectSQLError ` + -FormatArgs @($SQLServer,$SQLInstanceName) ` + -ErrorCategory InvalidOperation + } + + $returnValue = @{ + Ensure = $Ensure + Database = $Database + Name = $Name + PermissionState = $PermissionState + Permissions = $getSqlDatabasePermissionResult + SQLServer = $SQLServer + SQLInstanceName = $SQLInstanceName + } + + $returnValue +} + +<# + .SYNOPSIS + Sets the permissions for the user in the database. + + .PARAMETER Ensure + This is The Ensure if the permission should be granted (Present) or revoked (Absent) + + .PARAMETER Database + This is the SQL database + + .PARAMETER Name + This is the name of the SQL login for the permission set + + .PARAMETER PermissionState + This is the state of permission set. Valid values are 'Grant' or 'Deny' + + .PARAMETER Permissions + This is a list that represents a SQL Server set of database permissions + + .PARAMETER SQLServer + This is the SQL Server for the database + + .PARAMETER SQLInstanceName + This is the SQL instance for the database +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [ValidateSet('Present','Absent')] + [System.String] + $Ensure = 'Present', + + [parameter(Mandatory = $true)] + [System.String] + $Database, + + [parameter(Mandatory = $true)] + [System.String] + $Name, + + [parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny')] + [System.String] + $PermissionState, + + [parameter(Mandatory = $true)] + [System.String[]] + $Permissions, + + [parameter(Mandatory = $true)] + [System.String] + $SQLServer = $env:COMPUTERNAME, + + [parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName = 'MSSQLSERVER' + ) + + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + if ($sqlServerObject) + { + Write-Verbose -Message "Setting permissions of database '$Database' for login '$Name'" + + if ($Ensure -eq 'Present') + { + Add-SqlDatabasePermission -SqlServerObject $sqlServerObject ` + -Name $Name ` + -Database $Database ` + -PermissionState $PermissionState ` + -Permissions $Permissions + + New-VerboseMessage -Message "$PermissionState - SQL Permissions for $Name, successfullly added in $Database" + } + else + { + Remove-SqlDatabasePermission -SqlServerObject $sqlServerObject ` + -Name $Name ` + -Database $Database ` + -PermissionState $PermissionState ` + -Permissions $Permissions + + New-VerboseMessage -Message "$PermissionState - SQL Permissions for $Name, successfullly removed in $Database" + } + } + else + { + throw New-TerminatingError -ErrorType ConnectSQLError ` + -FormatArgs @($SQLServer,$SQLInstanceName) ` + -ErrorCategory InvalidOperation + } +} + +<# + .SYNOPSIS + Tests if the permissions is set for the user in the database + + .PARAMETER Ensure + This is The Ensure if the permission should be granted (Present) or revoked (Absent) + + .PARAMETER Database + This is the SQL database + + .PARAMETER Name + This is the name of the SQL login for the permission set + + .PARAMETER PermissionState + This is the state of permission set. Valid values are 'Grant' or 'Deny' + + .PARAMETER Permissions + This is a list that represents a SQL Server set of database permissions + + .PARAMETER SQLServer + This is the SQL Server for the database + + .PARAMETER SQLInstanceName + This is the SQL instance for the database +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [ValidateSet('Present','Absent')] + [System.String] + $Ensure = 'Present', + + [parameter(Mandatory = $true)] + [System.String] + $Database, + + [parameter(Mandatory = $true)] + [System.String] + $Name, + + [parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny')] + [System.String] + $PermissionState, + + [parameter(Mandatory = $true)] + [System.String[]] + $Permissions, + + [parameter(Mandatory = $true)] + [System.String] + $SQLServer = $env:COMPUTERNAME, + + [parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName = 'MSSQLSERVER' + ) + + Write-Verbose -Message "Evaluating permissions for user '$Name' in database '$Database'." + + $getTargetResourceResult = Get-TargetResource @PSBoundParameters + + return Test-SQLDscParameterState -CurrentValues $getTargetResourceResult ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck @('Name', 'Ensure', 'PermissionState', 'Permissions') +} + +Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.schema.mof b/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.schema.mof new file mode 100644 index 000000000..5d58b58a1 --- /dev/null +++ b/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.schema.mof @@ -0,0 +1,11 @@ +[ClassVersion("1.0.0.0"), FriendlyName("xSQLServerDatabasePermission")] +class MSFT_xSQLServerDatabasePermission : OMI_BaseResource +{ + [Write, Description("If the values should be present or absent. Valid values are 'Present' or 'Absent'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Key, Description("The name of the database.")] String Database; + [Key, Description("The name of the user that should be granted or denied the permission.")] String Name; + [Key, Description("The state of the permission. Valid values are 'Grant' or 'Deny'."), ValueMap{"Grant","Deny"}, Values{"Grant","Deny"}] String PermissionState; + [Required, Description("The set of permissions for the SQL database.")] String Permissions[]; + [Key, Description("The host name of the SQL Server to be configured.")] String SQLServer; + [Key, Description("The name of the SQL instance to be configured.")] String SQLInstanceName; +}; diff --git a/DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.psm1 b/DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.psm1 deleted file mode 100644 index 6645d6ccc..000000000 --- a/DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.psm1 +++ /dev/null @@ -1,201 +0,0 @@ -$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Write-Debug -Message "CurrentPath: $currentPath" - -# Load Common Code -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -Verbose:$false -ErrorAction Stop - -# DSC resource to manage SQL database permissions - -# NOTE: This resource requires WMF5 and PsDscRunAsCredential - -function Get-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [parameter(Mandatory = $true)] - [System.String] - $Database, - - [parameter(Mandatory = $true)] - [System.String] - $Name, - - [parameter(Mandatory = $true)] - [System.String[]] - $Permissions, - - [System.String] - $SQLServer = $env:COMPUTERNAME, - - [System.String] - $SQLInstanceName = "MSSQLSERVER" - ) - - if(!$SQL) - { - $SQL = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - } - - if($SQL) - { - # Check database exists - if(!($SQLDatabase = $SQL.Databases[$Database])) - { - throw New-TerminatingError -ErrorType NoDatabase -FormatArgs @($Database,$SQLServer,$SQLInstanceName) -ErrorCategory InvalidResult - } - - # Check login exists - if(!($SQLLogin = $SQL.Logins[$Name])) - { - throw New-TerminatingError -ErrorType LoginNotFound -FormatArgs @($Name,$SQLServer,$SQLInstanceName) -ErrorCategory ObjectNotFound - } - - $Permissions = @() - $PermissionSet = $SQLDatabase.EnumDatabasePermissions($Name) - foreach($Permission in $PermissionSet) - { - $Properties = ($Permission.PermissionType | Get-Member -MemberType Property).Name - foreach($Property in $Properties) - { - if($Permission.PermissionType."$Property") - { - $Permissions += $Property - } - } - } - } - else - { - $Name = $null - } - - $returnValue = @{ - Database = $Database - Name = $Name - Permissions = $Permissions - SQLServer = $SQLServer - SQLInstanceName = $SQLInstanceName - } - - $returnValue -} - - -function Set-TargetResource -{ - [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [System.String] - $Database, - - [parameter(Mandatory = $true)] - [System.String] - $Name, - - [parameter(Mandatory = $true)] - [System.String[]] - $Permissions, - - [System.String] - $SQLServer = $env:COMPUTERNAME, - - [System.String] - $SQLInstanceName = "MSSQLSERVER" - ) - - if(!$SQL) - { - $SQL = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - } - - if($SQL) - { - $SQLDatabase = $SQL.Databases[$Database] - - if(!$SQLDatabase.Users[$Name]) - { - try - { - Write-Verbose "Adding SQL login $Name as a user of database $Database on $SQLServer\$SQLInstanceName" - $SQLDatabaseUser = New-Object Microsoft.SqlServer.Management.Smo.User $SQLDatabase,$Name - $SQLDatabaseUser.Login = $Name - $SQLDatabaseUser.Create() - } - catch - { - Write-Verbose "Failed adding SQL login $Name as a user of database $Database on $SQLServer\$SQLInstanceName" - } - } - - if($SQLDatabase.Users[$Name]) - { - try - { - Write-Verbose "Granting SQL login $Name to permissions $Permissions on database $Database on $SQLServer\$SQLInstanceName" - $PermissionSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabasePermissionSet - foreach($Permission in $Permissions) - { - $PermissionSet."$Permission" = $true - } - $SQLDatabase.Grant($PermissionSet,$Name) - } - catch - { - Write-Verbose "Failed granting SQL login $Name to permissions $Permissions on database $Database on $SQLServer\$SQLInstanceName" - } - } - } - - if(!(Test-TargetResource @PSBoundParameters)) - { - throw New-TerminatingError -ErrorType TestFailedAfterSet -ErrorCategory InvalidResult - } -} - - -function Test-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [parameter(Mandatory = $true)] - [System.String] - $Database, - - [parameter(Mandatory = $true)] - [System.String] - $Name, - - [parameter(Mandatory = $true)] - [System.String[]] - $Permissions, - - [System.String] - $SQLServer = $env:COMPUTERNAME, - - [System.String] - $SQLInstanceName = "MSSQLSERVER" - ) - - $SQLDatabasePermissions = (Get-TargetResource @PSBoundParameters).Permissions - - $result = $true - foreach($Permission in $Permissions) - { - if($SQLDatabasePermissions -notcontains $Permission) - { - Write-Verbose "Failed test for permission $Permission" - $result = $false - } - } - - $result -} - - -Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.schema.mof b/DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.schema.mof deleted file mode 100644 index 0b2d24a1f..000000000 --- a/DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.schema.mof +++ /dev/null @@ -1,9 +0,0 @@ -[ClassVersion("1.0.0.0"), FriendlyName("xSQLServerDatabasePermissions")] -class MSFT_xSQLServerDatabasePermissions : OMI_BaseResource -{ - [Key, Description("The SQL database.")] String Database; - [Required, Description("The name of the SQL login for the permission set.")] String Name; - [Required, Description("The set of permissions for the SQL database.")] String Permissions[]; - [Write, Description("The SQL Server for the database.")] String SQLServer; - [Write, Description("The SQL instance for the database.")] String SQLInstanceName; -}; diff --git a/Examples/Resources/xSQLServerDatabasePermission/1-GrantDatabasePermissions.ps1 b/Examples/Resources/xSQLServerDatabasePermission/1-GrantDatabasePermissions.ps1 new file mode 100644 index 000000000..ad468721b --- /dev/null +++ b/Examples/Resources/xSQLServerDatabasePermission/1-GrantDatabasePermissions.ps1 @@ -0,0 +1,74 @@ +<# +.DESCRIPTION + This example shows how to ensure that the user account CONTOSO\SQLAdmin + has "Connect" and "Update" SQL Permissions for database "AdventureWorks". +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerLogin Add_SqlServerLogin_SQLAdmin + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerLogin Add_SqlServerLogin_SQLUser + { + Ensure = 'Present' + Name = 'CONTOSO\SQLUser' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission Grant_SqlDatabasePermissions_SQLAdmin_Db01 + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = 'Connect','Update' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission Grant_SqlDatabasePermissions_SQLUser_Db01 + { + Ensure = 'Present' + Name = 'CONTOSO\SQLUser' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = 'Connect','Update' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission Grant_SqlDatabasePermissions_SQLAdmin_Db02 + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorksLT' + PermissionState = 'Grant' + Permissions = 'Connect','Update' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerDatabasePermission/2-RevokeDatabasePermissions.ps1 b/Examples/Resources/xSQLServerDatabasePermission/2-RevokeDatabasePermissions.ps1 new file mode 100644 index 000000000..e156e9f29 --- /dev/null +++ b/Examples/Resources/xSQLServerDatabasePermission/2-RevokeDatabasePermissions.ps1 @@ -0,0 +1,42 @@ +<# +.DESCRIPTION + This example shows how to ensure that the user account CONTOSO\SQLAdmin + hasn't "Select" and "Create Table" SQL Permissions for database "AdventureWorks". +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerDatabasePermission RevokeGrant_SqlDatabasePermissions_SQLAdmin + { + Ensure = 'Absent' + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = 'Connect','Update' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission RevokeDeny_SqlDatabasePermissions_SQLAdmin + { + Ensure = 'Absent' + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorks' + PermissionState = 'Deny' + Permissions = 'Select','Create Table' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerDatabasePermission/3-DenyDatabasePermissions.ps1 b/Examples/Resources/xSQLServerDatabasePermission/3-DenyDatabasePermissions.ps1 new file mode 100644 index 000000000..32e589e1c --- /dev/null +++ b/Examples/Resources/xSQLServerDatabasePermission/3-DenyDatabasePermissions.ps1 @@ -0,0 +1,74 @@ +<# +.DESCRIPTION + This example shows how to ensure that the user account CONTOSO\SQLAdmin + has "Connect" and "Update" SQL Permissions for database "AdventureWorks". +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerLogin Add_SqlServerLogin_SQLAdmin + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerLogin Add_SqlServerLogin_SQLUser + { + Ensure = 'Present' + Name = 'CONTOSO\SQLUser' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission Deny_SqlDatabasePermissions_SQLAdmin_Db01 + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorks' + PermissionState = 'Deny' + Permissions = 'Select','Create Table' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission Deny_SqlDatabasePermissions_SQLUser_Db01 + { + Ensure = 'Present' + Name = 'CONTOSO\SQLUser' + Database = 'AdventureWorks' + PermissionState = 'Deny' + Permissions = 'Select','Create Table' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission Deny_SqlDatabasePermissions_SQLAdmin_Db02 + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorksLT' + PermissionState = 'Deny' + Permissions = 'Select','Create Table' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/SQLPush_SingleServer.ps1 b/Examples/SQLPush_SingleServer.ps1 index 01b783c2b..8596ede98 100644 --- a/Examples/SQLPush_SingleServer.ps1 +++ b/Examples/SQLPush_SingleServer.ps1 @@ -128,7 +128,7 @@ Configuration SQLSA Database = "TestDB" Name = "TestUser2" } - xSQLServerDatabasePermissions($Node.Nodename) + xSQLServerDatabasePermission($Node.Nodename) { Database = "Model" Name = "TestUser1" diff --git a/README.md b/README.md index 34f0d63a6..b6420dcad 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ A full list of changes in each version can be found in the [change log](CHANGELO * [**xSQLServerConfiguration**](#xsqlserverconfiguration) resource to manage [SQL Server Configuration Options](https://msdn.microsoft.com/en-us/library/ms189631.aspx) * [**xSQLServerDatabase**](#xsqlserverdatabase) resource to manage ensure database is present or absent * [**xSQLServerDatabaseOwner**](#xsqlserverdatabaseowner) resource to manage SQL database owners -* [**xSQLServerDatabasePermissions**](#xsqlserverdatabasepermissions) resource to manage SQL database permissions +* [**xSQLServerDatabasePermission**](#xsqlserverdatabasepermission) resource to manage SQL database permissions * [**xSQLServerDatabaseRecoveryModel**](#xsqlserverdatabaserecoverymodel) resource to manage database recovery model * [**xSQLServerDatabaseRole**](#xsqlserverdatabaserole) resource to manage SQL database roles * [**xSQLServerEndpoint**](#xsqlserverendpoint) resource to ensure database endpoint is present or absent @@ -276,9 +276,10 @@ No description. * [Set database owner](/Examples/Resources/xSQLServerDatabaseOwner/1-SetDatabaseOwner.ps1) -### xSQLServerDatabasePermissions +### xSQLServerDatabasePermission -No description. +This resource is used to grant, deny or revoke permissions for a user in a database. +For more information about permissions, please read the article [Permissions (Database Engine)](https://msdn.microsoft.com/en-us/library/ms191291.aspx). #### Requirements @@ -287,15 +288,19 @@ No description. #### Parameters -* **[String] Database** _(Key)_: The SQL Database -* **[String] Name** _(Required)_: The name of permissions for the SQL database -* **[String[]] Permissions** _(Required)_: The set of Permissions for the SQL database -* **[String] SQLServer** _(Write)_: The SQL Server for the database -* **[String] SQLInstanceName** _(Write)_: The SQL instance for the database +* **[String] Ensure** _(Write)_: If the permission should be granted (Present) or revoked (Absent). { Present | Absent }. +* **[String] Database** _(Key)_: The name of the database. +* **[String] Name** _(Key)_: The name of the user that should be granted or denied the permission. +* **[String[]] Permissions** _(Required)_: The permissions to be granted or denied for the user in the database. Valid permissions can be found in the article [SQL Server Permissions](https://msdn.microsoft.com/en-us/library/ms191291.aspx#Anchor_3). +* **[String] PermissionState** _(Key)_: The state of the permission. { Grant | Deny }. +* **[String] SQLServer** _(Key)_: The host name of the SQL Server to be configured. Default values is 'env:COMPUTERNAME'. +* **[String] SQLInstanceName** _(Key)_: The name of the SQL instance to be configured. Default value is 'MSSQLSERVER'. #### Examples -None. +* [Grant Database Permission](/Examples/Resources/xSQLServerDatabasePermission/1-GrantDatabasePermissions.ps1) +* [Revoke Database Permission](/Examples/Resources/xSQLServerDatabasePermission/2-RevokeDatabasePermissions.ps1) +* [Deny Database Permission](/Examples/Resources/xSQLServerDatabasePermission/3-DenyDatabasePermissions.ps1) ### xSQLServerDatabaseRecoveryModel diff --git a/Tests/Unit/MSFT_xSQLServerDatabasePermission.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerDatabasePermission.Tests.ps1 new file mode 100644 index 000000000..faa2ddb2c --- /dev/null +++ b/Tests/Unit/MSFT_xSQLServerDatabasePermission.Tests.ps1 @@ -0,0 +1,359 @@ +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerDatabasePermission' + +#region HEADER + +# Unit Test Template Version: 1.1.0 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Test Initialization + + $defaultParameters = @{ + SQLInstanceName = 'MSSQLSERVER' + SQLServer = 'localhost' + Database = 'AdventureWorks' + Name = 'CONTOSO\SqlServiceAcct' + } + + #endregion Pester Test Initialization + + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + Mock -CommandName Get-SqlDatabasePermission -MockWith { + return $null + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the state as absent' { + $result.Ensure | Should Be 'Absent' + $result.Permissions | Should Be $null + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + $result.PermissionState | Should Be $testParameters.PermissionState + } + + It 'Should call the mock functions Connect-SQL and Get-SqlDatabasePermission' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state for PermissionState equal to Grant' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + Mock -CommandName Get-SqlDatabasePermission -MockWith { return @( 'Connect','Update' ) } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the state as present' { + $result.Ensure | Should Be 'Present' + $result.Permissions | Should Be $testParameters.Permissions + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + $result.PermissionState | Should Be $testParameters.PermissionState + } + + It 'Should call the mock functions Connect-SQL and Get-SqlDatabasePermission' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state for PermissionState equal to Deny' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Deny' + Permissions = @( 'Connect','Update' ) + } + + Mock -CommandName Get-SqlDatabasePermission -MockWith { return @( 'Connect','Update' ) } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the state as present' { + $result.Ensure | Should Be 'Present' + $result.Permissions | Should Be $testParameters.Permissions + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + $result.PermissionState | Should Be $testParameters.PermissionState + } + + It 'Should call the mock functions Connect-SQL and Get-SqlDatabasePermission' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + + It 'Should return the state as false when desired permissions does not exist' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + Mock -CommandName Get-SqlDatabasePermission -MockWith { + return $null + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state' { + It 'Should return the state as true when desired permissions exist for PermissionState equal to Grant' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + Mock -CommandName Get-SqlDatabasePermission -MockWith { + @( 'Connect','Update') + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should return the state as true when desired permissions exist for PermissionState equal to Deny' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Deny' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + Mock -CommandName Get-SqlDatabasePermission -MockWith { + @( 'Connect','Update') + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Grant' + Ensure = 'Present' + Permissions = @( 'Connect','Update' ) + } + + It 'Should throw an error when desired database does not exist' { + Mock -CommandName Add-SqlDatabasePermission -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should throw an error when desired login does not exist' { + Mock -CommandName Add-SqlDatabasePermission -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Granting - Should call the function Add-SqlDatabasePermission when desired state is already present' { + Mock -CommandName Add-SqlDatabasePermission -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + $testParameters.Ensure = 'Absent' + + It 'Granting - Should call the function Remove-SqlDatabasePermission when desired state is already absent' { + Mock -CommandName Remove-SqlDatabasePermission -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Remove-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + $testParameters.PermissionState = 'Deny' + + It 'Denying - Should call the function Remove-SqlDatabasePermission when desired state is already absent' { + Mock -CommandName Remove-SqlDatabasePermission -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Remove-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + $testParameters.Ensure = 'Present' + + It 'Denying - Should call the function Remove-SqlDatabasePermission when desired state is already present' { + Mock -CommandName Add-SqlDatabasePermission -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + + Context 'When the system is in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Grant' + Ensure = 'Present' + Permissions = @( 'Connect','Update' ) + } + + It 'Should throw an error when desired database does not exist' { + Mock -CommandName Add-SqlDatabasePermission -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should throw an error when desired login does not exist' { + Mock -CommandName Add-SqlDatabasePermission -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should not call the function Add-SqlDatabasePermission when desired state is already present' { + Mock -CommandName Get-SqlDatabasePermission -MockWith { return @( 'Connect','Update' ) } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + + $testParameters.Ensure = 'Absent' + + It 'Should not call the function Remove-SqlDatabasePermission when desired state is already absent' { + Mock -CommandName Get-SqlDatabasePermission -MockWith { return $null } -ModuleName $script:DSCResourceName -Verifiable + Mock -CommandName Remove-SqlDatabasePermission -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Remove-SqlDatabasePermission -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + } + + Assert-VerifiableMocks + } +} +finally +{ + #region FOOTER + + Restore-TestEnvironment -TestEnvironment $TestEnvironment + + #endregion +} diff --git a/Tests/Unit/Stubs/SMO.cs b/Tests/Unit/Stubs/SMO.cs index 01f91d53e..bf50586ef 100644 --- a/Tests/Unit/Stubs/SMO.cs +++ b/Tests/Unit/Stubs/SMO.cs @@ -130,6 +130,45 @@ public ServerPermissionInfo( public string PermissionState = "Grant"; } + // TypeName: Microsoft.SqlServer.Management.Smo.DatabasePermissionSet + // BaseType: Microsoft.SqlServer.Management.Smo.PermissionSetBase + // Used by: + // xSQLServerDatabasePermission.Tests.ps1 + public class DatabasePermissionSet + { + public DatabasePermissionSet(){} + + public DatabasePermissionSet( bool connect, bool update ) + { + this.Connect = connect; + this.Update = update; + } + + public bool Connect = false; + public bool Update = false; + } + + // TypeName: Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo + // BaseType: Microsoft.SqlServer.Management.Smo.PermissionInfo + // Used by: + // xSQLServerDatabasePermission.Tests.ps1 + public class DatabasePermissionInfo + { + public DatabasePermissionInfo() + { + Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet = { new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet() }; + this.PermissionType = permissionSet; + } + + public DatabasePermissionInfo( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet ) + { + this.PermissionType = permissionSet; + } + + public Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] PermissionType; + public string PermissionState = "Grant"; + } + // TypeName: Microsoft.SqlServer.Management.Smo.Server // BaseType: Microsoft.SqlServer.Management.Smo.SqlSmoObject // Used by: @@ -388,8 +427,11 @@ public ServerRole( Object server, string name ) { // BaseType: Microsoft.SqlServer.Management.Smo.ScriptNameObjectBase // Used by: // MSFT_xSQLServerDatabase + // MSFT_xSQLServerDatabasePermission public class Database { + public string MockGranteeName; + public Database( Server server, string name ) { this.Name = name; } @@ -407,6 +449,44 @@ public void Create() public void Drop() { } + + public Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[] EnumDatabasePermissions( string granteeName ) + { + List listOfDatabasePermissionInfo = new List(); + + if( Globals.GenerateMockData ) { + Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet = { + new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet( true, false ), + new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet( false, true ) + }; + + listOfDatabasePermissionInfo.Add( new Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo( permissionSet ) ); + } else { + listOfDatabasePermissionInfo.Add( new Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo() ); + } + + Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[] permissionInfo = listOfDatabasePermissionInfo.ToArray(); + + return permissionInfo; + } + + public void Grant( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet permission, string granteeName ) + { + if( granteeName != this.MockGranteeName ) + { + string errorMessage = "Expected to get granteeName == '" + this.MockGranteeName + "'. But got '" + granteeName + "'"; + throw new System.ArgumentException(errorMessage, "granteeName"); + } + } + + public void Deny( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet permission, string granteeName ) + { + if( granteeName != this.MockGranteeName ) + { + string errorMessage = "Expected to get granteeName == '" + this.MockGranteeName + "'. But got '" + granteeName + "'"; + throw new System.ArgumentException(errorMessage, "granteeName"); + } + } } // TypeName: Microsoft.SqlServer.Management.Smo.User diff --git a/Tests/Unit/xSQLServerHelper.Tests.ps1 b/Tests/Unit/xSQLServerHelper.Tests.ps1 index fdcb908fa..875d75a4b 100644 --- a/Tests/Unit/xSQLServerHelper.Tests.ps1 +++ b/Tests/Unit/xSQLServerHelper.Tests.ps1 @@ -4,7 +4,7 @@ $script:moduleName = 'xSQLServerHelper' [String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) { & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) } @@ -12,6 +12,9 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force Import-Module (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent | Split-Path -Parent) -ChildPath 'xSQLServerHelper.psm1') -Scope Global -Force +# Loading mocked classes +Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + # Begin Testing InModuleScope $script:moduleName { Describe 'Testing Restart-SqlService' { @@ -202,6 +205,92 @@ InModuleScope $script:moduleName { } } + Describe "Testing Get-SqlDatabasePermission" { + $mockSqlServerObject = [pscustomobject]@{ + InstanceName = 'MSSQLSERVER' + ComputerNamePhysicalNetBIOS = 'SQL01' + } + + $mockSqlServerObject = $mockSqlServerObject | Add-Member -MemberType ScriptProperty -Name Databases -Value { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } | Add-Member -MemberType ScriptMethod -Name EnumDatabasePermissions -Value { + return @{ + 'CONTOSO\SqlAdmin' = @( 'Connect','Update' ) + } + } -PassThru -Force + } -PassThru -Force + + $mockSqlServerObject = $mockSqlServerObject | Add-Member -MemberType ScriptProperty -Name Logins -Value { + return @{ + 'CONTOSO\SqlAdmin' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'CONTOSO\SqlAdmin') -Property @{ LoginType = 'WindowsUser'} ) ) + } + } -PassThru -Force + + + Context 'When the specified database does not exist' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'UnknownDatabase' + PermissionState = 'Grant' + } + + It 'Should throw the correct error' { + { Get-SqlDatabasePermission @testParameters } | Should Throw "Database 'UnknownDatabase' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Context 'When the specified login does not exist' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\UnknownUser' + Database = 'AdventureWorks' + PermissionState = 'Grant' + } + + It 'Should throw the correct error' { + { Get-SqlDatabasePermission @testParameters } | Should Throw "Login 'CONTOSO\UnknownUser' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Context 'When the specified database and login exist and the system is not in desired state' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'AdventureWorks' + PermissionState = 'Grant' + } + + It 'Should not return any permissions' { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false + + $permission = Get-SqlDatabasePermission @testParameters + $permission | Should BeNullOrEmpty + } + + } + + Context 'When the specified database and login exist and the system is in desired state' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'AdventureWorks' + PermissionState = 'Grant' + } + + It 'Should return the correct permissions' { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true + + $permission = Get-SqlDatabasePermission @testParameters + $permission -contains 'Connect' | Should Be $true + $permission -contains 'Update' | Should Be $true + } + } + + Assert-VerifiableMocks + } + Describe "Testing Get-SqlDatabaseRecoveryModel" { $mockSqlServerObject = [pscustomobject]@{ InstanceName = 'MSSQLSERVER' @@ -282,4 +371,158 @@ InModuleScope $script:moduleName { Assert-VerifiableMocks } + + Describe "Testing Add-SqlDatabasePermission" { + $mockSqlServerObject = [PSCustomObject]@{ + InstanceName = 'MSSQLSERVER' + ComputerNamePhysicalNetBIOS = 'SQL01' + } + + $mockSqlServerObject = $mockSqlServerObject | Add-Member -MemberType ScriptProperty -Name Databases -Value { + return @{ + 'AdventureWorks' = @( + ( + New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') | + Add-Member -MemberType ScriptProperty -Name Users -Value { + return @{ + 'CONTOSO\SqlAdmin' = $true + 'CONTOSO\UnknownUser' = $false + } + } -PassThru -Force + ) + ) + } | Add-Member -MemberType ScriptMethod -Name EnumDatabasePermissions -Value { + return @{ + 'CONTOSO\SqlAdmin' = @( 'Connect','Update' ) + } + } -PassThru -Force + } -PassThru -Force + + $mockSqlServerObject = $mockSqlServerObject | Add-Member -MemberType ScriptProperty -Name Logins -Value { + return @{ + 'CONTOSO\SqlAdmin' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'CONTOSO\SqlAdmin') -Property @{ LoginType = 'WindowsUser'} ) ) + } + } -PassThru -Force + + Context 'When the specified database and login exist and the system is not in desired state' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should add permissions to the specified database' { + Add-SqlDatabasePermission @testParameters + } + } + + Context 'When the specified database does not exist' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'UnknownDatabase' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should throw the correct error' { + { Add-SqlDatabasePermission @testParameters } | Should Throw "Database 'UnknownDatabase' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Context 'When the specified login does not exist' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\UnknownUser' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should throw the correct error' { + { Add-SqlDatabasePermission @testParameters } | Should Throw "Login 'CONTOSO\UnknownUser' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Assert-VerifiableMocks + } + + Describe "Testing Remove-SqlDatabasePermission" { + $mockSqlServerObject = [PSCustomObject]@{ + InstanceName = 'MSSQLSERVER' + ComputerNamePhysicalNetBIOS = 'SQL01' + } + + $mockSqlServerObject = $mockSqlServerObject | Add-Member -MemberType ScriptProperty -Name Databases -Value { + return @{ + 'AdventureWorks' = @( + ( + New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') | + Add-Member -MemberType ScriptProperty -Name Users -Value { + return @{ + 'CONTOSO\SqlAdmin' = $true + 'CONTOSO\UnknownUser' = $false + } + } -PassThru -Force + ) + ) + } | Add-Member -MemberType ScriptMethod -Name EnumDatabasePermissions -Value { + return @{ + 'CONTOSO\SqlAdmin' = @( 'Connect','Update' ) + } + } -PassThru -Force + } -PassThru -Force + + $mockSqlServerObject = $mockSqlServerObject | Add-Member -MemberType ScriptProperty -Name Logins -Value { + return @{ + 'CONTOSO\SqlAdmin' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'CONTOSO\SqlAdmin') -Property @{ LoginType = 'WindowsUser'} ) ) + } + } -PassThru -Force + + Context 'When the specified database and login exist and the system is not in desired state' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should remove permissions to the specified database' { + Remove-SqlDatabasePermission @testParameters + } + } + + Context 'When the specified database does not exist' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'UnknownDatabase' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should throw the correct error' { + { Remove-SqlDatabasePermission @testParameters } | Should Throw "Database 'UnknownDatabase' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Context 'When the specified login does not exist' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\UnknownUser' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should throw the correct error' { + { Remove-SqlDatabasePermission @testParameters } | Should Throw "Login 'CONTOSO\UnknownUser' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Assert-VerifiableMocks + } } diff --git a/xSQLServerHelper.psm1 b/xSQLServerHelper.psm1 index 6f03050ec..ab05df141 100644 --- a/xSQLServerHelper.psm1 +++ b/xSQLServerHelper.psm1 @@ -1082,13 +1082,13 @@ function Confirm-SqlServerRoleMember <# .SYNOPSIS - This cmdlet is used to return the owner of a SQL database. + This cmdlet is used to return the owner of a SQL database - .PARAMETER SQL - This is an object of the SQL server that contains the result of Connect-SQL. + .PARAMETER Sql + This is an object of the SQL server that contains the result of Connect-SQL .PARAMETER Database - This is the SQL database that will be checking. + This is the SQL database that will be checking #> function Get-SqlDatabaseOwner { @@ -1097,7 +1097,7 @@ function Get-SqlDatabaseOwner ( [ValidateNotNull()] [System.Object] - $SQL, + $Sql, [ValidateNotNull()] [System.String] @@ -1105,7 +1105,7 @@ function Get-SqlDatabaseOwner ) Write-Verbose -Message 'Getting SQL Databases' - $sqlDatabase = $SQL.Databases + $sqlDatabase = $Sql.Databases if ($sqlDatabase) { if ($sqlDatabase[$Database]) @@ -1147,7 +1147,7 @@ function Set-SqlDatabaseOwner ( [ValidateNotNull()] [System.Object] - $SQL, + $Sql, [ValidateNotNull()] [System.String] @@ -1159,8 +1159,8 @@ function Set-SqlDatabaseOwner ) Write-Verbose -Message 'Getting SQL Databases' - $sqlDatabase = $SQL.Databases - $sqlLogins = $SQL.Logins + $sqlDatabase = $Sql.Databases + $sqlLogins = $Sql.Logins if ($sqlDatabase -and $sqlLogins) { @@ -1389,3 +1389,352 @@ function Set-SqlDatabaseRecoveryModel -ErrorCategory InvalidResult } } + +<# + .SYNOPSIS + This cmdlet is used to return the permission for a user in a database + + .PARAMETER SqlServerObject + This is the Server object returned by Connect-SQL + + .PARAMETER Name + This is the name of the user to get the current permissions for + + .PARAMETER Database + This is the name of the SQL database + + .PARAMETER PermissionState + If the permission should be granted or denied. Valid values are Grant or Deny +#> +function Get-SqlDatabasePermission +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Object] + $SqlServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Database, + + [Parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny')] + [ValidateNotNullOrEmpty()] + [System.String] + $PermissionState + ) + + Write-Verbose -Message 'Evaluating database and login.' + $sqlDatabase = $SqlServerObject.Databases[$Database] + $sqlLogin = $SqlServerObject.Logins[$Name] + $sqlInstanceName = $SqlServerObject.InstanceName + $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS + + # Initialize variable permission + [System.String[]] $permission = @() + + if ($sqlDatabase) + { + if ($sqlLogin) + { + Write-Verbose -Message "Getting permissions for user '$Name' in database '$Database'." + + $databasePermissionInfo = $sqlDatabase.EnumDatabasePermissions($Name) + $databasePermissionInfo = $databasePermissionInfo | Where-Object -FilterScript { + $_.PermissionState -eq $PermissionState + } + + foreach ($currentDatabasePermissionInfo in $databasePermissionInfo) + { + $permissionProperty = ($currentDatabasePermissionInfo.PermissionType | Get-Member -MemberType Property).Name + foreach ($currentPermissionProperty in $permissionProperty) + { + if ($currentDatabasePermissionInfo.PermissionType."$currentPermissionProperty") + { + $permission += $currentPermissionProperty + } + } + } + } + else + { + throw New-TerminatingError -ErrorType LoginNotFound ` + -FormatArgs @($Name,$sqlServer,$sqlInstanceName) ` + -ErrorCategory ObjectNotFound + } + } + else + { + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($Database,$sqlServer,$sqlInstanceName) ` + -ErrorCategory InvalidResult + } + + $permission +} + +<# + .SYNOPSIS + This cmdlet is used to grant or deny permissions for a user in a database + + .PARAMETER SqlServerObject + This is the Server object returned by Connect-SQL + + .PARAMETER Name + This is the name of the user to get the current permissions for + + .PARAMETER Database + This is the name of the SQL database + + .PARAMETER PermissionState + If the permission should be granted or denied. Valid values are Grant or Deny + + .PARAMETER Permissions + The permissions to be granted or denied for the user in the database. + Valid permissions can be found in the article SQL Server Permissions: + https://msdn.microsoft.com/en-us/library/ms191291.aspx#SQL Server Permissions +#> +function Add-SqlDatabasePermission +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Object] + $SqlServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Database, + + [Parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny')] + [ValidateNotNullOrEmpty()] + [System.String] + $PermissionState, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String[]] + $Permissions + ) + + Write-Verbose -Message 'Evaluating database and login.' + $sqlDatabase = $SqlServerObject.Databases[$Database] + $sqlLogin = $SqlServerObject.Logins[$Name] + $sqlInstanceName = $SqlServerObject.InstanceName + $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS + + if ($sqlDatabase) + { + if ($sqlLogin) + { + if (!$sqlDatabase.Users[$Name]) + { + try + { + Write-Verbose -Message ("Adding SQL login $Name as a user of database " + ` + "$Database on $sqlServer\$sqlInstanceName") + $sqlDatabaseUser = New-Object Microsoft.SqlServer.Management.Smo.User $sqlDatabase,$Name + $sqlDatabaseUser.Login = $Name + $sqlDatabaseUser.Create() + } + catch + { + Write-Verbose -Message ("Failed adding SQL login $Name as a user of " + ` + "database $Database on $sqlServer\$sqlInstanceName") + } + } + + if ($sqlDatabase.Users[$Name]) + { + try + { + Write-Verbose -Message ("$PermissionState the permissions '$Permissions' to the " + ` + "database '$Database' on the server $sqlServer$sqlInstanceName") + $permissionSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabasePermissionSet + + foreach ($permission in $permissions) + { + $permissionSet."$permission" = $true + } + + switch ($PermissionState) + { + 'Grant' + { + $sqlDatabase.Grant($permissionSet,$Name) + } + + 'Deny' + { + $sqlDatabase.Deny($permissionSet,$Name) + } + } + } + catch + { + Write-Verbose -Message ("Failed setting SQL login $Name to permissions $permissions " + ` + "on database $Database on $sqlServer\$sqlInstanceName") + } + } + } + else + { + throw New-TerminatingError -ErrorType LoginNotFound ` + -FormatArgs @($Name,$sqlServer,$sqlInstanceName) ` + -ErrorCategory ObjectNotFound + } + } + else + { + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($Database,$sqlServer,$sqlInstanceName) ` + -ErrorCategory InvalidResult + } +} + +<# + .SYNOPSIS + This cmdlet is used to remove (revoke) permissions for a user in a database + + .PARAMETER SqlServerObject + This is the Server object returned by Connect-SQL. + + .PARAMETER Name + This is the name of the user for which permissions will be removed (revoked) + + .PARAMETER Database + This is the name of the SQL database + + .PARAMETER PermissionState + f the permission that should be removed was granted or denied. Valid values are Grant or Deny + + .PARAMETER Permissions + The permissions to be remove (revoked) for the user in the database. + Valid permissions can be found in the article SQL Server Permissions: + https://msdn.microsoft.com/en-us/library/ms191291.aspx#SQL Server Permissions. +#> +function Remove-SqlDatabasePermission +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Object] + $SqlServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Database, + + [Parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny')] + [ValidateNotNullOrEmpty()] + [System.String] + $PermissionState, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String[]] + $Permissions + ) + + Write-Verbose -Message 'Evaluating database and login' + $sqlDatabase = $SqlServerObject.Databases[$Database] + $sqlLogin = $SqlServerObject.Logins[$Name] + $sqlInstanceName = $SqlServerObject.InstanceName + $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS + + if ($sqlDatabase) + { + if ($sqlLogin) + { + if (!$sqlDatabase.Users[$Name]) + { + try + { + Write-Verbose -Message ("Adding SQL login $Name as a user of database " + ` + "$Database on $sqlServer\$sqlInstanceName") + $sqlDatabaseUser = New-Object -TypeName Microsoft.SqlServer.Management.Smo.User ` + -ArgumentList $sqlDatabase,$Name + $sqlDatabaseUser.Login = $Name + $sqlDatabaseUser.Create() + } + catch + { + Write-Verbose -Message ("Failed adding SQL login $Name as a user of " + ` + "database $Database on $sqlServer\$sqlInstanceName") + } + } + + if ($sqlDatabase.Users[$Name]) + { + try + { + Write-Verbose -Message ("Revoking $PermissionState permissions '$Permissions' to the " + ` + "database '$Database' on the server $sqlServer$sqlInstanceName") + $permissionSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabasePermissionSet + + foreach ($permission in $permissions) + { + $permissionSet."$permission" = $false + } + + switch ($PermissionState) + { + 'Grant' + { + $sqlDatabase.Grant($permissionSet,$Name) + } + + 'Deny' + { + $sqlDatabase.Deny($permissionSet,$Name) + } + } + } + catch + { + Write-Verbose -Message ("Failed removing SQL login $Name to permissions $permissions " + ` + "on database $Database on $sqlServer\$sqlInstanceName") + } + } + } + else + { + throw New-TerminatingError -ErrorType LoginNotFound ` + -FormatArgs @($Name,$sqlServer,$sqlInstanceName) ` + -ErrorCategory ObjectNotFound + } + } + else + { + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($Database,$sqlServer,$sqlInstanceName) ` + -ErrorCategory InvalidResult + } +} From d2636b3e34a0bff5128c86db4024abfa6615db67 Mon Sep 17 00:00:00 2001 From: Brandon Date: Mon, 23 Jan 2017 14:39:51 -0500 Subject: [PATCH 27/31] xSQLServerSetup: Add support for clustered installations (#326) - Added support for clustered installations to xSQLServerSetup - Migrated relevant code from xSQLServerFailoverClusterSetup - Removed Get-WmiObject usage - Clustered storage mapping now supports asymmetric cluster storage - Added support for multi-subnet clusters - Added localized error messages for cluster object mapping - Updated README.md to reflect new parameters - Updated description for xSQLServerFailoverClusterSetup to indicate it is deprecated. --- CHANGELOG.md | 8 + .../MSFT_xSQLServerSetup.psm1 | 457 +++++++-- .../MSFT_xSQLServerSetup.schema.mof | 4 + README.md | 8 +- Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 | 904 +++++++++++++++--- en-US/xSQLServer.strings.psd1 | 5 + 6 files changed, 1175 insertions(+), 211 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec8ecf61a..b91d0fffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,14 @@ - Changes to xSQLServerDatabasePermission - BREAKING CHANGE: Renamed xSQLServerDatabasePermissions to xSQLServerDatabasePermission to align wíth naming convention. - BREAKING CHANGE: The mandatory parameters now include PermissionState, SQLServer, and SQLInstanceName. +- Added support for clustered installations to xSQLServerSetup + - Migrated relevant code from xSQLServerFailoverClusterSetup + - Removed Get-WmiObject usage + - Clustered storage mapping now supports asymmetric cluster storage + - Added support for multi-subnet clusters + - Added localized error messages for cluster object mapping + - Updated README.md to reflect new parameters +- Updated description for xSQLServerFailoverClusterSetup to indicate it is deprecated. ## 4.0.0.0 diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 index a64ce672c..eb96f9090 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 @@ -93,6 +93,9 @@ function Get-TargetResource $integrationServiceName = "MsDtsServer$($sqlVersion)0" $features = '' + $clusteredSqlGroupName = '' + $clusteredSqlHostname = '' + $clusteredSqlIPAddress = '' $services = Get-Service if ($services | Where-Object {$_.Name -eq $databaseServiceName}) @@ -149,6 +152,36 @@ function Get-TargetResource $sqlUserDatabaseDirectory = $databaseServer.DefaultFile $sqlUserDatabaseLogDirectory = $databaseServer.DefaultLog $sqlBackupDirectory = $databaseServer.BackupDirectory + + if ($databaseServer.IsClustered) + { + New-VerboseMessage -Message 'Clustered instance detected' + + $clusteredSqlInstance = Get-CimInstance -Namespace root/MSCluster -ClassName MSCluster_Resource -Filter "Type = 'SQL Server'" | + Where-Object { $_.PrivateProperties.InstanceName -eq $InstanceName } + + if (!$clusteredSqlInstance) + { + throw New-TerminatingError -ErrorType FailoverClusterResourceNotFound -FormatArgs $InstanceName -ErrorCategory 'ObjectNotFound' + } + + New-VerboseMessage -Message 'Clustered SQL Server resource located' + + $clusteredSqlGroup = $clusteredSqlInstance | Get-CimAssociatedInstance -ResultClassName MSCluster_ResourceGroup + $clusteredSqlNetworkName = $clusteredSqlGroup | Get-CimAssociatedInstance -ResultClassName MSCluster_Resource | + Where-Object { $_.Type -eq "Network Name" } + + $clusteredSqlIPAddress = ($clusteredSqlNetworkName | Get-CimAssociatedInstance -ResultClassName MSCluster_Resource | + Where-Object { $_.Type -eq "IP Address" }).PrivateProperties.Address + + # Extract the required values + $clusteredSqlGroupName = $clusteredSqlGroup.Name + $clusteredSqlHostname = $clusteredSqlNetworkName.PrivateProperties.DnsName + } + else + { + New-VerboseMessage -Message 'Clustered instance not detected' + } } if ($services | Where-Object {$_.Name -eq $fullTextServiceName}) @@ -310,6 +343,9 @@ function Get-TargetResource ASTempDir = $analysisTempDirectory ASConfigDir = $analysisConfigDirectory ISSvcAccountUsername = $integrationServiceAccountUsername + FailoverClusterGroupName = $clusteredSqlGroupName + FailoverClusterNetworkName = $clusteredSqlHostname + FailoverClusterIPAddress = $clusteredSqlIPAddress } } @@ -317,6 +353,10 @@ function Get-TargetResource .SYNOPSIS Installs the SQL Server features to the node. + .PARAMETER Action + The action to be performed. Default value is 'Install'. + Possible values are 'Install', 'InstallFailoverCluster', 'AddNode', 'PrepareFailoverCluster', and 'CompleteFailoverCluster' + .PARAMETER SourcePath The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. @@ -442,6 +482,15 @@ function Get-TargetResource .PARAMETER BrowserSvcStartupType Specifies the startup mode for SQL Server Browser service + + .PARAMETER FailoverClusterGroupName + The name of the resource group to create for the clustered SQL Server instance + + .PARAMETER FailoverClusterIPAddress + Array of IP Addresses to be assigned to the clustered SQL Server instance + + .PARAMETER FailoverClusterNetworkName + Host name to be assigned to the clustered SQL Server instance #> function Set-TargetResource { @@ -450,6 +499,10 @@ function Set-TargetResource [CmdletBinding()] param ( + [ValidateSet('Install','InstallFailoverCluster','AddNode','PrepareFailoverCluster','CompleteFailoverCluster')] + [System.String] + $Action = 'Install', + [System.String] $SourcePath, @@ -571,7 +624,16 @@ function Set-TargetResource [System.String] [ValidateSet('Automatic', 'Disabled', 'Manual')] - $BrowserSvcStartupType + $BrowserSvcStartupType, + + [System.String] + $FailoverClusterGroupName = "SQL Server ($InstanceName)", + + [System.String[]] + $FailoverClusterIPAddress, + + [System.String] + $FailoverClusterNetworkName ) $parameters = @{ @@ -713,8 +775,113 @@ function Set-TargetResource } } - # Create install arguments - $arguments = "/Quiet=`"True`" /IAcceptSQLServerLicenseTerms=`"True`" /Action=`"Install`"" + $setupArguments = @{} + + if ($Action -in @('PrepareFailoverCluster','CompleteFailoverCluster','InstallFailoverCluster','AddNode')) + { + # Set the group name for this clustered instance. + $setupArguments += @{ + FailoverClusterGroup = $FailoverClusterGroupName + + # This was brought over from the old module. Should be removed (breaking change). + SkipRules = 'Cluster_VerifyForErrors' + } + } + + # Perform disk mapping for specific cluster installation types + if ($Action -in @('CompleteFailoverCluster','InstallFailoverCluster')) + { + $failoverClusterDisks = @() + + # Get a required lising of drives based on user parameters + $requiredDrives = Get-Variable -Name 'SQL*Dir' -ValueOnly | Where-Object { -not [String]::IsNullOrEmpty($_) } | Split-Path -Qualifier | Sort-Object -Unique + + # Get the disk resources that are available (not assigned to a cluster role) + $availableStorage = Get-CimInstance -Namespace 'root/MSCluster' -ClassName 'MSCluster_ResourceGroup' -Filter "Name = 'Available Storage'" | + Get-CimAssociatedInstance -Association MSCluster_ResourceGroupToResource -ResultClassName MSCluster_Resource + + foreach ($diskResource in $availableStorage) + { + # Determine whether the current node is a possible owner of the disk resource + $possibleOwners = $diskResource | Get-CimAssociatedInstance -Association 'MSCluster_ResourceToPossibleOwner' -KeyOnly | Select-Object -ExpandProperty Name + + if ($possibleOwners -icontains $env:COMPUTERNAME) + { + # Determine whether this disk contains one of our required partitions + if ($requiredDrives -icontains ($diskResource | Get-CimAssociatedInstance -ResultClassName 'MSCluster_DiskPartition' | Select-Object -ExpandProperty Path)) + { + $failoverClusterDisks += $diskResource.Name + } + } + } + + # Ensure we have a unique listing of disks + $failoverClusterDisks = $failoverClusterDisks | Sort-Object -Unique + + # Ensure we mapped all required drives + $requiredDriveCount = $requiredDrives.Count + $mappedDriveCount = $failoverClusterDisks.Count + + if ($mappedDriveCount -ne $requiredDriveCount) + { + throw New-TerminatingError -ErrorType FailoverClusterDiskMappingError -FormatArgs ($failoverClusterDisks -join '; ') -ErrorCategory InvalidResult + } + + # Add the cluster disks as a setup argument + $setupArguments += @{ FailoverClusterDisks = ($failoverClusterDisks | Sort-Object) } + } + + # Determine network mapping for specific cluster installation types + if ($Action -in @('CompleteFailoverCluster','InstallFailoverCluster','AddNode')) + { + $clusterIPAddresses = @() + + # If no IP Address has been specified, use "DEFAULT" + if ($FailoverClusterIPAddress.Count -eq 0) + { + $clusterIPAddresses += "DEFAULT" + } + else + { + # Get the available client networks + $availableNetworks = @(Get-CimInstance -Namespace root/MSCluster -ClassName MSCluster_Network -Filter 'Role >= 2') + + # Add supplied IP Addresses that are valid for available cluster networks + foreach ($address in $FailoverClusterIPAddress) + { + foreach ($network in $availableNetworks) + { + # Determine whether the IP address is valid for this network + if (Test-IPAddress -IPAddress $address -NetworkID $network.Address -SubnetMask $network.AddressMask) + { + # Add the formatted string to our array + $clusterIPAddresses += "IPv4; $address; $($network.Name); $($network.AddressMask)" + } + } + } + } + + # Ensure we mapped all required networks + $suppliedNetworkCount = $FailoverClusterIPAddress.Count + $mappedNetworkCount = $clusterIPAddresses.Count + + # Determine whether we have mapping issues for the IP Address(es) + if ($mappedNetworkCount -lt $suppliedNetworkCount) + { + throw New-TerminatingError -ErrorType FailoverClusterIPAddressNotValid -ErrorCategory InvalidArgument + } + + # Add the networks to the installation arguments + $setupArguments += @{ FailoverClusterIPAddresses = $clusterIPAddresses } + } + + # Add standard install arguments + $setupArguments += @{ + Quiet = $true + IAcceptSQLServerLicenseTerms = $true + Action = $Action + } + $argumentVars = @( 'InstanceName', 'InstanceID', @@ -749,22 +916,33 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey('SQLSvcAccount')) { - $arguments = $arguments | Join-ServiceAccountInfo -UsernameArgumentName 'SQLSVCACCOUNT' -PasswordArgumentName 'SQLSVCPASSWORD' -User $SQLSvcAccount + $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $SQLSvcAccount -ServiceType 'SQL') } if($PSBoundParameters.ContainsKey('AgtSvcAccount')) { - $arguments = $arguments | Join-ServiceAccountInfo -UsernameArgumentName 'AGTSVCACCOUNT' -PasswordArgumentName 'AGTSVCPASSWORD' -User $AgtSvcAccount + $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $AgtSvcAccount -ServiceType 'AGT') + } + + $setupArguments += @{ SQLSysAdminAccounts = @($SetupCredential.UserName) } + if ($PSBoundParameters -icontains 'SQLSysAdminAccounts') + { + $setupArguments['SQLSysAdminAccounts'] += $SQLSysAdminAccounts + } + + if ($SecurityMode -eq 'SQL') + { + $setupArguments += @{ SAPwd = $SAPwd.GetNetworkCredential().Password } } - $arguments += ' /AGTSVCSTARTUPTYPE=Automatic' + $setupArguments += @{ AgtSvcStartupType = 'Automatic' } } if ($Features.Contains('FULLTEXT')) { if ($PSBoundParameters.ContainsKey('FTSvcAccount')) { - $arguments = $arguments | Join-ServiceAccountInfo -UsernameArgumentName 'FTSVCACCOUNT' -PasswordArgumentName 'FTSVCPASSWORD' -User $FTSvcAccount + $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $FTSvcAccount -ServiceType 'FT') } } @@ -772,7 +950,7 @@ function Set-TargetResource { if ($PSBoundParameters.ContainsKey('RSSvcAccount')) { - $arguments = $arguments | Join-ServiceAccountInfo -UsernameArgumentName 'RSSVCACCOUNT' -PasswordArgumentName 'RSSVCPASSWORD' -User $RSSvcAccount + $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $RSSvcAccount -ServiceType 'RS') } } @@ -789,7 +967,14 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey('ASSvcAccount')) { - $arguments = $arguments | Join-ServiceAccountInfo -UsernameArgumentName 'ASSVCACCOUNT' -PasswordArgumentName 'ASSVCPASSWORD' -User $ASSvcAccount + $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $ASSvcAccount -ServiceType 'AS') + } + + $setupArguments += @{ ASSysAdminAccounts = @($SetupCredential.UserName) } + + if($PSBoundParameters.ContainsKey("ASSysAdminAccounts")) + { + $setupArguments['ASSysAdminAccounts'] += $ASSysAdminAccounts } } @@ -797,45 +982,49 @@ function Set-TargetResource { if ($PSBoundParameters.ContainsKey('ISSvcAccount')) { - $arguments = $arguments | Join-ServiceAccountInfo -UsernameArgumentName 'ISSVCACCOUNT' -PasswordArgumentName 'ISSVCPASSWORD' -User $ISSvcAccount + $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $ISSvcAccount -ServiceType 'IS') } } - foreach ($argumentVar in $argumentVars) + # Automatically include any additional arguments + foreach ($argument in $argumentVars) { - if ((Get-Variable -Name $argumentVar).Value -ne '') - { - $arguments += " /$argumentVar=`"" + (Get-Variable -Name $argumentVar).Value + "`"" - } + $setupArguments += @{ $argument = (Get-Variable -Name $argument -ValueOnly) } } - if ($Features.Contains('SQLENGINE')) + # Build the argument string to be passed to setup + $arguments = '' + foreach ($currentSetupArgument in $setupArguments.GetEnumerator()) { - $arguments += " /SQLSysAdminAccounts=`"" + $SetupCredential.UserName + "`"" - if ($PSBoundParameters.ContainsKey('SQLSysAdminAccounts')) + if ($currentSetupArgument.Value -ne '') { - foreach ($adminAccount in $SQLSysAdminAccounts) + # Arrays are handled specially + if ($currentSetupArgument.Value -is [array]) { - $arguments += " `"$adminAccount`"" + # Sort and format the array + $setupArgumentValue = ($currentSetupArgument.Value | Sort-Object | ForEach-Object { '"{0}"' -f $_ }) -join ' ' } - } - - if ($SecurityMode -eq 'SQL') - { - $arguments += " /SAPwd=" + $SAPwd.GetNetworkCredential().Password - } - } - - if ($Features.Contains('AS')) - { - $arguments += " /ASSysAdminAccounts=`"" + $SetupCredential.UserName + "`"" - if($PSBoundParameters.ContainsKey("ASSysAdminAccounts")) - { - foreach($adminAccount in $ASSysAdminAccounts) + elseif ($currentSetupArgument.Value -is [Boolean]) + { + $setupArgumentValue = @{ $true = 'True'; $false = 'False' }[$currentSetupArgument.Value] + $setupArgumentValue = '"{0}"' -f $setupArgumentValue + } + else { - $arguments += " `"$adminAccount`"" + # Features are comma-separated, no quotes + if ($currentSetupArgument.Key -eq 'Features') + { + $setupArgumentValue = $currentSetupArgument.Value + } + else + { + $setupArgumentValue = '"{0}"' -f $currentSetupArgument.Value + } } + + $arguments += "/$($currentSetupArgument.Key.ToUpper())=$($setupArgumentValue) " } + } # Replace sensitive values for verbose output @@ -861,7 +1050,9 @@ function Set-TargetResource New-VerboseMessage -Message "Starting setup using arguments: $log" + $arguments = $arguments.Trim() $process = StartWin32Process -Path $pathToSetupExecutable -Arguments $arguments + New-VerboseMessage -Message $process WaitForWin32ProcessEnd -Path $pathToSetupExecutable -Arguments $arguments @@ -887,6 +1078,10 @@ function Set-TargetResource .SYNOPSIS Tests if the SQL Server features are installed on the node. + .PARAMETER Action + The action to be performed. Default value is 'Install'. + Possible values are 'Install', 'InstallFailoverCluster', 'AddNode', 'PrepareFailoverCluster', and 'CompleteFailoverCluster' + .PARAMETER SourcePath The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. @@ -1012,6 +1207,15 @@ function Set-TargetResource .PARAMETER BrowserSvcStartupType Specifies the startup mode for SQL Server Browser service + + .PARAMETER FailoverClusterGroupName + The name of the resource group to create for the clustered SQL Server instance + + .PARAMETER FailoverClusterIPAddress + Array of IP Addresses to be assigned to the clustered SQL Server instance + + .PARAMETER FailoverClusterNetworkName + Host name to be assigned to the clustered SQL Server instance #> function Test-TargetResource { @@ -1019,6 +1223,10 @@ function Test-TargetResource [OutputType([System.Boolean])] param ( + [ValidateSet('Install','InstallFailoverCluster','AddNode','PrepareFailoverCluster','CompleteFailoverCluster')] + [System.String] + $Action = 'Install', + [System.String] $SourcePath, @@ -1140,7 +1348,19 @@ function Test-TargetResource [System.String] [ValidateSet('Automatic', 'Disabled', 'Manual')] - $BrowserSvcStartupType + $BrowserSvcStartupType, + + [Parameter(ParameterSetName = 'ClusterInstall')] + [System.String] + $FailoverClusterGroupName = "SQL Server ($InstanceName)", + + [Parameter(ParameterSetName = 'ClusterInstall')] + [System.String[]] + $FailoverClusterIPAddress, + + [Parameter(ParameterSetName = 'ClusterInstall')] + [System.String] + $FailoverClusterNetworkName ) $parameters = @{ @@ -1151,7 +1371,7 @@ function Test-TargetResource } $getTargetResourceResult = Get-TargetResource @parameters - New-VerboseMessage -Message "Features found: '$($SQLData.Features)'" + New-VerboseMessage -Message "Features found: '$($getTargetResourceResult.Features)'" $result = $false if ($getTargetResourceResult.Features ) @@ -1170,6 +1390,22 @@ function Test-TargetResource } } } + + if ($PSCmdlet.ParameterSetName -eq 'ClusterInstall') + { + New-VerboseMessage -Message "Clustered install, checking parameters." + + $result = $true + + Get-Variable -Name FailoverCluster* | ForEach-Object { + $variableName = $_.Name + + if ($getTargetResourceResult.$variableName -ne $_.Value) { + New-VerboseMessage -Message "$variableName '$($_.Value)' is not in the desired state for this cluster." + $result = $false + } + } + } $result } @@ -1330,61 +1566,132 @@ function Get-TemporaryFolder <# .SYNOPSIS - Returns the argument string appeneded with the account information as is given in UserAlias and User parameters + Returns the decimal representation of an IP Addresses + + .PARAMETER IPAddress + The IP Address to be converted #> -function Join-ServiceAccountInfo +function ConvertTo-Decimal { - <# - Suppressing this rule because there are parameters that contain the text 'UserName' and 'Password' - but they are not actually used to pass any credentials. Instead the parameters are used to provide the - argument that should be evaluated for setup.exe. - #> - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '')] - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] + [CmdletBinding()] + [OutputType([System.UInt32])] param( - [Parameter(Mandatory, ValueFromPipeline=$true)] - [string] - $ArgumentString, + [Parameter(Mandatory = $true)] + [System.Net.IPAddress] + $IPAddress + ) + + $i = 3 + $DecimalIP = 0 + $IPAddress.GetAddressBytes() | ForEach-Object { + $DecimalIP += $_ * [Math]::Pow(256,$i) + $i-- + } + + return [UInt32]$DecimalIP +} - [Parameter(Mandatory)] - [PSCredential] - $User, +<# + .SYNOPSIS + Determines whether an IP Address is valid for a given network / subnet + + .PARAMETER IPAddress + IP Address to be checked + + .PARAMETER NetworkID + IP Address of the network identifier + + .PARAMETER SubnetMask + Subnet mask of the network to be checked +#> +function Test-IPAddress +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Net.IPAddress] + $IPAddress, - [Parameter(Mandatory)] - [string] - $UsernameArgumentName, + [Parameter(Mandatory = $true)] + [System.Net.IPAddress] + $NetworkID, - [Parameter(Mandatory)] - [string] - $PasswordArgumentName + [Parameter(Mandatory = $true)] + [System.Net.IPAddress] + $SubnetMask ) + + # Convert all values to decimal + $IPAddressDecimal = ConvertTo-Decimal -IPAddress $IPAddress + $NetworkDecimal = ConvertTo-Decimal -IPAddress $NetworkID + $SubnetDecimal = ConvertTo-Decimal -IPAddress $SubnetMask + + # Determine whether the IP Address is valid for this network / subnet + return (($IPAddressDecimal -band $SubnetDecimal) -eq ($NetworkDecimal -band $SubnetDecimal)) +} - process { +<# + .SYNOPSIS + Builds service account parameters for setup + + .PARAMETER ServiceAccount + Credential for the service account - <# - Regex to determine if given username is an NT Authority account or not. - Accepted inputs are optional ntauthority with or without space between nt and authority - then a predefined list of users system, networkservice and localservice - #> - if($User.UserName.ToUpper() -match '^(NT ?AUTHORITY\\)?(SYSTEM|LOCALSERVICE|NETWORKSERVICE)$') + .PARAMETER ServiceType + Type of service account +#> +function Get-ServiceAccountParameters +{ + [CmdletBinding()] + [OutputType([Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [PSCredential] + $ServiceAccount, + + [Parameter(Mandatory = $true)] + [ValidateSet('SQL','AGT','IS','RS','AS','FT')] + [String] + $ServiceType + ) + + $parameters = @{} + + switch -Regex ($ServiceAccount.UserName.ToUpper()) + { + '^(?:NT ?AUTHORITY\\)?(SYSTEM|LOCALSERVICE|LOCAL SERVICE|NETWORKSERVICE|NETWORK SERVICE)$' { - # Dealing with NT Authority user - $ArgumentString += (' /{0}="NT AUTHORITY\{1}"' -f $UsernameArgumentName, $matches[2]) + $parameters = @{ + "$($ServiceType)SVCACCOUNT" = "NT AUTHORITY\$($Matches[1])" + } } - elseif ($User.UserName -like '*$') + + '^(?:NT SERVICE\\)(.*)$' { - # Dealing with Managed Service Account - $ArgumentString += (' /{0}="{1}"' -f $UsernameArgumentName, $User.UserName) + $parameters = @{ + "$($ServiceType)SVCACCOUNT" = "NT SERVICE\$($Matches[1])" + } } - else + + '.*\$' { - # Dealing with local or domain user - $ArgumentString += (' /{0}="{1}"' -f $UsernameArgumentName, $User.UserName) - $ArgumentString += (' /{0}="{1}"' -f $PasswordArgumentName, $User.GetNetworkCredential().Password) + $parameters = @{ + "$($ServiceType)SVCACCOUNT" = $ServiceAccount.UserName + } } - return $ArgumentString + default + { + $parameters = @{ + "$($ServiceType)SVCACCOUNT" = $ServiceAccount.UserName + "$($ServiceType)SVCPASSWORD" = $ServiceAccount.GetNetworkCredential().Password + } + } } + + return $parameters } Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof index f4b028d81..5c178bfe9 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof @@ -1,6 +1,7 @@ [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerSetup")] class MSFT_xSQLServerSetup : OMI_BaseResource { + [Write, Description("The action to be performed. Default value is 'Install'."), ValueMap{"Install","InstallFailoverCluster","AddNode","PrepareFailoverCluster","CompleteFailoverCluster"}, Values{"Install","InstallFailoverCluster","AddNode","PrepareFailoverCluster","CompleteFailoverCluster"}] String Action; [Write, Description("The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path.")] String SourcePath; [Required, EmbeddedInstance("MSFT_Credential"), Description("Credential to be used to perform the installation.")] String SetupCredential; [Write, EmbeddedInstance("MSFT_Credential"), Description("Credentials used to access the path set in the parameter 'SourcePath'.")] String SourceCredential; @@ -47,4 +48,7 @@ class MSFT_xSQLServerSetup : OMI_BaseResource [Write, EmbeddedInstance("MSFT_Credential"), Description("Service account for Integration Services service.")] String ISSvcAccount; [Read, Description("Output username for the Integration Services service.")] String ISSvcAccountUsername; [Write, Description("Specifies the startup mode for SQL Server Browser service."), ValueMap{"Automatic", "Disabled", "Manual"}, Values{"Automatic", "Disabled", "Manual"}] String BrowserSvcStartupType; + [Write, Description("The name of the resource group to create for the clustered SQL Server instance.")] String FailoverClusterGroupName; + [Write, Description("Array of IP Addresses to be assigned to the clustered SQL Server instance.")] String FailoverClusterIPAddress[]; + [Write, Description("Host name to be assigend to the clustered SQL Server instance.")] String FailoverClusterNetworkName; }; diff --git a/README.md b/README.md index b6420dcad..3aba6f0bd 100644 --- a/README.md +++ b/README.md @@ -418,7 +418,7 @@ None. ### xSQLServerFailoverClusterSetup -No description. +**This resource is deprecated.** The functionality of this resource has been merged with [xSQLServerSetup](#xsqlserversetup). Please do not use this resource for new development efforts. #### Requirements @@ -758,6 +758,7 @@ Installs SQL Server on the target node. #### Parameters +* **[String] Action** _(Write)_: The action to be performed. Defaults to 'Install'. { _Install_ | InstallFailoverCluster | AddNode | PrepareFailoverCluster | CompleteFailoverCluster } * **[String] InstanceName** _(Key)_: SQL instance to be installed. * **[PSCredential] SetupCredential** _(Required)_: Credential to be used to perform the installation. * **[String] SourcePath** _(Write)_: The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. @@ -796,8 +797,11 @@ Installs SQL Server on the target node. * **[String] ASBackupDir** _(Write)_: Path for Analysis Services backup files. * **[String] ASTempDir** _(Write)_: Path for Analysis Services temp files. * **[String] ASConfigDir** _(Write)_: Path for Analysis Services config. -* **ISSvcAccount** _(Write)_: Service account for Integration Services service. +* **[PSCredential] ISSvcAccount** _(Write)_: Service account for Integration Services service. * **[String] BrowserSvcStartupType** _(Write)_: Specifies the startup mode for SQL Server Browser service. { Automatic | Disabled | 'Manual' } +* **[String] FailoverClusterGroupName** _(Write)_: The name of the resource group to create for the clustered SQL Server instance. Defaults to 'SQL Server (_InstanceName_)'. +* **[String[]]FailoverClusterIPAddress** _(Write)_: Array of IP Addresses to be assigned to the clustered SQL Server instance. IP addresses must be in [dotted-decimal notation](https://en.wikipedia.org/wiki/Dot-decimal_notation), for example ````10.0.0.100````. If no IP address is specified, uses 'DEFAULT' for this setup parameter. +* **[String] FailoverClusterNetworkName** _(Write)_: Host name to be assigned to the clustered SQL Server instance. #### Read-Only Properties from Get-TargetResource diff --git a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 index 49646a28b..f7891b512 100644 --- a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 @@ -76,6 +76,10 @@ try $mockDefaultInstance_IntegrationServiceName = $mockSqlIntegrationName $mockDefaultInstance_AnalysisServiceName = 'MSSQLServerOLAPService' + $mockDefaultInstance_FailoverClusterNetworkName = 'TestDefaultCluster' + $mockDefaultInstance_FailoverClusterIPAddress = '10.0.0.10' + $mockDefaultInstance_FailoverClusterGroupName = "SQL Server ($mockDefaultInstance_InstanceName)" + $mockNamedInstance_InstanceName = 'TEST' $mockNamedInstance_DatabaseServiceName = "$($mockSqlDatabaseEngineName)`$$($mockNamedInstance_InstanceName)" $mockNamedInstance_AgentServiceName = "$($mockSqlAgentName)`$$($mockNamedInstance_InstanceName)" @@ -84,13 +88,41 @@ try $mockNamedInstance_IntegrationServiceName = $mockSqlIntegrationName $mockNamedInstance_AnalysisServiceName = "$($mockSqlAnalysisName)`$$($mockNamedInstance_InstanceName)" - $mockmockSetupCredentialUserName = "COMPANY\sqladmin" + $mockNamedInstance_FailoverClusterNetworkName = 'TestDefaultCluster' + $mockNamedInstance_FailoverClusterIPAddress = '10.0.0.20' + $mockNamedInstance_FailoverClusterGroupName = "SQL Server ($mockNamedInstance_InstanceName)" + + $mockmockSetupCredentialUserName = "COMPANY\sqladmin" + $mockmockSetupCredentialPassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force $mockSetupCredential = New-Object System.Management.Automation.PSCredential( $mockmockSetupCredentialUserName, $mockmockSetupCredentialPassword ) $mockSqlServiceAccount = 'COMPANY\SqlAccount' $mockAgentServiceAccount = 'COMPANY\AgentAccount' + $mockClusterNodes = @($env:COMPUTERNAME,'SQL01','SQL02') + + $mockClusterDiskMap = @{ + UserData = 'K:' + UserLogs = 'L:' + TempDbData = 'M:' + TempDbLogs = 'N:' + SQLBackup = 'O:' + } + + $mockClusterSites = @( + @{ + Name = 'SiteA' + Address = '10.0.0.100' + Mask = '255.255.255.0' + }, + @{ + Name = 'SiteB' + Address = '10.0.10.100' + Mask = '255.255.255.0' + } + ) + #region Function mocks $mockGetSqlMajorVersion = { return $mockSqlMajorVersion @@ -431,6 +463,31 @@ try ) } + $mockConnectSQLCluster = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'LoginMode' -Value $mockSqlLoginMode -PassThru | + Add-Member -MemberType NoteProperty -Name 'Collation' -Value $mockSqlCollation -PassThru | + Add-Member -MemberType NoteProperty -Name 'InstallDataDirectory' -Value $mockSqlInstallPath -PassThru | + Add-Member -MemberType NoteProperty -Name 'BackupDirectory' -Value $mockSqlBackupPath -PassThru | + Add-Member -MemberType NoteProperty -Name 'SQLTempDBDir' -Value $mockSqlTempDatabasePath -PassThru | + Add-Member -MemberType NoteProperty -Name 'SQLTempDBLogDir' -Value $mockSqlTempDatabaseLogPath -PassThru | + Add-Member -MemberType NoteProperty -Name 'DefaultFile' -Value $mockSqlDefaultDatabaseFilePath -PassThru | + Add-Member -MemberType NoteProperty -Name 'DefaultLog' -Value $mockSqlDefaultDatabaseLogPath -PassThru | + Add-Member -MemberType NoteProperty -Name 'IsClustered' -Value $true -PassThru | + Add-Member ScriptProperty Logins { + return @( ( New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockSqlSystemAdministrator -PassThru | + Add-Member ScriptMethod ListMembers { + return @('sysadmin') + } -PassThru -Force + ) ) + } -PassThru -Force + ) + ) + } + $mockConnectSQLAnalysis = { return @( ( @@ -513,20 +570,169 @@ try return $mockSourcePathUNC } + $mockGetCimInstance_MSClusterResource = { + return @( + ( + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_Resource','root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Name' -Value "SQL Server ($mockCurrentInstanceName)" -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'Type' -Value 'SQL Server' -TypeName 'String' -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'PrivateProperties' -Value @{ InstanceName = $mockCurrentInstanceName } -PassThru -Force + ) + ) + } + + $mockGetCimInstance_MSClusterResourceGroup_AvailableStorage = { + return @( + ( + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_ResourceGroup', 'root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Name' -Value 'Available Storage' -PassThru -Force + ) + ) + } + + $mockGetCimInstance_MSClusterNetwork = { + return @( + ( + $mockClusterSites | ForEach-Object { + $network = $_ + + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_Network', 'root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Name' -Value "$($network.Name)_Prod" -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'Role' -Value 2 -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'Address' -Value $network.Address -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'AddressMask' -Value $network.Mask -PassThru -Force + } + ) + ) + } + + $mockGetCimAssociatedInstance_MSClusterResourceGroup_DefaultInstance = { + return @( + ( + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_ResourceGroup', 'root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_FailoverClusterGroupName -PassThru -Force + ) + ) + } + + $mockGetCimAssociatedInstance_MSClusterResource_DefaultInstance = { + return @( + ( + @('Network Name','IP Address') | ForEach-Object { + $resourceType = $_ + + $propertyValue = @{ + MemberType = 'NoteProperty' + Name = 'PrivateProperties' + Value = $null + } + + switch ($resourceType) + { + 'Network Name' + { + $propertyValue.Value = @{ DnsName = $mockDefaultInstance_FailoverClusterNetworkName } + } + + 'IP Address' + { + $propertyValue.Value = @{ Address = $mockDefaultInstance_FailoverClusterIPAddress } + } + } + + return New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_Resource', 'root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Type' -Value $resourceType -PassThru -Force | + Add-Member @propertyValue -PassThru -Force + } + ) + ) + } + + # Mock to return physical disks that are part of the "Available Storage" cluster role + $mockGetCimAssociatedInstance_MSCluster_ResourceGroupToResource = { + return @( + ( + $mockClusterDiskMap.Keys | ForEach-Object { + $diskName = $_ + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_Resource','root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $diskName -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'State' -Value 2 -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'Type' -Value 'Physical Disk' -PassThru -Force + } + ) + ) + } + + $mockGetCimAssociatedInstance_MSCluster_ResourceToPossibleOwner = { + return @( + ( + $mockClusterNodes | ForEach-Object { + $node = $_ + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_Node', 'root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $node -PassThru -Force + } + ) + ) + } + + $mockGetCimAssociatedInstance_MSCluster_DiskPartition = { + $clusterDiskName = $InputObject.Name + $clusterDiskPath = $mockClusterDiskMap.$clusterDiskName + + return @( + ( + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_DiskPartition','root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Path' -Value $clusterDiskPath -PassThru -Force + ) + ) + } + <# 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). #> - $mockStartWin32ProcessExpectedArgument = '' # Set dynamically during runtime + $mockStartWin32ProcessExpectedArgument = @{} + + $mockStartWin32ProcessExpectedArgumentClusterDefault = @{ + IAcceptSQLServerLicenseTerms = 'True' + Quiet = 'True' + InstanceName = 'MSSQLSERVER' + AGTSVCSTARTUPTYPE = 'Automatic' + Features = 'SQLENGINE' + SQLSysAdminAccounts = 'COMPANY\sqladmin' + FailoverClusterGroup = 'SQL Server (MSSQLSERVER)' + } + $mockStartWin32Process = { - if ( $Arguments -ne $mockStartWin32ProcessExpectedArgument ) + $argumentHashTable = @{} + + # Break the argument string into a hash table + ($Arguments -split ' ?/') | ForEach-Object { + if ($_ -imatch '(\w+)="?([^/]+)"?') + { + $key = $Matches[1] + $value = ($Matches[2] -replace '" "','; ') -replace '"','' + $null = $argumentHashTable.Add($key, $value) + } + } + + # Start by checking whether we have the same number of parameters + New-VerboseMessage 'Verifying argument count (expected vs actual)' + $mockStartWin32ProcessExpectedArgument.Keys.Count | Should BeExactly $argumentHashTable.Keys.Count + + foreach ($argumentKey in $mockStartWin32ProcessExpectedArgument.Keys) { - throw "Expected arguments was not the same as the arguments in the function call.`nExpected: '$mockStartWin32ProcessExpectedArgument' `n But was: '$Arguments'" + New-VerboseMessage "Testing Parameter [$argumentKey]" + $argumentPassed = $argumentHashTable.ContainsKey($argumentKey) + $argumentPassed | Should Be $true + + $argumentValue = $argumentHashTable.$argumentKey + $argumentValue | Should Be $mockStartWin32ProcessExpectedArgument.$argumentKey } - return 'Process started' + return 'Process Started' } #endregion Function mocks @@ -537,6 +743,19 @@ try Features = 'SQLEngine,Replication,FullText,Rs,Is,As' } + $mockDefaultClusterParameters = @{ + SetupCredential = $mockSetupCredential + + # Feature support is tested elsewhere, so just include the minimum + Features = 'SQLEngine' + + # Ensure we use "clustered" disks for our paths + SQLUserDBDir = 'K:\MSSQL\Data\' + SQLUserDBLogDir = 'L:\MSSQL\Logs' + SQLTempDbDir = 'M:\MSSQL\TempDb\Data\' + SQLTempDbLogDir = 'N:\MSSQL\TempDb\Logs' + } + Describe "xSQLServerSetup\Get-TargetResource" -Tag 'Get' { #region Setting up TestDrive:\ @@ -1496,6 +1715,90 @@ try $result.ISSvcAccountUsername | Should Be $mockSqlServiceAccount } } + + Context "When SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for a clustered default instance" { + + BeforeAll { + $testParams = $mockDefaultParameters.Clone() + $testParams.Remove('Features') + $testParams += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + } + + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + + Mock -CommandName Get-CimInstance -MockWith {} -Verifiable + + Mock -CommandName Get-CimAssociatedInstance -MockWith {} -Verifiable + + Mock -CommandName Get-ItemProperty -MockWith $mockGetItemProperty_Setup -Verifiable + + Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable + } + + It 'Should not attempt to collect cluster information for a standalone instance' { + + $currentState = Get-TargetResource @testParams + + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-CimAssociatedInstance -Exactly -Times 0 -Scope It + + $currentState.FailoverClusterGroupName | Should BeNullOrEmpty + $currentState.FailoverClusterNetworkName | Should BeNullOrEmpty + $currentState.FailoverClusterIPAddress | Should BeNullOrEmpty + } + } + + Context "When SQL Server version is $mockSqlMajorVersion and the system is in the desired state for a clustered default instance" { + + BeforeEach { + $testParams = $mockDefaultParameters.Clone() + $testParams.Remove('Features') + $testParams += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + } + + $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + + Mock -CommandName Connect-SQL -MockWith $mockConnectSQLCluster -Verifiable + + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterResource -Verifiable -ParameterFilter { + $Filter -eq "Type = 'SQL Server'" + } + + Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSClusterResourceGroup_DefaultInstance -Verifiable -ParameterFilter { $ResultClassName -eq 'MSCluster_ResourceGroup' } + + Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSClusterResource_DefaultInstance -Verifiable -ParameterFilter { $ResultClassName -eq 'MSCluster_Resource' } + + Mock -CommandName Get-ItemProperty -MockWith $mockGetItemProperty_Setup -Verifiable + + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should collect information for a clustered instance' { + $currentState = Get-TargetResource @testParams + + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 1 -Scope It -ParameterFilter { $Filter -eq "Type = 'SQL Server'" } + Assert-MockCalled -CommandName Get-CimAssociatedInstance -Exactly -Times 1 -Scope It -ParameterFilter { $ResultClassName -eq 'MSCluster_ResourceGroup' } + Assert-MockCalled -CommandName Get-CimAssociatedInstance -Exactly -Times 2 -Scope It -ParameterFilter { $ResultClassName -eq 'MSCluster_Resource' } + + $currentState.InstanceName | Should Be $testParams.InstanceName + } + + It 'Should return correct cluster information' { + $currentState = Get-TargetResource @testParams + + $currentState.FailoverClusterGroupName | Should Be $mockDefaultInstance_FailoverClusterGroupName + $currentState.FailoverClusterIPAddress | Should Be $mockDefaultInstance_FailoverClusterIPAddress + $currentSTate.FailoverClusterNetworkName | Should Be $mockDefaultInstance_FailoverClusterNetworkName + } + } } Assert-VerifiableMocks @@ -1566,7 +1869,7 @@ try $mockSqlDefaultDatabaseFilePath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" $mockSqlDefaultDatabaseLogPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" - Context "When the system is not in the desired state" { + Context 'When the system is not in the desired state' { BeforeEach { $testParameters = $mockDefaultParameters $testParameters += @{ @@ -1822,6 +2125,20 @@ try } -Exactly -Times 1 -Scope It #endregion Assert Get-CimInstance } + + It 'Should return that the desired state is absent when a clustered instance cannot be found' { + $testClusterParameters = $testParameters.Clone() + + $testClusterParameters += @{ + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } + + $result = Test-TargetResource @testClusterParameters + + $result | Should Be $false + } } Context "When the system is in the desired state" { @@ -1952,6 +2269,36 @@ try } -Exactly -Times 1 -Scope It #endregion Assert Get-CimInstance } + + It 'Should return that the desired state is present when the correct clustered instance was found' { + $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + + Mock -CommandName Connect-SQL -MockWith $mockConnectSQLCluster -Verifiable + + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterResource -Verifiable -ParameterFilter { + $Filter -eq "Type = 'SQL Server'" + } + + Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSClusterResourceGroup_DefaultInstance -Verifiable -ParameterFilter { $ResultClassName -eq 'MSCluster_ResourceGroup' } + + Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSClusterResource_DefaultInstance -Verifiable -ParameterFilter { $ResultClassName -eq 'MSCluster_Resource' } + + $testClusterParameters = $testParameters.Clone() + + $testClusterParameters += @{ + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } + + $result = Test-TargetResource @testClusterParameters + + $result | Should Be $true + + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 1 -Scope It -ParameterFilter { $Filter -eq "Type = 'SQL Server'" } + Assert-MockCalled -CommandName Get-CimAssociatedInstance -Exactly -Times 3 -Scope It + } } Assert-VerifiableMocks @@ -2090,15 +2437,16 @@ try } It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/AGTSVCSTARTUPTYPE=Automatic', - '/InstanceName="MSSQLSERVER"', - '/Features="SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS"', - '/SQLSysAdminAccounts="COMPANY\sqladmin"', - '/ASSysAdminAccounts="COMPANY\sqladmin"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + AGTSVCSTARTUPTYPE = 'Automatic' + InstanceName = 'MSSQLSERVER' + Features = 'SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS' + SQLSysAdminAccounts = 'COMPANY\sqladmin' + ASSysAdminAccounts = 'COMPANY\sqladmin' + } { Set-TargetResource @testParameters } | Should Not Throw @@ -2133,14 +2481,14 @@ try if( $mockSqlMajorVersion -eq 13 ) { It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = '' + $mockStartWin32ProcessExpectedArgument = @{} { 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 = '' + $mockStartWin32ProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } @@ -2148,12 +2496,13 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="MSSQLSERVER"', - '/Features="SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'MSSQLSERVER' + Features = 'SSMS' + } { Set-TargetResource @testParameters } | Should Not Throw @@ -2183,12 +2532,13 @@ try It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="MSSQLSERVER"', - '/Features="ADV_SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'MSSQLSERVER' + Features = 'ADV_SSMS' + } { Set-TargetResource @testParameters } | Should Not Throw @@ -2246,15 +2596,17 @@ try } It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/AGTSVCSTARTUPTYPE=Automatic', - '/InstanceName="MSSQLSERVER"', - '/Features="SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS"', - '/SQLSysAdminAccounts="COMPANY\sqladmin"', - '/ASSysAdminAccounts="COMPANY\sqladmin"' -join ' ' + + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + AgtSvcStartupType = 'Automatic' + InstanceName = 'MSSQLSERVER' + Features = 'SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS' + SQLSysAdminAccounts = 'COMPANY\sqladmin' + ASSysAdminAccounts = 'COMPANY\sqladmin' + } { Set-TargetResource @testParameters } | Should Not Throw @@ -2305,12 +2657,13 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="MSSQLSERVER"', - '/Features="SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'MSSQLSERVER' + Features = 'SSMS' + } { Set-TargetResource @testParameters } | Should Not Throw @@ -2341,12 +2694,13 @@ try It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="MSSQLSERVER"', - '/Features="ADV_SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'MSSQLSERVER' + Features = 'ADV_SSMS' + } { Set-TargetResource @testParameters } | Should Not Throw @@ -2401,16 +2755,17 @@ try } -MockWith $mockEmptyHashtable -Verifiable } - It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/AGTSVCSTARTUPTYPE=Automatic', - '/InstanceName="MSSQLSERVER"', - '/Features="SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS"', - '/SQLSysAdminAccounts="COMPANY\sqladmin"', - '/ASSysAdminAccounts="COMPANY\sqladmin"' -join ' ' + It 'Should set the system in the desired state when feature is SQLENGINE' { + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + AGTSVCSTARTUPTYPE = 'Automatic' + InstanceName = 'MSSQLSERVER' + Features = 'SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS' + SQLSysAdminAccounts = 'COMPANY\sqladmin' + ASSysAdminAccounts = 'COMPANY\sqladmin' + } { Set-TargetResource @testParameters } | Should Not Throw @@ -2446,14 +2801,14 @@ try if( $mockSqlMajorVersion -eq 13 ) { It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = '' + $mockStartWin32ProcessExpectedArgument = @{} { 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 = '' + $mockStartWin32ProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } @@ -2461,12 +2816,13 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="MSSQLSERVER"', - '/Features="SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'MSSQLSERVER' + Features = 'SSMS' + } { Set-TargetResource @testParameters } | Should Not Throw @@ -2497,12 +2853,13 @@ try It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="MSSQLSERVER"', - '/Features="ADV_SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'MSSQLSERVER' + Features = 'ADV_SSMS' + } { Set-TargetResource @testParameters } | Should Not Throw @@ -2562,15 +2919,16 @@ try } It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/AGTSVCSTARTUPTYPE=Automatic', - '/InstanceName="TEST"', - '/Features="SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS"', - '/SQLSysAdminAccounts="COMPANY\sqladmin"', - '/ASSysAdminAccounts="COMPANY\sqladmin"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + AGTSVCSTARTUPTYPE = 'Automatic' + InstanceName = 'TEST' + Features = 'SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS' + SQLSysAdminAccounts = 'COMPANY\sqladmin' + ASSysAdminAccounts = 'COMPANY\sqladmin' + } { Set-TargetResource @testParameters } | Should Not Throw @@ -2601,14 +2959,14 @@ try if( $mockSqlMajorVersion -eq 13 ) { It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { $testParameters.Features = $($testParameters.Features), 'SSMS' -join ',' - $mockStartWin32ProcessExpectedArgument = '' + $mockStartWin32ProcessExpectedArgument = @{} { 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 = '' + $mockStartWin32ProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } @@ -2616,12 +2974,13 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="TEST"', - '/Features="SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'TEST' + Features = 'SSMS' + } { Set-TargetResource @testParameters } | Should Not Throw @@ -2651,12 +3010,13 @@ try It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="TEST"', - '/Features="ADV_SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'TEST' + Features = 'ADV_SSMS' + } { Set-TargetResource @testParameters } | Should Not Throw @@ -2683,57 +3043,273 @@ try } } } - } - Assert-VerifiableMocks - } + Context "When SQL Server version is $mockSqlMajorVersion and the system is not in the desired state and the action is PrepareFailoverCluster" { + BeforeAll { + $testParameters = $mockDefaultParameters.Clone() + $testParameters.Remove('Features') + $testParameters.Remove('SourceCredential') + $testParameters.Remove('ASSysAdminAccounts') - Describe 'Join-ServiceAccountInfo' -Tag 'Helper' { - Context 'When called it should return a string with service account information appended to the original argument string' { + $testParameters += @{ + Features = 'SQLENGINE' + InstanceName = 'MSSQLSERVER' + SourcePath = $mockSourcePath + Action = 'PrepareFailoverCluster' + } - $Params = @{ UsernameArgumentname = 'SQLSVCACCOUNT'; PasswordArgumentName = 'SQLSVCPASSWORD'; ArgumentString = "C:\Install\SQLSource\setup.exe" } + Mock -CommandName NetUse -Verifiable + Mock -CommandName Copy-ItemWithRoboCopy -Verifiable + Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable + Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - $mockSetupDomainCredential = New-Object System.Management.Automation.PSCredential( 'Company\SQLServer', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) - $mockSetupMSACredential = New-Object System.Management.Automation.PSCredential( 'Company\SQLServer$', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) - $mockSetupSystemCredential = New-Object System.Management.Automation.PSCredential( 'SYSTEM', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) - $mockSetupLocalServiceCredential = New-Object System.Management.Automation.PSCredential( 'LOCALSERVICE', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) - $mockSetupNetworkServiceCredential = New-Object System.Management.Automation.PSCredential( 'NETWORKSERVICE', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) - $mockSetupNTSystemCredential = New-Object System.Management.Automation.PSCredential( 'NT AUTHORITY\SYSTEM', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) - $mockSetupNTLocalServiceCredential = New-Object System.Management.Automation.PSCredential( 'NT AUTHORITY\LOCALSERVICE', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) - $mockSetupNTNetworkServiceCredential = New-Object System.Management.Automation.PSCredential( 'NT AUTHORITY\NETWORKSERVICE', (ConvertTo-SecureString 'password' -AsPlainText -Force) ) + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - It 'Should return string with service account information and password appended Domain/Local Account' { - Join-ServiceAccountInfo @Params -User $mockSetupDomainCredential | Should BeExactly ('{0} /{1}="{2}" /{3}="{4}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupDomainCredential.UserName, $Params['PasswordArgumentname'], $mockSetupDomainCredential.GetNetworkCredential().Password) - } + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -MockWith $mockGetItemProperty_Setup -Verifiable - It 'Should return string service account information and no password appended for Managed Service Account' { - Join-ServiceAccountInfo @Params -User $mockSetupMSACredential | Should BeExactly ('{0} /{1}="{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupMSACredential.UserName) - } + Mock -CommandName StartWin32Process -MockWith $mockStartWin32Process -Verifiable - It 'Should return string service account information and no password appended for NT AUTHORITY\SYSTEM. Test without NT AUTHORITY prepended' { - Join-ServiceAccountInfo @Params -User $mockSetupSystemCredential | Should BeExactly ('{0} /{1}="NT AUTHORITY\{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupSystemCredential.UserName) - } + Mock -CommandName Get-CimInstance -MockWith {} -ParameterFilter { + ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_ResourceGroup') -and ($Filter -eq "Name = 'Available Storage'") + } -Verifiable - It 'Should return string service account information and no password appended for NT AUTHORITY\LOCALSERVICE. Test without NT AUTHORITY prepended' { - Join-ServiceAccountInfo @Params -User $mockSetupLocalServiceCredential | Should BeExactly ('{0} /{1}="NT AUTHORITY\{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupLocalServiceCredential.UserName) - } + Mock -CommandName Get-CimAssociatedInstance -MockWith {} -ParameterFilter { + ($Association -eq 'MSCluster_ResourceGroupToResource') -and ($ResultClassName -eq 'MSCluster_Resource') + } -Verfiable - It 'Should return string service account information and no password appended for NT AUTHORITY\NETWORKSERVICE. Test without NT AUTHORITY prepended' { - Join-ServiceAccountInfo @Params -User $mockSetupNetworkServiceCredential | Should BeExactly ('{0} /{1}="NT AUTHORITY\{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupNetworkServiceCredential.UserName) - } + Mock -CommandName Get-CimAssociatedInstance -MockWith {} -ParameterFilter { + $Association -eq 'MSCluster_ResourceToPossibleOwner' + } -Verifiable - It 'Should return string service account information and no password appended for NT AUTHORITY\SYSTEM. Test with NT AUTHORITY prepended' { - Join-ServiceAccountInfo @Params -User $mockSetupNTSystemCredential | Should BeExactly ('{0} /{1}="{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupNTSystemCredential.UserName) - } + Mock -CommandName Get-CimAssociatedInstance -MockWith {} -ParameterFilter { + $ResultClass -eq 'MSCluster_DiskPartition' + } -Verifiable + + Mock -CommandName Get-CimInstance -MockWith {} -ParameterFilter { + ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_Network') -and ($Filter -eq 'Role >= 2') + } -Verifiable + } + + It 'Should add the SkipRules parameter to the installation arguments' { + + $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() + $mockStartWin32ProcessExpectedArgument += @{ + Action = 'PrepareFailoverCluster' + SkipRules = 'Cluster_VerifyForErrors' + } + + { Set-TargetResource @testParameters } | Should Not throw + + 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 + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $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 Test-TargetResource -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_ResourceGroup') -and ($Filter -eq "Name = 'Available Storage'") + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-CimAssociatedInstance -ParameterFilter { + ($Association -eq 'MSCluster_ResourceGroupToResource') -and ($ResultClassName -eq 'MSCluster_Resource') + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-CimAssociatedInstance -ParameterFilter { + $Association -eq 'MSCluster_ResourceToPossibleOwner' + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-CimAssociatedInstance -ParameterFilter { + $ResultClass -eq 'MSCluster_DiskPartition' + } -Exactly -Times 0 -Scope It - It 'Should return string service account information and no password appended for NT AUTHORITY\LOCALSERVICE. Test with NT AUTHORITY prepended' { - Join-ServiceAccountInfo @Params -User $mockSetupNTLocalServiceCredential | Should BeExactly ('{0} /{1}="{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupNTLocalServiceCredential.UserName) + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_Network') -and ($Filter -eq 'Role >= 2') + } -Exactly -Times 0 -Scope It + } } - It 'Should return string service account information and no password appended for NT AUTHORITY\NETWORKSERVICE. Test with NT AUTHORITY prepended' { - Join-ServiceAccountInfo @Params -User $mockSetupNTNetworkServiceCredential | Should BeExactly ('{0} /{1}="{2}"' -f $Params['ArgumentString'], $Params['UsernameArgumentname'], $mockSetupNTNetworkServiceCredential.UserName) + Context "When SQL Server version is $mockSqlMajorVersion and the system is not in the desired state and the action is CompleteFailoverCluster." { + BeforeEach { + $testParameters = $mockDefaultClusterParameters.Clone() + + $testParameters += @{ + InstanceName = 'MSSQLSERVER' + SourcePath = $mockSourcePath + Action = 'CompleteFailoverCluster' + FailoverClusterGroupName = 'SQL Server (MSSQLSERVER)' + FailoverClusterNetworkName = 'TestCluster' + FailoverClusterIPAddress = '10.0.0.100' + } + } + + BeforeAll { + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterResourceGroup_AvailableStorage -ParameterFilter { + $Filter -eq "Name = 'Available Storage'" + } -Verifiable + + Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSCluster_ResourceGroupToResource -ParameterFilter { + ($Association -eq 'MSCluster_ResourceGroupToResource') -and ($ResultClassName -eq 'MSCluster_Resource') + } -Verfiable + + Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSCluster_ResourceToPossibleOwner -ParameterFilter { + $Association -eq 'MSCluster_ResourceToPossibleOwner' + } -Verifiable + + Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSCluster_DiskPartition -ParameterFilter { + $ResultClassName -eq 'MSCluster_DiskPartition' + } -Verifiable + + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterNetwork -ParameterFilter { + ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_Network') -and ($Filter -eq 'Role >= 2') + } -Verifiable + } + + It 'Should throw an error when one or more paths are not resolved to clustered storage' { + $badPathParameters = $testParameters.Clone() + + # Pass in a bad path + $badPathParameters.SQLUserDBDir = 'C:\MSSQL\' + + { Set-TargetResource @badPathParameters } | Should Throw 'Unable to map the specified paths to valid cluster storage. Drives mapped: TempDbData; TempDbLogs; UserLogs' + } + + It 'Should properly map paths to clustered disk resources' { + + $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() + $mockStartWin32ProcessExpectedArgument += @{ + Action = 'CompleteFailoverCluster' + FailoverClusterIPAddresses = 'IPV4; 10.0.0.100; SiteA_Prod; 255.255.255.0' + SQLUserDBDir = 'K:\MSSQL\Data' + SQLUserDBLogDir = 'L:\MSSQL\Logs' + SQLTempDBDir = 'M:\MSSQL\TempDb\Data' + SQLTempDBLogDir = 'N:\MSSQL\TempDb\Logs' + SkipRules = 'Cluster_VerifyForErrors' + + FailoverClusterDisks = 'TempDbData; TempDbLogs; UserData; UserLogs' + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should build a DEFAULT address string when no network is specified' { + $missingNetworkParams = $testParameters.Clone() + $missingNetworkParams.Remove('FailoverClusterIPAddress') + + $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() + $mockStartWin32ProcessExpectedArgument += @{ + Action = 'CompleteFailoverCluster' + FailoverClusterIPAddresses = 'DEFAULT' + + SQLUserDBDir = 'K:\MSSQL\Data' + SQLUserDBLogDir = 'L:\MSSQL\Logs' + SQLTempDBDir = 'M:\MSSQL\TempDb\Data' + SQLTempDBLogDir = 'N:\MSSQL\TempDb\Logs' + FailoverClusterDisks = 'TempDbData; TempDbLogs; UserData; UserLogs' + SkipRules = 'Cluster_VerifyForErrors' + } + + { Set-TargetResource @missingNetworkParams } | Should Not Throw + } + + It 'Should throw an error when an invalid IP Address is specified' { + $invalidAddressParameters = $testParameters.Clone() + + $invalidAddressParameters.Remove('FailoverClusterIPAddress') + $invalidAddressParameters += @{ + FailoverClusterIPAddress = '192.168.0.100' + } + + { Set-TargetResource @invalidAddressParameters } | Should Throw 'Unable to map the specified IP Address(es) to valid cluster networks.' + } + + It 'Should throw an error when an invalid IP Address is specified for a multi-subnet instance' { + $invalidAddressParameters = $testParameters.Clone() + + $invalidAddressParameters.Remove('FailoverClusterIPAddress') + $invalidAddressParameters += @{ + FailoverClusterIPAddress = @('10.0.0.100','192.168.0.100') + } + + { Set-TargetResource @invalidAddressParameters } | Should Throw 'Unable to map the specified IP Address(es) to valid cluster networks.' + } + + It 'Should build a valid IP address string for a single address' { + + $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() + $mockStartWin32ProcessExpectedArgument += @{ + FailoverClusterIPAddresses = 'IPv4; 10.0.0.100; SiteA_Prod; 255.255.255.0' + + SQLUserDBDir = 'K:\MSSQL\Data' + SQLUserDBLogDir = 'L:\MSSQL\Logs' + SQLTempDBDir = 'M:\MSSQL\TempDb\Data' + SQLTempDBLogDir = 'N:\MSSQL\TempDb\Logs' + FailoverClusterDisks = 'TempDbData; TempDbLogs; UserData; UserLogs' + SkipRules = 'Cluster_VerifyForErrors' + Action = 'CompleteFailoverCluster' + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should build a valid IP address string for a multi-subnet cluster' { + $multiSubnetParameters = $testParameters.Clone() + $multiSubnetParameters.Remove('FailoverClusterIPAddress') + $multiSubnetParameters += @{ + FailoverClusterIPAddress = ($mockClusterSites | ForEach-Object { $_.Address }) + } + + $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() + $mockStartWin32ProcessExpectedArgument += @{ + FailoverClusterIPAddresses = 'IPv4; 10.0.0.100; SiteA_Prod; 255.255.255.0; IPv4; 10.0.10.100; SiteB_Prod; 255.255.255.0' + + SQLUserDBDir = 'K:\MSSQL\Data' + SQLUserDBLogDir = 'L:\MSSQL\Logs' + SQLTempDBDir = 'M:\MSSQL\TempDb\Data' + SQLTempDBLogDir = 'N:\MSSQL\TempDb\Logs' + FailoverClusterDisks = 'TempDbData; TempDbLogs; UserData; UserLogs' + SkipRules = 'Cluster_VerifyForErrors' + Action = 'CompleteFailoverCluster' + } + + { Set-TargetResource @multiSubnetParameters } | Should Not Throw + } + + It 'Should pass proper parameters to setup' { + $mockStartWin32ProcessExpectedArgument = @{ + IAcceptSQLServerLicenseTerms = 'True' + SkipRules = 'Cluster_VerifyForErrors' + Quiet = 'True' + AgtSvcStartupType = 'Automatic' + SQLSysAdminAccounts = 'COMPANY\sqladmin' + + Action = 'CompleteFailoverCluster' + InstanceName = 'MSSQLSERVER' + Features = 'SQLEngine' + FailoverClusterDisks = 'TempDbData; TempDbLogs; UserData; UserLogs' + FailoverClusterIPAddresses = 'IPV4; 10.0.0.100; SiteA_Prod; 255.255.255.0' + FailoverClusterGroup = 'SQL Server (MSSQLSERVER)' + SQLUserDBDir = 'K:\MSSQL\Data' + SQLUserDBLogDir = 'L:\MSSQL\Logs' + SQLTempDBDir = 'M:\MSSQL\TempDb\Data' + SQLTempDBLogDir = 'N:\MSSQL\TempDb\Logs' + } + + { Set-TargetResource @testParameters } | Should Not Throw + } } + } + + Assert-VerifiableMocks } # Tests only the parts of the code that does not already get tested thru the other tests. @@ -2892,7 +3468,67 @@ try } } } - } + + Describe 'Get-ServiceAccountParameters' -Tag 'Helper' { + $serviceTypes = @('SQL','AGT','IS','RS','AS','FT') + + BeforeAll { + $mockServiceAccountPassword = ConvertTo-SecureString 'Password' -AsPlainText -Force + + $mockSystemServiceAccount = ( + New-Object System.Management.Automation.PSCredential 'NT AUTHORITY\SYSTEM', $mockServiceAccountPassword + ) + + $mockVirtualServiceAccount = ( + New-Object System.Management.Automation.PSCredential 'NT SERVICE\MSSQLSERVER', $mockServiceAccountPassword + ) + + $mockManagedServiceAccount = ( + New-Object System.Management.Automation.PSCredential 'COMPANY\ManagedAccount$', $mockServiceAccountPassword + ) + + $mockDomainServiceAccount = ( + New-Object System.Management.Automation.PSCredential 'COMPANY\sql.service', $mockServiceAccountPassword + ) + } + + $serviceTypes | ForEach-Object { + + $serviceType = $_ + + Context "When service type is $serviceType" { + + It "Should return the correct parameters when the account is a system account." { + $result = Get-ServiceAccountParameters -ServiceAccount $mockSystemServiceAccount -ServiceType $serviceType + + $result.$("$($serviceType)SVCACCOUNT") | Should BeExactly $mockSystemServiceAccount.UserName + $result.ContainsKey("$($serviceType)SVCPASSWORD") | Should Be $false + } + + It "Should return the correct parameters when the account is a virtual service account" { + $result = Get-ServiceAccountParameters -ServiceAccount $mockVirtualServiceAccount -ServiceType $serviceType + + $result.$("$($serviceType)SVCACCOUNT") | Should BeExactly $mockVirtualServiceAccount.UserName + $result.ContainsKey("$($serviceType)SVCPASSWORD") | Should Be $false + } + + It "Should return the correct parameters when the account is a managed service account" { + $result = Get-ServiceAccountParameters -ServiceAccount $mockManagedServiceAccount -ServiceType $serviceType + + $result.$("$($serviceType)SVCACCOUNT") | Should BeExactly $mockManagedServiceAccount.UserName + $result.ContainsKey("$($serviceType)SVCPASSWORD") | Should Be $false + } + + It "Should return the correct parameters when the account is a domain account" { + $result = Get-ServiceAccountParameters -ServiceAccount $mockDomainServiceAccount -ServiceType $serviceType + + $result.$("$($serviceType)SVCACCOUNT") | Should BeExactly $mockDomainServiceAccount.UserName + $result.$("$($serviceType)SVCPASSWORD") | Should BeExactly $mockDomainServiceAccount.GetNetworkCredential().Password + } + } + } + } + } } finally { diff --git a/en-US/xSQLServer.strings.psd1 b/en-US/xSQLServer.strings.psd1 index 9ba278863..95a86b1f6 100644 --- a/en-US/xSQLServer.strings.psd1 +++ b/en-US/xSQLServer.strings.psd1 @@ -52,4 +52,9 @@ PasswordChangeFailed = Setting the password failed for the SQL Login '{0}'. AlterLoginFailed = Altering the login '{0}' failed. CreateLoginFailed = Creating the login '{0}' failed. DropLoginFailed = Dropping the login '{0}' failed. + +# Clustered Setup +FailoverClusterDiskMappingError = Unable to map the specified paths to valid cluster storage. Drives mapped: {0} +FailoverClusterIPAddressNotValid = Unable to map the specified IP Address(es) to valid cluster networks. +FailoverClusterResourceNotFound = Could not locate a SQL Server cluster resource for instance {0}. '@ From 6be772020144d1769c6a54d47f68c3fc48ad2aa0 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 23 Jan 2017 21:09:56 +0100 Subject: [PATCH 28/31] BREAKING CHANGE: xSQLServerFirewall: Add tests and cleanup code (#323) - Changes to xSQLServerFirewall - BREAKING CHANGE: Removed parameter SourceFolder. - BREAKING CHANGE: Removed default value "$PSScriptRoot\..\..\" from parameter SourcePath. - Old code, that no longer filled any function, has been replaced. - Function `ResolvePath` has been replaced with `[Environment]::ExpandEnvironmentVariables($SourcePath)` so that environment variables still can be used in Source Path. - Adding new optional parameter SourceCredential that can be used to authenticate against SourcePath. - Solved PSSA rules errors in the code. - Get-TargetResource no longer return $true when no products was installed. - Changes to the unit test for resource - xSQLServerFirewall - 1-CreateInboundFirewallRules - 2-RemoveInboundFirewallRules - Added tests for resources - xSQLServerFirewall - xPDT helper module - Function GetxPDTVariable was removed since it no longer was used by any resources. - File xPDT.xml was removed since it was not used by any resources, and did not provide any value to the module. - Changes xSQLServerHelper moduled - Removed the globally defined `$VerbosePreference = 'Continue'` from xSQLServerHelper. - Fixed a typo in a variable name in the function New-ListenerADObject. - Now Restart-SqlService will correctly show the services it restarts. Also fixed PSSA warnings. --- CHANGELOG.md | 19 + .../MSFT_xSQLServerFirewall.psm1 | 770 ++++++++--- .../MSFT_xSQLServerFirewall.schema.mof | 2 +- .../1-CreateInboundFirewallRules.ps1 | 39 + .../2-RemoveInboundFirewallRules.ps1 | 39 + README.md | 36 +- Tests/Unit/MSFT_xSQLServerFirewall.Tests.ps1 | 1183 +++++++++++++++++ xPDT.psm1 | 124 +- xPDT.xml | 1091 --------------- xSQLServerHelper.psm1 | 103 +- 10 files changed, 1964 insertions(+), 1442 deletions(-) create mode 100644 Examples/Resources/xSQLServerFirewall/1-CreateInboundFirewallRules.ps1 create mode 100644 Examples/Resources/xSQLServerFirewall/2-RemoveInboundFirewallRules.ps1 create mode 100644 Tests/Unit/MSFT_xSQLServerFirewall.Tests.ps1 delete mode 100644 xPDT.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index b91d0fffb..c53adfbf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,14 @@ - Added to the description text for the parameter `Credential` describing how to authenticate using Windows Authentication. - Added examples to show how to authenticate using either SQL or Windows authentication. - A recent issue showed that there is a known problem running this resource using PowerShell 4.0. For more information, see [issue #273](https://github.com/PowerShell/xSQLServer/issues/273) +- Changes to xSQLServerFirewall + - BREAKING CHANGE: Removed parameter SourceFolder. + - BREAKING CHANGE: Removed default value "$PSScriptRoot\..\..\" from parameter SourcePath. + - Old code, that no longer filled any function, has been replaced. + - Function `ResolvePath` has been replaced with `[Environment]::ExpandEnvironmentVariables($SourcePath)` so that environment variables still can be used in Source Path. + - Adding new optional parameter SourceCredential that can be used to authenticate against SourcePath. + - Solved PSSA rules errors in the code. + - Get-TargetResource no longer return $true when no products was installed. - Changes to the unit test for resource - xSQLServerSetup - Added test coverage for helper function Copy-ItemWithRoboCopy @@ -70,9 +78,13 @@ - 1-GrantDatabasePermissions.ps1 - 2-RevokeDatabasePermissions.ps1 - 3-DenyDatabasePermissions.ps1 + - xSQLServerFirewall + - 1-CreateInboundFirewallRules + - 2-RemoveInboundFirewallRules - Added tests for resources - xSQLServerDatabaseRecoveryModel - xSQLServerDatabasePermissions + - xSQLServerFirewall - Changes to xSQLServerDatabaseRecoveryModel - BREAKING CHANGE: Renamed xSQLDatabaseRecoveryModel to xSQLServerDatabaseRecoveryModel to align wíth naming convention. - BREAKING CHANGE: The mandatory parameters now include SQLServer, and SQLInstanceName. @@ -87,6 +99,13 @@ - Added localized error messages for cluster object mapping - Updated README.md to reflect new parameters - Updated description for xSQLServerFailoverClusterSetup to indicate it is deprecated. +- xPDT helper module + - Function GetxPDTVariable was removed since it no longer was used by any resources. + - File xPDT.xml was removed since it was not used by any resources, and did not provide any value to the module. +- Changes xSQLServerHelper moduled + - Removed the globally defined `$VerbosePreference = 'Continue'` from xSQLServerHelper. + - Fixed a typo in a variable name in the function New-ListenerADObject. + - Now Restart-SqlService will correctly show the services it restarts. Also fixed PSSA warnings. ## 4.0.0.0 diff --git a/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.psm1 b/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.psm1 index 8ea6698c5..cca1c76fb 100644 --- a/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.psm1 +++ b/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.psm1 @@ -1,334 +1,623 @@ -$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Write-Debug -Message "CurrentPath: $currentPath" +$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') -# Load Common Code -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -Verbose:$false -ErrorAction Stop +<# + .SYNOPSIS + Returns the current state of the firewall rules. + .PARAMETER SourcePath + The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. + + .PARAMETER Features + One or more SQL feature to create default firewall rules for. Each feature should be seperated with a comma, i.e. 'SQLEngine,IS,RS'. + + .PARAMETER SourceCredential + Credentials used to access the path set in the parameter `SourcePath`. + + .PARAMETER InstanceName + Name of the instance to get firewall rules for. +#> function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( + [Parameter()] [System.String] - $SourcePath = "$PSScriptRoot\..\..\", - - [System.String] - $SourceFolder = "Source", + $SourcePath, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $Features, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] - $InstanceName + $InstanceName, + + [Parameter()] + [System.Management.Automation.PSCredential] + $SourceCredential ) $InstanceName = $InstanceName.ToUpper() - Import-Module $PSScriptRoot\..\..\xPDT.psm1 + $SourcePath = [Environment]::ExpandEnvironmentVariables($SourcePath) + + if ($SourceCredential) + { + $newSmbMappingParameters = @{ + RemotePath = $SourcePath + UserName = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" + Password = $($SourceCredential.GetNetworkCredential().Password) + } + + $null = New-SmbMapping @newSmbMappingParameters + } + + $pathToSetupExecutable = Join-Path -Path $SourcePath -ChildPath 'setup.exe' + + New-VerboseMessage -Message "Using path: $pathToSetupExecutable" + + $sqlVersion = Get-SqlMajorVersion -Path $pathToSetupExecutable - $Path = Join-Path -Path (Join-Path -Path $SourcePath -ChildPath $SourceFolder) -ChildPath "setup.exe" - $Path = ResolvePath $Path - $SQLVersion = GetSQLVersion -Path $Path - - if($InstanceName -eq "MSSQLSERVER") + if ($SourceCredential) { - $DBServiceName = "MSSQLSERVER" - $AgtServiceName = "SQLSERVERAGENT" - $FTServiceName = "MSSQLFDLauncher" - $RSServiceName = "ReportServer" - $ASServiceName = "MSSQLServerOLAPService" + Remove-SmbMapping -RemotePath $SourcePath -Force + } + + if($InstanceName -eq 'MSSQLSERVER') + { + $databaseServiceName = 'MSSQLSERVER' + $reportServiceName = 'ReportServer' + $analysisServiceName = 'MSSQLServerOLAPService' } else { - $DBServiceName = "MSSQL`$$InstanceName" - $AgtServiceName = "SQLAgent`$$InstanceName" - $FTServiceName = "MSSQLFDLauncher`$$InstanceName" - $RSServiceName = "ReportServer`$$InstanceName" - $ASServiceName = "MSOLAP`$$InstanceName" + $databaseServiceName = 'MSSQL${0}' -f $InstanceName + $reportServiceName = 'ReportServer${0}' -f $InstanceName + $analysisServiceName = 'MSOLAP${0}' -f $InstanceName } - $ISServiceName = "MsDtsServer" + $SQLVersion + "0" - - $Ensure = "Present" - $Services = Get-Service - $FeaturesInstalled = "" - foreach($Feature in $Features.Split(",")) + + $integrationServiceName = 'MsDtsServer{0}0' -f $sqlVersion + $browserServiceName = 'SQLBrowser' + + $ensure = 'Absent' + $featuresInstalled = '' + + $services = Get-Service + + foreach ($currentFeature in $Features.Split(',')) { - switch($Feature) + switch ($currentFeature) { - "SQLENGINE" + 'SQLENGINE' { - if($Services | Where-Object {$_.Name -eq $DBServiceName}) + if ($services | Where-Object {$_.Name -eq $databaseServiceName}) { - $FeaturesInstalled += "SQLENGINE," - if(Get-FirewallRule -DisplayName ("SQL Server Database Engine instance " + $InstanceName) -Application ((GetSQLPath -Feature "SQLENGINE" -InstanceName $InstanceName) + "\sqlservr.exe")) + $featuresInstalled += "$_," + + $pathToDatabaseEngineExecutable = Join-Path -Path (Get-SQLPath -Feature $_ -InstanceName $InstanceName) -ChildPath 'sqlservr.exe' + $databaseEngineFirewallRuleDisplayName = "SQL Server Database Engine instance $InstanceName" + + $databaseEngineFirewallRuleParameters = @{ + DisplayName = $databaseEngineFirewallRuleDisplayName + Program = $pathToDatabaseEngineExecutable + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (Test-IsFirewallRuleInDesiredState @databaseEngineFirewallRuleParameters) { - $DatabaseEngineFirewall = $true + $databaseEngineFirewall = $true + $ensure = 'Present' } else { - $DatabaseEngineFirewall = $false - $Ensure = "Absent" + $databaseEngineFirewall = $false } - if(Get-FirewallRule -DisplayName "SQL Server Browser" -Service "SQLBrowser") + + $browserFirewallRuleDisplayName = 'SQL Server Browser' + + $browserFirewallRuleParameters = @{ + DisplayName = $browserFirewallRuleDisplayName + Service = $browserServiceName + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (Test-IsFirewallRuleInDesiredState @browserFirewallRuleParameters) { - $BrowserFirewall = $true + $browserFirewall = $true + $ensure = 'Present' } else { - $BrowserFirewall = $false - $Ensure = "Absent" + $browserFirewall = $false } } } - "RS" + + 'RS' { - if($Services | Where-Object {$_.Name -eq $RSServiceName}) + if ($services | Where-Object {$_.Name -eq $reportServiceName}) { - $FeaturesInstalled += "RS," - if((Get-FirewallRule -DisplayName "SQL Server Reporting Services 80" -Port "TCP/80") -and (Get-FirewallRule -DisplayName "SQL Server Reporting Services 443" -Port "TCP/443")) + $featuresInstalled += "$_," + + $reportingServicesNoSslProtocol = 'TCP' + $reportingServicesNoSslLocalPort = '80' + $reportingServicesNoSslFirewallRuleDisplayName = 'SQL Server Reporting Services 80' + + $reportingServicesNoSslFirewallRuleParameters = @{ + DisplayName = $reportingServicesNoSslFirewallRuleDisplayName + Protocol = $reportingServicesNoSslProtocol + LocalPort = $reportingServicesNoSslLocalPort + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + $reportingServicesSslProtocol = 'TCP' + $reportingServicesSslLocalPort = '443' + $reportingServicesSslFirewallRuleDisplayName = 'SQL Server Reporting Services 443' + + $reportingServicesSslFirewallRuleParameters = @{ + DisplayName = $reportingServicesSslFirewallRuleDisplayName + Protocol = $reportingServicesSslProtocol + LocalPort = $reportingServicesSslLocalPort + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if ((Test-IsFirewallRuleInDesiredState @reportingServicesNoSslFirewallRuleParameters) ` + -and (Test-IsFirewallRuleInDesiredState @reportingServicesSslFirewallRuleParameters)) { - $ReportingServicesFirewall = $true + $reportingServicesFirewall = $true + $ensure = 'Present' } else { - $ReportingServicesFirewall = $false - $Ensure = "Absent" + $reportingServicesFirewall = $false } } } - "AS" + + 'AS' { - if($Services | Where-Object {$_.Name -eq $ASServiceName}) + if ($services | Where-Object {$_.Name -eq $analysisServiceName}) { - $FeaturesInstalled += "AS," - if(Get-FirewallRule -DisplayName "SQL Server Analysis Services instance $InstanceName" -Service $ASServiceName) + $featuresInstalled += "$_," + + $analysisServicesFirewallRuleDisplayName = "SQL Server Analysis Services instance $InstanceName" + + $analysisServicesFirewallRuleParameters = @{ + DisplayName = $analysisServicesFirewallRuleDisplayName + Service = $analysisServiceName + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (Test-IsFirewallRuleInDesiredState @analysisServicesFirewallRuleParameters) { - $AnalysisServicesFirewall = $true + $analysisServicesFirewall = $true + $ensure = 'Present' } else { - $AnalysisServicesFirewall = $false - $Ensure = "Absent" + $analysisServicesFirewall = $false } - if(Get-FirewallRule -DisplayName "SQL Server Browser" -Service "SQLBrowser") + + $browserFirewallRuleDisplayName = 'SQL Server Browser' + + $browserFirewallRuleParameters = @{ + DisplayName = $browserFirewallRuleDisplayName + Service = $browserServiceName + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (Test-IsFirewallRuleInDesiredState @browserFirewallRuleParameters) { - $BrowserFirewall = $true + $browserFirewall = $true + $ensure = 'Present' } else { - $BrowserFirewall = $false - $Ensure = "Absent" + $browserFirewall = $false } } } - "IS" + + 'IS' { - if($Services | Where-Object {$_.Name -eq $ISServiceName}) + if ($services | Where-Object {$_.Name -eq $integrationServiceName}) { - $FeaturesInstalled += "IS," - if((Get-FirewallRule -DisplayName "SQL Server Integration Services Application" -Application ((GetSQLPath -Feature "IS" -SQLVersion $SQLVersion) + "Binn\MsDtsSrvr.exe")) -and (Get-FirewallRule -DisplayName "SQL Server Integration Services Port" -Port "TCP/135")) + $featuresInstalled += "$_," + + $integrationServicesRuleApplicationDisplayName = 'SQL Server Integration Services Application' + $pathToIntegrationServicesExecutable = (Join-Path -Path (Join-Path -Path (Get-SQLPath -Feature 'IS' -SQLVersion $sqlVersion) -ChildPath 'Binn') -ChildPath 'MsDtsSrvr.exe') + + $integrationServicesFirewallRuleApplicationParameters = @{ + DisplayName = $integrationServicesRuleApplicationDisplayName + Program = $pathToIntegrationServicesExecutable + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + $integrationServicesProtocol = 'TCP' + $integrationServicesLocalPort = '135' + $integrationServicesFirewallRuleDisplayName = 'SQL Server Integration Services Port' + + $integrationServicesFirewallRulePortParameters = @{ + DisplayName = $integrationServicesFirewallRuleDisplayName + Protocol = $integrationServicesProtocol + LocalPort = $integrationServicesLocalPort + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if ((Test-IsFirewallRuleInDesiredState @integrationServicesFirewallRuleApplicationParameters) ` + -and (Test-IsFirewallRuleInDesiredState @integrationServicesFirewallRulePortParameters)) { - $IntegrationServicesFirewall = $true + $integrationServicesFirewall = $true + $ensure = 'Present' } else { - $IntegrationServicesFirewall = $false - $Ensure = "Absent" + $integrationServicesFirewall = $false } } } } } - $FeaturesInstalled = $FeaturesInstalled.Trim(",") - $returnValue = @{ - Ensure = $Ensure + $featuresInstalled = $featuresInstalled.Trim(',') + + return @{ + Ensure = $ensure SourcePath = $SourcePath - SourceFolder = $SourceFolder - Features = $FeaturesInstalled + Features = $featuresInstalled InstanceName = $InstanceName - DatabaseEngineFirewall = $DatabaseEngineFirewall - BrowserFirewall = $BrowserFirewall - ReportingServicesFirewall = $ReportingServicesFirewall - AnalysisServicesFirewall = $AnalysisServicesFirewall - IntegrationServicesFirewall = $IntegrationServicesFirewall + DatabaseEngineFirewall = $databaseEngineFirewall + BrowserFirewall = $browserFirewall + ReportingServicesFirewall = $reportingServicesFirewall + AnalysisServicesFirewall = $analysisServicesFirewall + IntegrationServicesFirewall = $integrationServicesFirewall } - - $returnValue } +<# + .SYNOPSIS + Creates, updates or remove the firewall rules. + + .PARAMETER Ensure + If the firewall rules should be present ('Present') or absent ('Absent'). The default value is 'Present'. + + .PARAMETER SourcePath + The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. + .PARAMETER Features + One or more SQL feature to create default firewall rules for. Each feature should be seperated with a comma, i.e. 'SQLEngine,IS,RS'. + + .PARAMETER SourceCredential + Credentials used to access the path set in the parameter `SourcePath`. + + .PARAMETER InstanceName + Name of the instance to get firewall rules for. +#> function Set-TargetResource { [CmdletBinding()] param ( - [ValidateSet("Present","Absent")] + [Parameter()] + [ValidateSet('Present','Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', + [Parameter()] [System.String] - $SourcePath = "$PSScriptRoot\..\..\", + $SourcePath, - [System.String] - $SourceFolder = "Source", - - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $Features, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] - $InstanceName + $InstanceName, + + [Parameter()] + [System.Management.Automation.PSCredential] + $SourceCredential ) $InstanceName = $InstanceName.ToUpper() - Import-Module $PSScriptRoot\..\..\xPDT.psm1 + $SourcePath = [Environment]::ExpandEnvironmentVariables($SourcePath) + + if ($SourceCredential) + { + $newSmbMappingParameters = @{ + RemotePath = $SourcePath + UserName = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" + Password = $($SourceCredential.GetNetworkCredential().Password) + } + + $null = New-SmbMapping @newSmbMappingParameters + } + + $path = Join-Path -Path $SourcePath -ChildPath 'setup.exe' + + New-VerboseMessage -Message "Using path: $path" - $Path = Join-Path -Path (Join-Path -Path $SourcePath -ChildPath $SourceFolder) -ChildPath "setup.exe" - $Path = ResolvePath $Path - $SQLVersion = GetSQLVersion -Path $Path + $sqlVersion = Get-SqlMajorVersion -Path $path + + if ($SourceCredential) + { + Remove-SmbMapping -RemotePath $SourcePath -Force + } - if($InstanceName -eq "MSSQLSERVER") + if ($InstanceName -eq 'MSSQLSERVER') { - $DBServiceName = "MSSQLSERVER" - $AgtServiceName = "SQLSERVERAGENT" - $FTServiceName = "MSSQLFDLauncher" - $RSServiceName = "ReportServer" - $ASServiceName = "MSSQLServerOLAPService" + $analysisServiceName = 'MSSQLServerOLAPService' } else { - $DBServiceName = "MSSQL`$$InstanceName" - $AgtServiceName = "SQLAgent`$$InstanceName" - $FTServiceName = "MSSQLFDLauncher`$$InstanceName" - $RSServiceName = "ReportServer`$$InstanceName" - $ASServiceName = "MSOLAP`$$InstanceName" + $analysisServiceName = 'MSOLAP${0}' -f $InstanceName } - $ISServiceName = "MsDtsServer" + $SQLVersion + "0" - - $SQLData = Get-TargetResource -SourcePath $SourcePath -SourceFolder $SourceFolder -Features $Features -InstanceName $InstanceName - foreach($Feature in $SQLData.Features.Split(",")) + $browserServiceName = 'SQLBrowser' + + $getTargetResourceResult = Get-TargetResource -SourcePath $SourcePath -Features $Features -InstanceName $InstanceName + + foreach ($currentFeature in $getTargetResourceResult.Features.Split(',')) { - switch($Feature) + switch ($currentFeature) { - "SQLENGINE" + 'SQLENGINE' { - if(!($SQLData.DatabaseEngineFirewall)){ - if(!(Get-FirewallRule -DisplayName ("SQL Server Database Engine instance " + $InstanceName) -Application ((GetSQLPath -Feature "SQLENGINE" -InstanceName $InstanceName) + "\sqlservr.exe"))) + if (-not ($getTargetResourceResult.DatabaseEngineFirewall)) + { + $pathToDatabaseEngineExecutable = Join-Path -Path (Get-SQLPath -Feature $_ -InstanceName $InstanceName) -ChildPath 'sqlservr.exe' + $databaseEngineFirewallRuleDisplayName = "SQL Server Database Engine instance $InstanceName" + + $databaseEngineFirewallRuleParameters = @{ + DisplayName = $databaseEngineFirewallRuleDisplayName + Program = $pathToDatabaseEngineExecutable + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (-not (Test-IsFirewallRuleInDesiredState @databaseEngineFirewallRuleParameters)) { - New-FirewallRule -DisplayName ("SQL Server Database Engine instance " + $InstanceName) -Application ((GetSQLPath -Feature "SQLENGINE" -InstanceName $InstanceName) + "\sqlservr.exe") + New-NetFirewallRule @databaseEngineFirewallRuleParameters } } - if(!($SQLData.BrowserFirewall)){ - if(!(Get-FirewallRule -DisplayName "SQL Server Browser" -Service "SQLBrowser")) + + if (-not ($getTargetResourceResult.BrowserFirewall)) + { + $browserFirewallRuleDisplayName = 'SQL Server Browser' + + $browserFirewallRuleParameters = @{ + DisplayName = $browserFirewallRuleDisplayName + Service = $browserServiceName + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (-not (Test-IsFirewallRuleInDesiredState @browserFirewallRuleParameters)) { - New-FirewallRule -DisplayName "SQL Server Browser" -Service "SQLBrowser" + New-NetFirewallRule @browserFirewallRuleParameters } } } - "RS" + + 'RS' { - if(!($SQLData.ReportingServicesFirewall)){ - if(!(Get-FirewallRule -DisplayName "SQL Server Reporting Services 80" -Port "TCP/80")) + if (-not ($getTargetResourceResult.ReportingServicesFirewall)) + { + $reportingServicesNoSslProtocol = 'TCP' + $reportingServicesNoSslLocalPort = '80' + $reportingServicesNoSslFirewallRuleDisplayName = 'SQL Server Reporting Services 80' + + $reportingServicesNoSslFirewallRuleParameters = @{ + DisplayName = $reportingServicesNoSslFirewallRuleDisplayName + Protocol = $reportingServicesNoSslProtocol + LocalPort = $reportingServicesNoSslLocalPort + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (-not (Test-IsFirewallRuleInDesiredState @reportingServicesNoSslFirewallRuleParameters)) { - New-FirewallRule -DisplayName "SQL Server Reporting Services 80" -Port "TCP/80" + New-NetFirewallRule @reportingServicesNoSslFirewallRuleParameters + } + + $reportingServicesSslProtocol = 'TCP' + $reportingServicesSslLocalPort = '443' + $reportingServicesSslFirewallRuleDisplayName = 'SQL Server Reporting Services 443' + + $reportingServicesSslFirewallRuleParameters = @{ + DisplayName = $reportingServicesSslFirewallRuleDisplayName + Protocol = $reportingServicesSslProtocol + LocalPort = $reportingServicesSslLocalPort + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' } - if(!(Get-FirewallRule -DisplayName "SQL Server Reporting Services 443" -Port "TCP/443")) + + if (-not (Test-IsFirewallRuleInDesiredState @reportingServicesSslFirewallRuleParameters)) { - New-FirewallRule -DisplayName "SQL Server Reporting Services 443" -Port "TCP/443" + New-NetFirewallRule @reportingServicesSslFirewallRuleParameters } } } - "AS" + + 'AS' { - if(!($SQLData.AnalysisServicesFirewall)){ - if(!(Get-FirewallRule -DisplayName "SQL Server Analysis Services instance $InstanceName" -Service $ASServiceName)) + if (-not ($getTargetResourceResult.AnalysisServicesFirewall)) + { + $analysisServicesFirewallRuleDisplayName = "SQL Server Analysis Services instance $InstanceName" + + $analysisServicesFirewallRuleParameters = @{ + DisplayName = $analysisServicesFirewallRuleDisplayName + Service = $analysisServiceName + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if(-not (Test-IsFirewallRuleInDesiredState @analysisServicesFirewallRuleParameters)) { - New-FirewallRule -DisplayName "SQL Server Analysis Services instance $InstanceName" -Service $ASServiceName + New-NetFirewallRule @analysisServicesFirewallRuleParameters } } - if(!($SQLData.BrowserFirewall)){ - if(!(Get-FirewallRule -DisplayName "SQL Server Browser" -Service "SQLBrowser")) + + if (-not ($getTargetResourceResult.BrowserFirewall)) + { + $browserFirewallRuleDisplayName = 'SQL Server Browser' + + $browserFirewallRuleParameters = @{ + DisplayName = $browserFirewallRuleDisplayName + Service = $browserServiceName + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (-not (Test-IsFirewallRuleInDesiredState @browserFirewallRuleParameters)) { - New-FirewallRule -DisplayName "SQL Server Browser" -Service "SQLBrowser" + New-NetFirewallRule @browserFirewallRuleParameters } } } - "IS" + + 'IS' { - if(!($SQLData.IntegrationServicesFirewall)){ - if(!(Get-FirewallRule -DisplayName "SQL Server Integration Services Application" -Application ((GetSQLPath -Feature "IS" -SQLVersion $SQLVersion) + "Binn\MsDtsSrvr.exe"))) + if (!($getTargetResourceResult.IntegrationServicesFirewall)) + { + $integrationServicesRuleApplicationDisplayName = 'SQL Server Integration Services Application' + $pathToIntegrationServicesExecutable = (Join-Path -Path (Join-Path -Path (Get-SQLPath -Feature 'IS' -SQLVersion $sqlVersion) -ChildPath 'Binn') -ChildPath 'MsDtsSrvr.exe') + + $integrationServicesFirewallRuleApplicationParameters = @{ + DisplayName = $integrationServicesRuleApplicationDisplayName + Program = $pathToIntegrationServicesExecutable + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (-not (Test-IsFirewallRuleInDesiredState @integrationServicesFirewallRuleApplicationParameters)) { - New-FirewallRule -DisplayName "SQL Server Integration Services Application" -Application ((GetSQLPath -Feature "IS" -SQLVersion $SQLVersion) + "Binn\MsDtsSrvr.exe") + New-NetFirewallRule @integrationServicesFirewallRuleApplicationParameters + } + + $integrationServicesProtocol = 'TCP' + $integrationServicesLocalPort = '135' + $integrationServicesFirewallRuleDisplayName = 'SQL Server Integration Services Port' + + $integrationServicesFirewallRulePortParameters = @{ + DisplayName = $integrationServicesFirewallRuleDisplayName + Protocol = $integrationServicesProtocol + LocalPort = $integrationServicesLocalPort + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' } - if(!(Get-FirewallRule -DisplayName "SQL Server Integration Services Port" -Port "TCP/135")) + + if (-not (Test-IsFirewallRuleInDesiredState @integrationServicesFirewallRulePortParameters)) { - New-FirewallRule -DisplayName "SQL Server Integration Services Port" -Port "TCP/135" + New-NetFirewallRule @integrationServicesFirewallRulePortParameters } } } } } - if(!(Test-TargetResource -SourcePath $SourcePath -SourceFolder $SourceFolder -Features $Features -InstanceName $InstanceName)) + if (-not (Test-TargetResource -SourcePath $SourcePath -Features $Features -InstanceName $InstanceName)) { throw New-TerminatingError -ErrorType TestFailedAfterSet -ErrorCategory InvalidResult } } +<# + .SYNOPSIS + Test if the firewall rules are in desired state. + + .PARAMETER Ensure + If the firewall rules should be present ('Present') or absent ('Absent'). The default value is 'Present'. + + .PARAMETER SourcePath + The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. + + .PARAMETER Features + One or more SQL feature to create default firewall rules for. Each feature should be seperated with a comma, i.e. 'SQLEngine,IS,RS'. + .PARAMETER SourceCredential + Credentials used to access the path set in the parameter `SourcePath`. + + .PARAMETER InstanceName + Name of the instance to get firewall rules for. +#> function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( - [ValidateSet("Present","Absent")] - [System.String] - $Ensure = "Present", - + [Parameter()] + [ValidateSet('Present','Absent')] [System.String] - $SourcePath = "$PSScriptRoot\..\..\", + $Ensure = 'Present', + [Parameter()] [System.String] - $SourceFolder = "Source", + $SourcePath, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $Features, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] - $InstanceName + $InstanceName, + + [Parameter()] + [System.Management.Automation.PSCredential] + $SourceCredential ) - $result = ((Get-TargetResource -SourcePath $SourcePath -SourceFolder $SourceFolder -Features $Features -InstanceName $InstanceName).Ensure -eq $Ensure) - - $result -} + $getTargetResourceResult = Get-TargetResource -SourcePath $SourcePath -Features $Features -InstanceName $InstanceName + return ($getTargetResourceResult.Ensure -eq $Ensure) +} -function GetSQLVersion -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory=$true)] - [String] - $Path - ) +<# + .SYNOPSIS + Get the path to SQL Server executables. - return (Get-Item -Path $Path).VersionInfo.ProductVersion.Split(".")[0] -} + .PARAMETER Feature + String containing the feature name for which to get the path. + .PARAMETER InstanceName + String containing the name of the instance for which to get the path. -function GetSQLPath + .PARAMETER SQLVersion + String containing the major version of the SQL server to get the path for. + This is used to evaluate the Integration Services version number. +#> +function Get-SQLPath { [CmdletBinding()] param @@ -336,42 +625,75 @@ function GetSQLPath [Parameter(Mandatory=$true)] [String] $Feature, - + + [Parameter()] [String] $InstanceName, + [Parameter()] [String] $SQLVersion ) - if(($Feature -eq "SQLENGINE") -or ($Feature -eq "AS")) + if (($Feature -eq 'SQLENGINE') -or ($Feature -eq 'AS')) { - switch($Feature) + switch ($Feature) { - "SQLENGINE" + 'SQLENGINE' { - $RegSubKey = "SQL" + $productinstanceId = 'SQL' } - "AS" + + 'AS' { - $RegSubKey = "OLAP" + $productinstanceId = 'OLAP' } } - $RegKey = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\$RegSubKey" -Name $InstanceName).$InstanceName - $Path = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$RegKey\setup" -Name "SQLBinRoot")."SQLBinRoot" + + $instanceId = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\$($productinstanceId)" -Name $InstanceName).$InstanceName + $path = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($instanceId)\setup" -Name 'SQLBinRoot').SQLBinRoot } - if($Feature -eq "IS") + if ($Feature -eq 'IS') { - $Path = (Get-ItemProperty -Path ("HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\" + $SQLVersion + "0\DTS\setup") -Name "SQLPath")."SQLPath" + $path = (Get-ItemProperty -Path ("HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($SQLVersion)0\DTS\setup") -Name 'SQLPath').SQLPath } - return $Path + return $path } +<# + .SYNOPSIS + Evaluates if the firewall rule is in desired state. + + .PARAMETER DisplayName + String containing the display name for the firewall rule. + + .PARAMETER Enabled + String containing either 'True' or 'False' meaning if the firewall rule should be active or not. + + .PARAMETER Profile + String containing one or more profiles to which the firewall rule is assigned. + + .PARAMETER Direction + String containing the direction of traffic for the the firewall rule. It can be either 'Inbound' or 'Outbound'. + + .PARAMETER Program + String containing the path to an executable. This paramater is optional. + + .PARAMETER Service + String containing the name of a service for the firewall rule. This parameter is optional. + + .PARAMETER Protocol + String containing the protocol for the local port parameter. This parameter is optional. -function Get-FirewallRule + .PARAMETER LocalPort + String containing the local port for the firewall rule. This parameter is optional, with the exception that if + the parameter Protocol is specified this parameter must also be specified. +#> +function Test-IsFirewallRuleInDesiredState { + [OutputType([System.Boolean])] [CmdletBinding()] param ( @@ -379,89 +701,101 @@ function Get-FirewallRule [String] $DisplayName, + [Parameter(Mandatory=$true)] + [ValidateSet('True','False')] + [String] + $Enabled, + + [Parameter(Mandatory=$true)] + [String[]] + [ValidateSet('Any','Domain','Private','Public','NotApplicable')] + $Profile, + + [Parameter(Mandatory=$true)] + [ValidateSet('Inbound','Outbound')] [String] - $Application, + $Direction, + [Parameter()] + [String] + $Program, + + [Parameter()] [String] $Service, + [Parameter()] + [ValidateSet('TCP','UDP','ICMPv4','ICMPv6')] + [String] + $Protocol, + + [Parameter()] [String] - $Port + $LocalPort ) - $Return = $false - if($FirewallRule = Get-NetFirewallRule -DisplayName $DisplayName -ErrorAction SilentlyContinue) + $isRuleInDesiredState = $false + + if ($firewallRule = Get-NetFirewallRule -DisplayName $DisplayName -ErrorAction SilentlyContinue) { - if(($FirewallRule.Enabled) -and ($FirewallRule.Profile -eq "Any") -and ($FirewallRule.Direction -eq "Inbound")) + if (($firewallRule.Enabled -eq $Enabled) -and ($firewallRule.Profile -eq $Profile) -and ($firewallRule.Direction -eq $Direction)) { - if($PSBoundParameters.ContainsKey("Application")) + if ($PSBoundParameters.ContainsKey('Program')) { - if($FirewallApplicationFilter = Get-NetFirewallApplicationFilter -AssociatedNetFirewallRule $FirewallRule -ErrorAction SilentlyContinue) + if ($firewallApplicationFilter = Get-NetFirewallApplicationFilter -AssociatedNetFirewallRule $firewallRule -ErrorAction SilentlyContinue) { - if($FirewallApplicationFilter.Program -eq $Application) + if ($firewallApplicationFilter.Program -eq $Program) { - $Return = $true + $isRuleInDesiredState = $true } } } - if($PSBoundParameters.ContainsKey("Service")) + + if ($PSBoundParameters.ContainsKey('Service')) { - if($FirewallServiceFilter = Get-NetFirewallServiceFilter -AssociatedNetFirewallRule $FirewallRule -ErrorAction SilentlyContinue) + if ($firewallServiceFilter = Get-NetFirewallServiceFilter -AssociatedNetFirewallRule $firewallRule -ErrorAction SilentlyContinue) { - if($FirewallServiceFilter.Service -eq $Service) + if ($firewallServiceFilter.Service -eq $Service) { - $Return = $true + $isRuleInDesiredState = $true } } } - if($PSBoundParameters.ContainsKey("Port")) + + if ($PSBoundParameters.ContainsKey('Protocol') -and $PSBoundParameters.ContainsKey('LocalPort')) { - if($FirewallPortFilter = Get-NetFirewallPortFilter -AssociatedNetFirewallRule $FirewallRule -ErrorAction SilentlyContinue) + if ($firewallPortFilter = Get-NetFirewallPortFilter -AssociatedNetFirewallRule $firewallRule -ErrorAction SilentlyContinue) { - if(($FirewallPortFilter.Protocol -eq $Port.Split("/")[0]) -and ($FirewallPortFilter.LocalPort -eq $Port.Split("/")[1])) + if ($firewallPortFilter.Protocol -eq $Protocol -and $firewallPortFilter.LocalPort -eq $LocalPort) { - $Return = $true + $isRuleInDesiredState = $true } } } } } - return $Return + + return $isRuleInDesiredState } +<# + .SYNOPSIS + Returns the SQL Server major version from the setup.exe executable provided in the Path parameter. -function New-FirewallRule + .PARAMETER Path + String containing the path to the SQL Server setup.exe executable. +#> +function Get-SqlMajorVersion { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [String] - $DisplayName, - - [String] - $Application, - - [String] - $Service, - - [String] - $Port + $path ) - if($PSBoundParameters.ContainsKey("Application")) - { - New-NetFirewallRule -DisplayName $DisplayName -Enabled True -Profile Any -Direction Inbound -Program $Application - } - if($PSBoundParameters.ContainsKey("Service")) - { - New-NetFirewallRule -DisplayName $DisplayName -Enabled True -Profile Any -Direction Inbound -Service $Service - } - if($PSBoundParameters.ContainsKey("Port")) - { - New-NetFirewallRule -DisplayName $DisplayName -Enabled True -Profile Any -Direction Inbound -Protocol $Port.Split("/")[0] -LocalPort $Port.Split("/")[1] - } + (Get-Item -Path $path).VersionInfo.ProductVersion.Split('.')[0] } - Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.schema.mof b/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.schema.mof index ccb122cb5..8879b0459 100644 --- a/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.schema.mof +++ b/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.schema.mof @@ -3,7 +3,6 @@ class MSFT_xSQLServerFirewall : OMI_BaseResource { [Write, Description("An enumerated value that describes if the SQL firewall rules are is expected to be enabled on the machine.\nPresent {default} \nAbsent \n"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; [Write, Description("UNC path to the root of the source files for installation.")] String SourcePath; - [Write, Description("Folder within the source path containing the source files for installation.")] String SourceFolder; [Key, Description("SQL features to enable firewall rules for.")] String Features; [Key, Description("SQL instance to enable firewall rules for.")] String InstanceName; [Read, Description("Is the firewall rule for the Database Engine enabled?")] boolean DatabaseEngineFirewall; @@ -11,4 +10,5 @@ class MSFT_xSQLServerFirewall : OMI_BaseResource [Read, Description("Is the firewall rule for Reporting Services enabled?")] boolean ReportingServicesFirewall; [Read, Description("Is the firewall rule for Analysis Services enabled?")] boolean AnalysisServicesFirewall; [Read, Description("Is the firewall rule for the Integration Services enabled?")] boolean IntegrationServicesFirewall; + [Write, EmbeddedInstance("MSFT_Credential"), Description("Credentials used to access the path set in the parameter 'SourcePath'.")] String SourceCredential; }; diff --git a/Examples/Resources/xSQLServerFirewall/1-CreateInboundFirewallRules.ps1 b/Examples/Resources/xSQLServerFirewall/1-CreateInboundFirewallRules.ps1 new file mode 100644 index 000000000..8834baee3 --- /dev/null +++ b/Examples/Resources/xSQLServerFirewall/1-CreateInboundFirewallRules.ps1 @@ -0,0 +1,39 @@ +<# +.EXAMPLE + This example shows how to create the default rules for the supported features. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerFirewall Create_FirewallRules_For_SQL2012 + { + Ensure = 'Present' + Features = 'SQLENGINE,AS,RS,IS' + InstanceName = 'SQL2012' + SourcePath = '\\files.company.local\images\SQL2012' + + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerFirewall Create_FirewallRules_For_SQL2016 + { + Ensure = 'Present' + Features = 'SQLENGINE' + InstanceName = 'SQL2016' + SourcePath = '\\files.company.local\images\SQL2016' + + SourceCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerFirewall/2-RemoveInboundFirewallRules.ps1 b/Examples/Resources/xSQLServerFirewall/2-RemoveInboundFirewallRules.ps1 new file mode 100644 index 000000000..b06f60d36 --- /dev/null +++ b/Examples/Resources/xSQLServerFirewall/2-RemoveInboundFirewallRules.ps1 @@ -0,0 +1,39 @@ +<# +.EXAMPLE + This example shows how to remove the default rules for the supported features. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerFirewall Remove_FirewallRules_For_SQL2012 + { + Ensure = 'Absent' + Features = 'SQLENGINE,AS,RS,IS' + InstanceName = 'SQL2012' + SourcePath = '\\files.company.local\images\SQL2012' + + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerFirewall Remove_FirewallRules_For_SQL2016 + { + Ensure = 'Absent' + Features = 'SQLENGINE' + InstanceName = 'SQL2016' + SourcePath = '\\files.company.local\images\SQL2016' + + SourceCredential = $SysAdminAccount + } + } +} diff --git a/README.md b/README.md index 3aba6f0bd..020c1faea 100644 --- a/README.md +++ b/README.md @@ -484,7 +484,36 @@ None. ### xSQLServerFirewall -No description. +This will set default firewall rules for the supported features. Currently the features supported are Database Engine, +Analysis Services, SQL Browser, SQL Reporting Services and Integration Services. + +#### Firewall rules + +##### Default rules for default instance + +| Feature | Component | Enable Firewall Rule | Firewall Name | +| --- | --- | --- | --- | +| SQLENGINE | Database Engine | Application: sqlservr.exe | SQL Server Database Engine instance MSSQLSERVER | +| SQLENGINE | Database Engine | Service: SQLBrowser | SQL Server Browser | +| AS | Analysis Services | Service: MSSQLServerOLAPService | SQL Server Analysis Services instance MSSQLSERVER | +| AS | Analysis Services | Service: SQLBrowser | SQL Server Browser | +| RS | Reporting Services | Port: tcp/80 | SQL Server Reporting Services 80 | +| RS | Reporting Services | Port: tcp/443 | SQL Server Reporting Services 443 | +| IS | Integration Services | Application: MsDtsSrvr.exe | SQL Server Integration Services Application | +| IS | Integration Services | Port: tcp/135 | SQL Server Integration Services Port | + +##### Default rules for named instance + +| Feature | Component | Enable Firewall Rule | Firewall Name | +| --- | --- | --- | --- | +| SQLENGINE | Database Engine | Application: sqlservr.exe | SQL Server Database Engine instance \ | +| SQLENGINE | Database Engine | Service: SQLBrowser | SQL Server Browser | +| AS | Analysis Services | Service: MSOLAP$INSTANCE | | SQL Server Analysis Services instance \ | +| AS | Analysis Services | Service: SQLBrowser | SQL Server Browser | +| RS | Reporting Services | Port: tcp/80 | SQL Server Reporting Services 80 | +| RS | Reporting Services | Port: tcp/443 | SQL Server Reporting Services 443 | +| IS | Integration Services | Application: MsDtsSrvr.exe | SQL Server Integration Services Application | +| IS | Integration Services | Port: tcp/135 | SQL Server Integration Services Port | #### Requirements @@ -496,7 +525,7 @@ No description. * **[String] InstanceName** _(Key)_: SQL instance to enable firewall rules for. * **[String] Ensure** _(Write)_: Ensures that SQL firewall rules are **Present** or **Absent** on the machine. { *Present* | Absent }. * **[String] SourcePath** _(Write)_: UNC path to the root of the source files for installation. -* **[String] SourceFolder** _(Write)_: Folder within the source path containing the source files for installation. +* **[String] SourceCredential** _(Write)_: Credentials used to access the path set in the parameter 'SourcePath'. This parmeter is optional either if built-in parameter 'PsDscRunAsCredential' is used, or if the source path can be access using the SYSTEM account. #### Read-Only Properties from Get-TargetResource @@ -508,7 +537,8 @@ No description. #### Examples -None. +* [Create inbound firewall rules](/Examples/Resources/xSQLServerFirewall/1-CreateInboundFirewallRules.ps1) +* [Remove inbound firewall rules](/Examples/Resources/xSQLServerFirewall/2-RemoveInboundFirewallRules.ps1) ### xSQLServerLogin diff --git a/Tests/Unit/MSFT_xSQLServerFirewall.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerFirewall.Tests.ps1 new file mode 100644 index 000000000..338311c62 --- /dev/null +++ b/Tests/Unit/MSFT_xSQLServerFirewall.Tests.ps1 @@ -0,0 +1,1183 @@ +# Suppressing this rule because PlainText is required for one of the functions used in this test +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerFirewall' + +#region HEADER + +# Unit Test Template Version: 1.2.0 +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +#endregion HEADER + +function Invoke-TestSetup { +} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope $script:DSCResourceName { + <# + Testing two major versions to verify Integration Services differences (i.e service name). + No point in testing each supported SQL Server version, since there are no difference + between the other major versions. + #> + $testProductVersion = @( + 11, # SQL Server 2012 + 10 # SQL Server 2008 and 2008 R2 + ) + + $mockSqlDatabaseEngineName = 'MSSQL' + $mockSqlAgentName = 'SQLAgent' + $mockSqlFullTextName = 'MSSQLFDLauncher' + $mockSqlReportingName = 'ReportServer' + $mockSqlIntegrationName = 'MsDtsServer{0}0' # {0} will be replaced by SQL major version in runtime + $mockSqlAnalysisName = 'MSOLAP' + + $mockSqlDatabaseEngineInstanceIdName = $mockSqlDatabaseEngineName + $mockSqlAnalysisServicesInstanceIdName = 'MSAS' + + $mockSetupExecutableName = 'setup.exe' + $mockDatabaseEngineExecutableName = 'sqlservr.exe' + $mockIntegrationServicesExecutableName = 'MsDtsSrvr.exe' + + $mockFirewallRulePort_ReportingServicesNoSslProtocol = 'tcp' + $mockFirewallRulePort_ReportingServicesNoSslLocalPort = 80 + $mockFirewallRulePort_ReportingServicesSslProtocol = 'tcp' + $mockFirewallRulePort_ReportingServicesSslLocalPort = 443 + $mockFirewallRulePort_IntegrationServicesProtocol = 'tcp' + $mockFirewallRulePort_IntegrationServicesLocalPort = 135 + + $mockDefaultInstance_InstanceName = 'MSSQLSERVER' + + $mockSQLBrowserServiceName = 'SQLBrowser' + + $mockDefaultInstance_InstanceName = 'MSSQLSERVER' + $mockDefaultInstance_DatabaseServiceName = $mockDefaultInstance_InstanceName + $mockDefaultInstance_AgentServiceName = 'SQLSERVERAGENT' + $mockDefaultInstance_FullTextServiceName = $mockSqlFullTextName + $mockDefaultInstance_ReportingServiceName = $mockSqlReportingName + $mockDefaultInstance_IntegrationServiceName = $mockSqlIntegrationName + $mockDefaultInstance_AnalysisServiceName = 'MSSQLServerOLAPService' + + $mockNamedInstance_InstanceName = 'TEST' + $mockNamedInstance_DatabaseServiceName = "$($mockSqlDatabaseEngineName)`$$($mockNamedInstance_InstanceName)" + $mockNamedInstance_AgentServiceName = "$($mockSqlAgentName)`$$($mockNamedInstance_InstanceName)" + $mockNamedInstance_FullTextServiceName = "$($mockSqlFullTextName)`$$($mockNamedInstance_InstanceName)" + $mockNamedInstance_ReportingServiceName = "$($mockSqlReportingName)`$$($mockNamedInstance_InstanceName)" + $mockNamedInstance_IntegrationServiceName = $mockSqlIntegrationName + $mockNamedInstance_AnalysisServiceName = "$($mockSqlAnalysisName)`$$($mockNamedInstance_InstanceName)" + + $mockmockSourceCredentialUserName = "COMPANY\sqladmin" + $mockmockSourceCredentialPassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force + $mockSourceCredential = New-Object System.Management.Automation.PSCredential( $mockmockSourceCredentialUserName, $mockmockSourceCredentialPassword ) + + #region Function mocks + $mockEmptyHashtable = { + return @() + } + + $mockGetService_DefaultInstance = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_DatabaseServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AgentServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_FullTextServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_ReportingServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockDefaultInstance_IntegrationServiceName -f $mockCurrentSqlMajorVersion) -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AnalysisServiceName -PassThru -Force + ) + ) + } + + + $mockGetService_NamedInstance = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_DatabaseServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AgentServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_FullTextServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_ReportingServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockNamedInstance_IntegrationServiceName -f $mockCurrentSqlMajorVersion) -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AnalysisServiceName -PassThru -Force + ) + ) + } + + $mockGetItemProperty_CallingWithWrongParameters = { + throw 'Mock Get-ItemProperty was called with wrong parameters' + } + + $mockRegistryPathSqlInstanceId = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' + $mockRegistryPathAnalysisServicesInstanceId = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\OLAP' + $mockGetItemProperty_SqlInstanceId = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name $mockCurrentInstanceName -Value $mockCurrentDatabaseEngineInstanceId -PassThru -Force + ) + ) + } + + $mockGetItemProperty_SqlInstanceId_ParameterFilter = { + $Path -eq $mockRegistryPathSqlInstanceId -and + $Name -eq $mockCurrentInstanceName + } + + $mockGetItemProperty_AnalysisServicesInstanceId = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name $mockCurrentInstanceName -Value $mockCurrentAnalysisServiceInstanceId -PassThru -Force + ) + ) + } + + $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter = { + $Path -eq $mockRegistryPathAnalysisServicesInstanceId -and + $Name -eq $mockCurrentInstanceName + } + + $mockGetItemProperty_DatabaseEngineSqlBinRoot = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'SQLBinRoot' -Value $mockCurrentDatabaseEngineSqlBinDirectory -PassThru -Force + ) + ) + } + + $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter = { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\setup" -and + $Name -eq 'SQLBinRoot' + } + + $mockGetItemProperty_AnalysisServicesSqlBinRoot = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'SQLBinRoot' -Value $mockCurrentAnalysisServicesSqlBinDirectory -PassThru -Force + ) + ) + } + + $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter = { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockCurrentAnalysisServiceInstanceId\setup" -and + $Name -eq 'SQLBinRoot' + } + + $mockGetItemProperty_IntegrationsServicesSqlPath = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'SQLPath' -Value $mockCurrentIntegrationServicesSqlPathDirectory -PassThru -Force + ) + ) + } + + $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter = { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockCurrentSqlMajorVersion)0\DTS\setup" -and + $Name -eq 'SQLPath' + } + + $mockGetNetFirewallRule = { + return @( + ( + New-CimInstance -ClassName 'MSFT_NetFirewallRule' -Property @{ + 'DisplayName' = $DisplayName + 'Enabled' = $true + 'Profile' = 'Any' + 'Direction' = 1 # 1 = Inbound, 2 = Outbound + } -Namespace 'root/standardcimv2' -ClientOnly + ) + ) + } + + $mockGetNetFirewallApplicationFilter = { + if ($AssociatedNetFirewallRule.DisplayName -eq "SQL Server Database Engine instance $mockCurrentInstanceName") + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Program' -Value (Join-Path $mockCurrentDatabaseEngineSqlBinDirectory -ChildPath $mockDatabaseEngineExecutableName) -PassThru -Force + ) + ) + } + elseif ($AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Integration Services Application') + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Program' -Value (Join-Path -Path (Join-Path $mockCurrentIntegrationServicesSqlPathDirectory -ChildPath 'Binn') -ChildPath $mockIntegrationServicesExecutableName) -PassThru -Force + ) + ) + } + else + { + throw "Mock Get-NetFirewallApplicationFilter was called with a rule containing an unknown display name; $($AssociatedNetFirewallRule.DisplayName)" + } + } + + $mockGetNetFirewallServiceFilter = { + if ($AssociatedNetFirewallRule.DisplayName -eq "SQL Server Analysis Services instance $mockCurrentInstanceName") + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Service' -Value $mockCurrentSqlAnalysisServiceName -PassThru -Force + ) + ) + } + elseif ($AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Browser') + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Service' -Value $mockSQLBrowserServiceName -PassThru -Force + ) + ) + } + else + { + throw "Mock Get-NetFirewallServiceFilter was called with a rule containing an unknown display name; $($AssociatedNetFirewallRule.DisplayName)" + } + } + + $mockGetNetFirewallPortFilter = { + if ($AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Reporting Services 80') + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Protocol' -Value $mockFirewallRulePort_ReportingServicesNoSslProtocol -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalPort' -Value $mockFirewallRulePort_ReportingServicesNoSslLocalPort -PassThru -Force + ) + ) + } + elseif ($AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Reporting Services 443') + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Protocol' -Value $mockFirewallRulePort_ReportingServicesSslProtocol -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalPort' -Value $mockFirewallRulePort_ReportingServicesSslLocalPort -PassThru -Force + ) + ) + } + elseif ($AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Integration Services Port') + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Protocol' -Value $mockFirewallRulePort_IntegrationServicesProtocol -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalPort' -Value $mockFirewallRulePort_IntegrationServicesLocalPort -PassThru -Force + ) + ) + } + else + { + throw "Mock Get-NetFirewallPortFilter was called with a rule containing an unknown display name; $($AssociatedNetFirewallRule.DisplayName)" + } + } + + $mockNewNetFirewallRule = { + if ( + ( + $DisplayName -eq "SQL Server Database Engine instance $mockCurrentInstanceName" -and + $Program -eq (Join-Path $mockCurrentDatabaseEngineSqlBinDirectory -ChildPath $mockDatabaseEngineExecutableName) + ) -or + ( + $DisplayName -eq 'SQL Server Browser' -and + $Service -eq $mockSQLBrowserServiceName + ) -or + ( + $DisplayName -eq "SQL Server Analysis Services instance $mockCurrentInstanceName" -and + $Service -eq $mockCurrentSqlAnalysisServiceName + ) -or + ( + $DisplayName -eq "SQL Server Reporting Services 80" -and + $Protocol -eq $mockFirewallRulePort_ReportingServicesNoSslProtocol -and + $LocalPort -eq $mockFirewallRulePort_ReportingServicesNoSslLocalPort + ) -or + ( + $DisplayName -eq "SQL Server Reporting Services 443" -and + $Protocol -eq $mockFirewallRulePort_ReportingServicesSslProtocol -and + $LocalPort -eq $mockFirewallRulePort_ReportingServicesSslLocalPort + ) -or + ( + $DisplayName -eq "SQL Server Integration Services Application" -and + $Program -eq (Join-Path -Path (Join-Path $mockCurrentIntegrationServicesSqlPathDirectory -ChildPath 'Binn') -ChildPath $mockIntegrationServicesExecutableName) + ) -or + ( + $DisplayName -eq "SQL Server Integration Services Port" -and + $Protocol -eq $mockFirewallRulePort_IntegrationServicesProtocol -and + $LocalPort -eq $mockFirewallRulePort_IntegrationServicesLocalPort + ) + ) + { + return + } + + throw "`nMock Get-NewFirewallRule was called with an unexpected rule configuration`n" + ` + "Display Name: $DisplayName`n" + ` + "Application: $Program`n" + ` + "Service: $Service`n" + ` + "Protocol: $Protocol`n" + ` + "Local port: $LocalPort`n" + } + + $mockGetItem_SqlMajorVersion = { + return New-Object Object | + Add-Member ScriptProperty VersionInfo { + return New-Object Object | + Add-Member -MemberType NoteProperty -Name 'ProductVersion' -Value ('{0}.0.0000.00000' -f $mockCurrentSqlMajorVersion) -PassThru -Force + } -PassThru -Force + } + + $mockGetItem_SqlMajorVersion_ParameterFilter = { + $Path -eq $mockCurrentPathToSetupExecutable + } + + #endregion Function mocks + + # Default parameters that are used for the It-blocks + $mockDefaultParameters = @{ + # These are written with both lower-case and upper-case to make sure we support that. + Features = 'SQLEngine,Rs,As,Is' + SourceCredential = $mockSourceCredential + } + + Describe "xSQLServerFirewall\Get-TargetResource" -Tag 'Get' { + # Local path to TestDrive:\ + $mockSourcePath = $TestDrive.FullName + + BeforeEach { + # General mocks + Mock -CommandName Get-Item -ParameterFilter $mockGetItem_SqlMajorVersion_ParameterFilter -MockWith $mockGetItem_SqlMajorVersion -Verifiable + + # Mock SQL Server Database Engine registry for Instance ID. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter ` + -MockWith $mockGetItemProperty_SqlInstanceId -Verifiable + + # Mock SQL Server Analysis Services registry for Instance ID. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter ` + -MockWith $mockGetItemProperty_AnalysisServicesInstanceId -Verifiable + + # Mocking SQL Server Database Engine registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter ` + -MockWith $mockGetItemProperty_DatabaseEngineSqlBinRoot -Verifiable + + # Mocking SQL Server Database Engine registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter ` + -MockWith $mockGetItemProperty_AnalysisServicesSqlBinRoot -Verifiable + + # Mock SQL Server Integration Services Registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter ` + -MockWith $mockGetItemProperty_IntegrationsServicesSqlPath -Verifiable + + Mock -CommandName Get-ItemProperty -MockWith $mockGetItemProperty_CallingWithWrongParameters -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + } + + $testProductVersion | ForEach-Object -Process { + $mockCurrentSqlMajorVersion = $_ + + $mockCurrentPathToSetupExecutable = Join-Path -Path $mockSourcePath -ChildPath $mockSetupExecutableName + + $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + $mockCurrentDatabaseEngineInstanceId = "$($mockSqlDatabaseEngineInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + $mockCurrentAnalysisServiceInstanceId = "$($mockSqlAnalysisServicesInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + + $mockCurrentSqlAnalysisServiceName = $mockDefaultInstance_AnalysisServiceName + + $mockCurrentDatabaseEngineSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\MSSQL\Binn" + $mockCurrentAnalysisServicesSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\OLAP\Binn" + $mockCurrentIntegrationServicesSqlPathDirectory = "C:\Program Files\Microsoft SQL Server\$($mockCurrentSqlMajorVersion)0\DTS\" + + + $mockSqlInstallPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL" + $mockSqlBackupPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\Backup" + $mockSqlTempDatabasePath = '' + $mockSqlTempDatabaseLogPath = '' + $mockSqlDefaultDatabaseFilePath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" + $mockSqlDefaultDatabaseLogPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" + + Context "When SQL Server version is $mockCurrentSqlMajorVersion. Testing helper function Get-SqlRootPath" { + It 'Should return the the correct path for Database Engine' { + $result = Get-SQLPath -Feature 'SQLEngine' -InstanceName $mockDefaultInstance_InstanceName + $result | Should Be $mockCurrentDatabaseEngineSqlBinDirectory + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 0 -Scope It + } + + It 'Should return the the correct path for Analysis Services' { + $result = Get-SQLPath -Feature 'As' -InstanceName $mockDefaultInstance_InstanceName + $result | Should Be $mockCurrentAnalysisServicesSqlBinDirectory + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 0 -Scope It + } + + It 'Should return the the correct path for Integration Services' { + $result = Get-SQLPath -Feature 'Is' -InstanceName $mockDefaultInstance_InstanceName -SQLVersion $mockCurrentSqlMajorVersion + $result | Should Be $mockCurrentIntegrationServicesSqlPathDirectory + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and there is no components installed" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Test-IsFirewallRuleInDesiredState -Verifiable + Mock -CommandName New-NetFirewallRule -Verifiable + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.InstanceName | Should Be $testParameters.InstanceName + $result.SourcePath | Should Be $testParameters.SourcePath + } + + It 'Should not return any values in the read parameters' { + $result = Get-TargetResource @testParameters + $result.DatabaseEngineFirewall | Should BeNullOrEmpty + $result.BrowserFirewall | Should BeNullOrEmpty + $result.ReportingServicesFirewall | Should BeNullOrEmpty + $result.AnalysisServicesFirewall | Should BeNullOrEmpty + $result.IntegrationServicesFirewall | Should BeNullOrEmpty + } + + It 'Should return state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + $result.Features | Should BeNullOrEmpty + } + + It 'Should call the correct functions exact number of times' { + $result = Get-TargetResource @testParameters + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Test-IsFirewallRuleInDesiredState -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 0 -Scope It + } + } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is not in the desired state for default instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -Verifiable + Mock -CommandName New-NetFirewallRule -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.InstanceName | Should Be $testParameters.InstanceName + $result.SourcePath | Should Be $testParameters.SourcePath + $result.Features | Should Be $testParameters.Features + } + + It 'Should return $false for the read parameter DatabaseEngineFirewall' { + $result = Get-TargetResource @testParameters + $result.DatabaseEngineFirewall | Should Be $false + } + + It 'Should return $false for the read parameter BrowserFirewall' { + $result = Get-TargetResource @testParameters + $result.BrowserFirewall | Should Be $false + } + + It 'Should return $false for the read parameter ReportingServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.ReportingServicesFirewall | Should Be $false + } + + It 'Should return $false for the read parameter AnalysisServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.AnalysisServicesFirewall | Should Be $false + } + + It 'Should return $false for the read parameter IntegrationServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.IntegrationServicesFirewall | Should Be $false + } + + It 'Should return state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + } + + It 'Should call the correct functions exact number of times' { + $result = Get-TargetResource @testParameters + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 6 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is in the desired state for default instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -MockWith $mockGetNetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -MockWith $mockGetNetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -MockWith $mockGetNetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -MockWith $mockGetNetFirewallPortFilter -Verifiable + Mock -CommandName New-NetFirewallRule -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.InstanceName | Should Be $testParameters.InstanceName + $result.SourcePath | Should Be $testParameters.SourcePath + } + + It 'Should return $true for the read parameter DatabaseEngineFirewall' { + $result = Get-TargetResource @testParameters + $result.DatabaseEngineFirewall | Should Be $true + } + + It 'Should return $true for the read parameter BrowserFirewall' { + $result = Get-TargetResource @testParameters + $result.BrowserFirewall | Should Be $true + } + + It 'Should return $true for the read parameter ReportingServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.ReportingServicesFirewall | Should Be $true + } + + It 'Should return $true for the read parameter AnalysisServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.AnalysisServicesFirewall | Should Be $true + } + + It 'Should return $true for the read parameter IntegrationServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.IntegrationServicesFirewall | Should Be $true + } + + It 'Should return state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + $result.Features | Should Be $testParameters.Features + } + + It 'Should call the correct functions exact number of times' { + $result = Get-TargetResource @testParameters + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 8 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + + $mockCurrentInstanceName = $mockNamedInstance_InstanceName + $mockCurrentDatabaseEngineInstanceId = "$($mockSqlDatabaseEngineInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + $mockCurrentAnalysisServiceInstanceId = "$($mockSqlAnalysisServicesInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + + $mockCurrentSqlAnalysisServiceName = $mockNamedInstance_AnalysisServiceName + + $mockCurrentDatabaseEngineSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\MSSQL\Binn" + $mockCurrentAnalysisServicesSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\OLAP\Binn" + $mockCurrentIntegrationServicesSqlPathDirectory = "C:\Program Files\Microsoft SQL Server\$($mockCurrentSqlMajorVersion)0\DTS\" + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is not in the desired state for named instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -Verifiable + Mock -CommandName New-NetFirewallRule -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_NamedInstance -Verifiable + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.InstanceName | Should Be $testParameters.InstanceName + $result.SourcePath | Should Be $testParameters.SourcePath + $result.Features | Should Be $testParameters.Features + } + + It 'Should return $false for the read parameter DatabaseEngineFirewall' { + $result = Get-TargetResource @testParameters + $result.DatabaseEngineFirewall | Should Be $false + } + + It 'Should return $false for the read parameter BrowserFirewall' { + $result = Get-TargetResource @testParameters + $result.BrowserFirewall | Should Be $false + } + + It 'Should return $false for the read parameter ReportingServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.ReportingServicesFirewall | Should Be $false + } + + It 'Should return $false for the read parameter AnalysisServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.AnalysisServicesFirewall | Should Be $false + } + + It 'Should return $false for the read parameter IntegrationServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.IntegrationServicesFirewall | Should Be $false + } + + It 'Should return state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + } + + It 'Should call the correct functions exact number of times' { + $result = Get-TargetResource @testParameters + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 6 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is in the desired state for named instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -MockWith $mockGetNetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -MockWith $mockGetNetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -MockWith $mockGetNetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -MockWith $mockGetNetFirewallPortFilter -Verifiable + Mock -CommandName New-NetFirewallRule -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_NamedInstance -Verifiable + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.InstanceName | Should Be $testParameters.InstanceName + $result.SourcePath | Should Be $testParameters.SourcePath + } + + It 'Should return $true for the read parameter DatabaseEngineFirewall' { + $result = Get-TargetResource @testParameters + $result.DatabaseEngineFirewall | Should Be $true + } + + It 'Should return $true for the read parameter BrowserFirewall' { + $result = Get-TargetResource @testParameters + $result.BrowserFirewall | Should Be $true + } + + It 'Should return $true for the read parameter ReportingServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.ReportingServicesFirewall | Should Be $true + } + + It 'Should return $true for the read parameter AnalysisServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.AnalysisServicesFirewall | Should Be $true + } + + It 'Should return $true for the read parameter IntegrationServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.IntegrationServicesFirewall | Should Be $true + } + + It 'Should return state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + $result.Features | Should Be $testParameters.Features + } + + It 'Should call the correct functions exact number of times' { + $result = Get-TargetResource @testParameters + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 8 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + } + } + + Describe "xSQLServerFirewall\Set-TargetResource" -Tag 'Set' { + # Local path to TestDrive:\ + $mockSourcePath = $TestDrive.FullName + + BeforeEach { + # General mocks + Mock -CommandName Get-Item -ParameterFilter $mockGetItem_SqlMajorVersion_ParameterFilter -MockWith $mockGetItem_SqlMajorVersion -Verifiable + + # Mock SQL Server Database Engine registry for Instance ID. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter ` + -MockWith $mockGetItemProperty_SqlInstanceId -Verifiable + + # Mock SQL Server Analysis Services registry for Instance ID. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter ` + -MockWith $mockGetItemProperty_AnalysisServicesInstanceId -Verifiable + + # Mocking SQL Server Database Engine registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter ` + -MockWith $mockGetItemProperty_DatabaseEngineSqlBinRoot -Verifiable + + # Mocking SQL Server Database Engine registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter ` + -MockWith $mockGetItemProperty_AnalysisServicesSqlBinRoot -Verifiable + + # Mock SQL Server Integration Services Registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter ` + -MockWith $mockGetItemProperty_IntegrationsServicesSqlPath -Verifiable + + Mock -CommandName Get-ItemProperty -MockWith $mockGetItemProperty_CallingWithWrongParameters -Verifiable + Mock -CommandName New-NetFirewallRule -MockWith $mockNewNetFirewallRule -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + } + + $testProductVersion | ForEach-Object -Process { + $mockCurrentSqlMajorVersion = $_ + + $mockCurrentPathToSetupExecutable = Join-Path -Path $mockSourcePath -ChildPath $mockSetupExecutableName + + $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + $mockCurrentDatabaseEngineInstanceId = "$($mockSqlDatabaseEngineInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + $mockCurrentAnalysisServiceInstanceId = "$($mockSqlAnalysisServicesInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + + $mockCurrentSqlAnalysisServiceName = $mockDefaultInstance_AnalysisServiceName + + $mockCurrentDatabaseEngineSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\MSSQL\Binn" + $mockCurrentAnalysisServicesSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\OLAP\Binn" + $mockCurrentIntegrationServicesSqlPathDirectory = "C:\Program Files\Microsoft SQL Server\$($mockCurrentSqlMajorVersion)0\DTS\" + + + $mockSqlInstallPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL" + $mockSqlBackupPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\Backup" + $mockSqlTempDatabasePath = '' + $mockSqlTempDatabaseLogPath = '' + $mockSqlDefaultDatabaseFilePath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" + $mockSqlDefaultDatabaseLogPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" + + # Mock this here because only the first test uses it. + Mock -CommandName Test-TargetResource -MockWith { return $false } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and there is no components installed" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Test-IsFirewallRuleInDesiredState -Verifiable + Mock -CommandName New-NetFirewallRule -Verifiable + + Mock New-TerminatingError -MockWith { return $ErrorType } + } + + It 'Should throw the correct error when Set-TargetResource verifies result with Test-TargetResource' { + { Set-TargetResource @testParameters } | Should Throw TestFailedAfterSet + + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Test-IsFirewallRuleInDesiredState -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 0 -Scope It + } + } + + # Mock this here so the rest of the test uses it. + Mock -CommandName Test-TargetResource -MockWith { return $true } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is not in the desired state for default instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should create all firewall rules without throwing' { + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 8 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 14 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 2 -Scope It + } + } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is in the desired state for default instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -MockWith $mockGetNetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -MockWith $mockGetNetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -MockWith $mockGetNetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -MockWith $mockGetNetFirewallPortFilter -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should not call mock New-NetFirewallRule' { + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 8 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + + $mockCurrentInstanceName = $mockNamedInstance_InstanceName + $mockCurrentDatabaseEngineInstanceId = "$($mockSqlDatabaseEngineInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + $mockCurrentAnalysisServiceInstanceId = "$($mockSqlAnalysisServicesInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + + $mockCurrentSqlAnalysisServiceName = $mockNamedInstance_AnalysisServiceName + + $mockCurrentDatabaseEngineSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\MSSQL\Binn" + $mockCurrentAnalysisServicesSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\OLAP\Binn" + $mockCurrentIntegrationServicesSqlPathDirectory = "C:\Program Files\Microsoft SQL Server\$($mockCurrentSqlMajorVersion)0\DTS\" + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is not in the desired state for named instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_NamedInstance -Verifiable + } + + It 'Should create all firewall rules without throwing' { + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 8 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 14 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 2 -Scope It + } + } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is in the desired state for named instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -MockWith $mockGetNetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -MockWith $mockGetNetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -MockWith $mockGetNetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -MockWith $mockGetNetFirewallPortFilter -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_NamedInstance -Verifiable + } + + It 'Should not call mock New-NetFirewallRule' { + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 8 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + } + } + + Describe "xSQLServerFirewall\Test-TargetResource" -Tag 'Test' { + # Local path to TestDrive:\ + $mockSourcePath = $TestDrive.FullName + + BeforeEach { + # General mocks + Mock -CommandName Get-Item -ParameterFilter $mockGetItem_SqlMajorVersion_ParameterFilter -MockWith $mockGetItem_SqlMajorVersion -Verifiable + + # Mock SQL Server Database Engine registry for Instance ID. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter ` + -MockWith $mockGetItemProperty_SqlInstanceId -Verifiable + + # Mock SQL Server Analysis Services registry for Instance ID. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter ` + -MockWith $mockGetItemProperty_AnalysisServicesInstanceId -Verifiable + + # Mocking SQL Server Database Engine registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter ` + -MockWith $mockGetItemProperty_DatabaseEngineSqlBinRoot -Verifiable + + # Mocking SQL Server Database Engine registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter ` + -MockWith $mockGetItemProperty_AnalysisServicesSqlBinRoot -Verifiable + + # Mock SQL Server Integration Services Registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter ` + -MockWith $mockGetItemProperty_IntegrationsServicesSqlPath -Verifiable + + Mock -CommandName Get-ItemProperty -MockWith $mockGetItemProperty_CallingWithWrongParameters -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + } + + $mockCurrentSqlMajorVersion = $_ + + $mockCurrentPathToSetupExecutable = Join-Path -Path $mockSourcePath -ChildPath $mockSetupExecutableName + + $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + $mockCurrentDatabaseEngineInstanceId = "$($mockSqlDatabaseEngineInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + $mockCurrentAnalysisServiceInstanceId = "$($mockSqlAnalysisServicesInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + + $mockCurrentSqlAnalysisServiceName = $mockDefaultInstance_AnalysisServiceName + + $mockCurrentDatabaseEngineSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\MSSQL\Binn" + $mockCurrentAnalysisServicesSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\OLAP\Binn" + $mockCurrentIntegrationServicesSqlPathDirectory = "C:\Program Files\Microsoft SQL Server\$($mockCurrentSqlMajorVersion)0\DTS\" + + + $mockSqlInstallPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL" + $mockSqlBackupPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\Backup" + $mockSqlTempDatabasePath = '' + $mockSqlTempDatabaseLogPath = '' + $mockSqlDefaultDatabaseFilePath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" + $mockSqlDefaultDatabaseLogPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" + + Context "When the system is not in the desired state" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should return $false from Test-TargetResource' { + $resultTestTargetResource = Test-TargetResource @testParameters + $resultTestTargetResource | Should Be $false + } + } + + Context "When the system is in the desired state" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -MockWith $mockGetNetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -MockWith $mockGetNetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -MockWith $mockGetNetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -MockWith $mockGetNetFirewallPortFilter -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should return $true from Test-TargetResource' { + $resultTestTargetResource = Test-TargetResource @testParameters + $resultTestTargetResource | Should Be $true + } + } + } + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/xPDT.psm1 b/xPDT.psm1 index 6f22564c1..6334ef1a4 100644 --- a/xPDT.psm1 +++ b/xPDT.psm1 @@ -108,11 +108,11 @@ function ExtractArguments ( [parameter(Mandatory = $true)] $functionBoundParameters, - + [parameter(Mandatory = $true)] [string[]] $argumentNames, - + [string[]] $newArgumentNames ) @@ -160,7 +160,7 @@ namespace Source 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 { @@ -259,25 +259,25 @@ namespace Source EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] public static extern bool CreateProcessAsUser( - IntPtr hToken, - string lpApplicationName, + IntPtr hToken, + string lpApplicationName, string lpCommandLine, - ref SECURITY_ATTRIBUTES lpProcessAttributes, + ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, - bool bInheritHandle, - Int32 dwCreationFlags, + bool bInheritHandle, + Int32 dwCreationFlags, IntPtr lpEnvrionment, - string lpCurrentDirectory, + string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation ); [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] public static extern bool DuplicateTokenEx( - IntPtr hExistingToken, + IntPtr hExistingToken, Int32 dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, - Int32 ImpersonationLevel, + Int32 ImpersonationLevel, Int32 dwTokenType, ref IntPtr phNewToken ); @@ -294,11 +294,11 @@ namespace Source [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] internal static extern bool AdjustTokenPrivileges( - IntPtr htok, + IntPtr htok, bool disall, - ref TokPriv1Luid newst, - int len, - IntPtr prev, + ref TokPriv1Luid newst, + int len, + IntPtr prev, IntPtr relen ); @@ -307,14 +307,14 @@ namespace Source [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] internal static extern bool OpenProcessToken( - IntPtr h, - int acc, + IntPtr h, + int acc, ref IntPtr phtok ); [DllImport("advapi32.dll", SetLastError = true)] internal static extern bool LookupPrivilegeValue( - string host, + string host, string name, ref long pluid ); @@ -338,15 +338,15 @@ namespace Source 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()); + 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, + hproc, + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok ); if(!bResult) @@ -357,8 +357,8 @@ namespace Source tp.Luid = 0; tp.Attr = SE_PRIVILEGE_ENABLED; bResult = LookupPrivilegeValue( - null, - SE_INCRASE_QUOTA, + null, + SE_INCRASE_QUOTA, ref tp.Luid ); if(!bResult) @@ -366,18 +366,18 @@ namespace Source 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, + 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, @@ -397,13 +397,13 @@ namespace Source hDupedToken, null, strCommand, - ref sa, ref sa, - false, - 0, + ref sa, + false, + 0, IntPtr.Zero, - null, - ref si, + null, + ref si, ref pi ); if(!bResult) @@ -495,7 +495,7 @@ function GetWin32ProcessOwner { return $Owner.Domain + "\" + $Owner.User } - else + else { return $Owner.User } @@ -643,7 +643,7 @@ function WaitForWin32ProcessStart { $value = @(GetWin32Process @GetArguments).Count -ge 1 } while(!$value -and ([DateTime]::Now - $start).TotalMilliseconds -lt $Delay) - + return $value } @@ -675,15 +675,15 @@ function WaitForWin32ProcessEnd function NetUse { param - ( + ( [parameter(Mandatory)] [string] $SourcePath, - + [parameter(Mandatory)] [PSCredential] $Credential, - + [string] [ValidateSet("Present","Absent")] $Ensure = "Present" @@ -691,46 +691,18 @@ function NetUse if(($SourcePath.Length -ge 2) -and ($SourcePath.Substring(0,2) -eq "\\")) { - $args = @() + $args = @() if ($Ensure -eq "Absent") { - $args += "use", $SourcePath, "/del" + $args += "use", $SourcePath, "/del" } - else + else { - $args += "use", $SourcePath, $($Credential.GetNetworkCredential().Password), "/user:$($Credential.GetNetworkCredential().Domain)\$($Credential.GetNetworkCredential().UserName)" + $args += "use", $SourcePath, $($Credential.GetNetworkCredential().Password), "/user:$($Credential.GetNetworkCredential().Domain)\$($Credential.GetNetworkCredential().UserName)" } - - &"net" $args - } -} -function GetxPDTVariable -{ - param - ( - [parameter(Mandatory = $true)] - [System.String] - $Component, - - [parameter(Mandatory = $true)] - [System.String] - $Version, - - [parameter(Mandatory = $true)] - [System.String] - $Role, - - [parameter(Mandatory = $true)] - [System.String] - $Name, - - [System.String] - $Update = "Latest" - ) - - $xPDT = [XML](Get-Content "$PSScriptRoot\xPDT.xml") - $xPDT.SelectSingleNode("//xPDT/Component[@Name='$Component' and @Version='$Version']/Role[@Name='$Role']/Update[@Name='$Update']/Variable[@Name='$Name']").Value + &"net" $args + } } -Export-ModuleMember ResolvePath,StartWin32Process,WaitForWin32ProcessEnd,NetUse,GetxPDTVariable +Export-ModuleMember ResolvePath,StartWin32Process,WaitForWin32ProcessEnd,NetUse diff --git a/xPDT.xml b/xPDT.xml deleted file mode 100644 index c0219c805..000000000 --- a/xPDT.xml +++ /dev/null @@ -1,1091 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/xSQLServerHelper.psm1 b/xSQLServerHelper.psm1 index ab05df141..bc5fefb75 100644 --- a/xSQLServerHelper.psm1 +++ b/xSQLServerHelper.psm1 @@ -1,6 +1,3 @@ -# Set Global Module Verbose -$VerbosePreference = 'Continue' - # Load Localization Data Import-LocalizedData LocalizedData -filename xSQLServer.strings.psd1 -ErrorAction SilentlyContinue Import-LocalizedData USLocalizedData -filename xSQLServer.strings.psd1 -UICulture en-US -ErrorAction SilentlyContinue @@ -295,7 +292,7 @@ function New-VerboseMessage [Parameter(Mandatory=$true)] $Message ) - Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": $Message"); + Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": $Message") -Verbose } <# @@ -656,7 +653,7 @@ function New-ListenerADObject Exit } - $SucccessChk =0 + $SuccessChk =0 #Check for AD Object Validate at least three successful attempts $i=1 @@ -1249,10 +1246,10 @@ function Restart-SqlService Where-Object { ($_.Type -eq "SQL Server Agent") -and ($_.State -eq 2) } ## Build a listing of resources being acted upon - $resourceNames = @($sqlService.Name, ($agentService | Select -ExpandProperty Name)) -join "," + $resourceNames = @($sqlService.Name, ($agentService | Select-Object -ExpandProperty Name)) -join "," ## Stop the SQL Server and dependent resources - New-VerboseMessage -Message 'Bringing the SQL Server resources $resourceNames offline.' + New-VerboseMessage -Message "Bringing the SQL Server resources $resourceNames offline." $sqlService | Invoke-CimMethod -MethodName TakeOffline -Arguments @{ Timeout = $Timeout } ## Start the SQL server resource @@ -1299,16 +1296,16 @@ function Restart-SqlService #> function Get-SqlDatabaseRecoveryModel { - [CmdletBinding()] + [CmdletBinding()] param - ( + ( [Parameter(Mandatory = $true)] - [ValidateNotNull()] + [ValidateNotNull()] [System.Object] $SqlServerObject, - + [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.String] $DatabaseName ) @@ -1319,7 +1316,7 @@ function Get-SqlDatabaseRecoveryModel $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS if ($sqlDatabase) - { + { $sqlDatabaseRecoveryModel = $sqlDatabase.RecoveryModel Write-Verbose -Message "The current recovery model used by database $Name is '$sqlDatabaseRecoveryModel'" } @@ -1348,16 +1345,16 @@ function Get-SqlDatabaseRecoveryModel #> function Set-SqlDatabaseRecoveryModel { - [CmdletBinding()] + [CmdletBinding()] param - ( + ( [Parameter(Mandatory = $true)] - [ValidateNotNull()] + [ValidateNotNull()] [System.Object] $SqlServerObject, - + [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.String] $DatabaseName, @@ -1374,7 +1371,7 @@ function Set-SqlDatabaseRecoveryModel $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS if ($sqlDatabase) - { + { if($sqlDatabase.RecoveryModel -ne $RecoveryModel) { $sqlDatabase.RecoveryModel = $RecoveryModel @@ -1408,27 +1405,27 @@ function Set-SqlDatabaseRecoveryModel #> function Get-SqlDatabasePermission { - [CmdletBinding()] + [CmdletBinding()] param - ( + ( [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.Object] $SqlServerObject, - + [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.String] $Name, [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.String] $Database, [Parameter(Mandatory = $true)] [ValidateSet('Grant','Deny')] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.String] $PermissionState ) @@ -1443,13 +1440,13 @@ function Get-SqlDatabasePermission [System.String[]] $permission = @() if ($sqlDatabase) - { + { if ($sqlLogin) { Write-Verbose -Message "Getting permissions for user '$Name' in database '$Database'." $databasePermissionInfo = $sqlDatabase.EnumDatabasePermissions($Name) - $databasePermissionInfo = $databasePermissionInfo | Where-Object -FilterScript { + $databasePermissionInfo = $databasePermissionInfo | Where-Object -FilterScript { $_.PermissionState -eq $PermissionState } @@ -1469,7 +1466,7 @@ function Get-SqlDatabasePermission { throw New-TerminatingError -ErrorType LoginNotFound ` -FormatArgs @($Name,$sqlServer,$sqlInstanceName) ` - -ErrorCategory ObjectNotFound + -ErrorCategory ObjectNotFound } } else @@ -1499,33 +1496,33 @@ function Get-SqlDatabasePermission If the permission should be granted or denied. Valid values are Grant or Deny .PARAMETER Permissions - The permissions to be granted or denied for the user in the database. + The permissions to be granted or denied for the user in the database. Valid permissions can be found in the article SQL Server Permissions: https://msdn.microsoft.com/en-us/library/ms191291.aspx#SQL Server Permissions #> function Add-SqlDatabasePermission { - [CmdletBinding()] + [CmdletBinding()] param - ( + ( [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.Object] $SqlServerObject, - + [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.String] $Name, [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.String] $Database, [Parameter(Mandatory = $true)] [ValidateSet('Grant','Deny')] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.String] $PermissionState, @@ -1542,7 +1539,7 @@ function Add-SqlDatabasePermission $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS if ($sqlDatabase) - { + { if ($sqlLogin) { if (!$sqlDatabase.Users[$Name]) @@ -1575,9 +1572,9 @@ function Add-SqlDatabasePermission $permissionSet."$permission" = $true } - switch ($PermissionState) + switch ($PermissionState) { - 'Grant' + 'Grant' { $sqlDatabase.Grant($permissionSet,$Name) } @@ -1586,7 +1583,7 @@ function Add-SqlDatabasePermission { $sqlDatabase.Deny($permissionSet,$Name) } - } + } } catch { @@ -1633,27 +1630,27 @@ function Add-SqlDatabasePermission #> function Remove-SqlDatabasePermission { - [CmdletBinding()] + [CmdletBinding()] param - ( + ( [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.Object] $SqlServerObject, - + [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.String] $Name, - + [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.String] $Database, [Parameter(Mandatory = $true)] [ValidateSet('Grant','Deny')] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [System.String] $PermissionState, @@ -1670,7 +1667,7 @@ function Remove-SqlDatabasePermission $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS if ($sqlDatabase) - { + { if ($sqlLogin) { if (!$sqlDatabase.Users[$Name]) @@ -1704,18 +1701,18 @@ function Remove-SqlDatabasePermission $permissionSet."$permission" = $false } - switch ($PermissionState) + switch ($PermissionState) { 'Grant' { $sqlDatabase.Grant($permissionSet,$Name) } - + 'Deny' { $sqlDatabase.Deny($permissionSet,$Name) } - } + } } catch { From d17eecc340329171675474e58490daac8f933750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl?= Date: Wed, 25 Jan 2017 18:46:23 +0100 Subject: [PATCH 29/31] BREAKING CHANGE: xSQLServerSetup: Rename parameter PID to ProductKey - Changes to xSQLServerSetup - BREAKING CHANGE: Renamed parameter PID to ProductKey to avoid collision with automatic variable $PID --- CHANGELOG.md | 1 + .../MSFT_xSQLServerSetup.psm1 | 23 ++++++++++++------- .../MSFT_xSQLServerSetup.schema.mof | 2 +- README.md | 2 +- Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 | 4 ++++ 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c53adfbf4..2522caac0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - Function `ResolvePath` has been replaced with `[Environment]::ExpandEnvironmentVariables($SourcePath)` so that environment variables still can be used in Source Path. - Function `NetUse` has been replaced with `New-SmbMapping` and `Remove-SmbMapping`. - Renamed function `GetSQLVersion` to `Get-SqlMajorVersion`. + - BREAKING CHANGE: Renamed parameter PID to ProductKey to avoid collision with automatic variable $PID - Changes to xSQLServerScript - All credential parameters now also has the type [System.Management.Automation.Credential()] to better work with PowerShell 4.0. - It is now possible to configure two instances on the same node, with the same script. diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 index eb96f9090..438c19e52 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 @@ -387,7 +387,7 @@ function Get-TargetResource .PARAMETER InstanceID SQL instance ID, if different from InstanceName. - .PARAMETER PID + .PARAMETER ProductKey Product key for licensed installations. .PARAMETER UpdateEnabled @@ -530,7 +530,7 @@ function Set-TargetResource $InstanceID, [System.String] - $PID, + $ProductKey, [System.String] $UpdateEnabled, @@ -888,7 +888,7 @@ function Set-TargetResource 'UpdateEnabled', 'UpdateSource', 'Features', - 'PID', + 'ProductKey', 'SQMReporting', 'ErrorReporting', 'InstallSharedDir', @@ -989,7 +989,14 @@ function Set-TargetResource # Automatically include any additional arguments foreach ($argument in $argumentVars) { - $setupArguments += @{ $argument = (Get-Variable -Name $argument -ValueOnly) } + if($argument -eq 'ProductKey') + { + $setupArguments += @{ 'PID' = (Get-Variable -Name $argument -ValueOnly) } + } + else + { + $setupArguments += @{ $argument = (Get-Variable -Name $argument -ValueOnly) } + } } # Build the argument string to be passed to setup @@ -1034,9 +1041,9 @@ function Set-TargetResource $log = $log.Replace($SAPwd.GetNetworkCredential().Password,"********") } - if ($PID -ne "") + if ($ProductKey -ne "") { - $log = $log.Replace($PID,"*****-*****-*****-*****-*****") + $log = $log.Replace($ProductKey,"*****-*****-*****-*****-*****") } $logVars = @('AgtSvcAccount', 'SQLSvcAccount', 'FTSvcAccount', 'RSSvcAccount', 'ASSvcAccount','ISSvcAccount') @@ -1112,7 +1119,7 @@ function Set-TargetResource .PARAMETER InstanceID SQL instance ID, if different from InstanceName. - .PARAMETER PID + .PARAMETER ProductKey Product key for licensed installations. .PARAMETER UpdateEnabled @@ -1254,7 +1261,7 @@ function Test-TargetResource $InstanceID, [System.String] - $PID, + $ProductKey, [System.String] $UpdateEnabled, diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof index 5c178bfe9..fa3e30b52 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof @@ -10,7 +10,7 @@ class MSFT_xSQLServerSetup : OMI_BaseResource [Write, Description("SQL features to be installed.")] String Features; [Key, Description("Name of the SQL instance to be installed.")] String InstanceName; [Write, Description("SQL instance ID, if different from InstanceName.")] String InstanceID; - [Write, Description("Product key for licensed installations.")] String PID; + [Write, Description("Product key for licensed installations.")] String ProductKey; [Write, Description("Enabled updates during installation.")] String UpdateEnabled; [Write, Description("Path to the source of updates to be applied during installation.")] String UpdateSource; [Write, Description("Enable customer experience reporting.")] String SQMReporting; diff --git a/README.md b/README.md index 020c1faea..0396a2006 100644 --- a/README.md +++ b/README.md @@ -797,7 +797,7 @@ Installs SQL Server on the target node. * **[Boolean] ForceReboot** _(Write)_: Forces reboot. * **[String] Features** _(Write)_: SQL features to be installed. * **[String] InstanceID** _(Write)_: SQL instance ID, if different from InstanceName. -* **[String] PID** _(Write)_: Product key for licensed installations. +* **[String] ProductKey** _(Write)_: Product key for licensed installations. * **[String] UpdateEnabled** _(Write)_: Enabled updates during installation. * **[String] UpdateSource** _(Write)_: Path to the source of updates to be applied during installation. * **[String] SQMReporting** _(Write)_: Enable customer experience reporting. diff --git a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 index f7891b512..d1f061a3d 100644 --- a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 @@ -2415,6 +2415,7 @@ try InstanceName = $mockDefaultInstance_InstanceName SourceCredential = $null SourcePath = $mockSourcePath + ProductKey = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' } Mock -CommandName New-SmbMapping -Verifiable @@ -2446,6 +2447,7 @@ try Features = 'SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS' SQLSysAdminAccounts = 'COMPANY\sqladmin' ASSysAdminAccounts = 'COMPANY\sqladmin' + PID = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' } { Set-TargetResource @testParameters } | Should Not Throw @@ -2502,6 +2504,7 @@ try Action = 'Install' InstanceName = 'MSSQLSERVER' Features = 'SSMS' + PID = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' } { Set-TargetResource @testParameters } | Should Not Throw @@ -2538,6 +2541,7 @@ try Action = 'Install' InstanceName = 'MSSQLSERVER' Features = 'ADV_SSMS' + PID = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' } { Set-TargetResource @testParameters } | Should Not Throw From c6a938ff3a2979b0d1f4eb9904a8a53b4331022f Mon Sep 17 00:00:00 2001 From: Katie Keim Date: Wed, 25 Jan 2017 15:40:01 -0800 Subject: [PATCH 30/31] Releasing version 5.0.0.0 --- CHANGELOG.md | 2 ++ appveyor.yml | 4 ++-- xSQLServer.psd1 | 41 ++++------------------------------------- 3 files changed, 8 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2522caac0..43403fe9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.0.0.0 + - Improvements how tests are initiated in AppVeyor - Removed previous workaround (issue #201) from unit tests. - Changes in appveyor.yml so that SQL modules are removed before common test is run. diff --git a/appveyor.yml b/appveyor.yml index c8880e62b..7410e5359 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ #---------------------------------# # environment configuration # #---------------------------------# -version: 4.0.{build}.0 +version: 5.0.{build}.0 install: - git clone https://github.com/PowerShell/DscResource.Tests - appveyor DownloadFile https://dist.nuget.org/win-x86-commandline/v3.4.4/NuGet.exe @@ -46,7 +46,7 @@ deploy_script: # Creating project artifact $stagingDirectory = (Resolve-Path ..).Path $manifest = Join-Path $pwd "xSQLServer.psd1" - (Get-Content $manifest -Raw).Replace("4.0.0.0", $env:APPVEYOR_BUILD_VERSION) | Out-File $manifest + (Get-Content $manifest -Raw).Replace("5.0.0.0", $env:APPVEYOR_BUILD_VERSION) | Out-File $manifest $zipFilePath = Join-Path $stagingDirectory "$(Split-Path $pwd -Leaf).zip" Add-Type -assemblyname System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::CreateFromDirectory($pwd, $zipFilePath) diff --git a/xSQLServer.psd1 b/xSQLServer.psd1 index b5895c057..d1f53e837 100644 --- a/xSQLServer.psd1 +++ b/xSQLServer.psd1 @@ -1,6 +1,6 @@ @{ # Version number of this module. -ModuleVersion = '4.0.0.0' +ModuleVersion = '5.0.0.0' # ID used to uniquely identify this module GUID = '74e9ddb5-4cbc-4fa2-a222-2bcfb533fd66' @@ -47,42 +47,8 @@ PrivateData = @{ # IconUri = '' # ReleaseNotes of this module - ReleaseNotes = '- Fixes in xSQLServerConfiguration - - Added support for clustered SQL instances - - BREAKING CHANGE: Updated parameters to align with other resources (SQLServer / SQLInstanceName) - - Updated code to utilize CIM rather than WMI -- Added tests for resources - - xSQLServerConfiguration - - xSQLServerSetup - - xSQLServerDatabaseRole - - xSQLAOGroupJoin - - xSQLServerHelper and moved the existing tests for Restart-SqlService to it. - - xSQLServerAlwaysOnService -- Fixes in xSQLAOGroupJoin - - Availability Group name now appears in the error message for a failed Availability Group join attempt. - - Get-TargetResource now works with Get-DscConfiguration -- Fixes in xSQLServerRole - - Updated Ensure parameter to "Present" default value - - Renamed helper functions *-SqlServerRole to *-SqlServerRoleMember -- Changes to xSQLAlias - - Add UseDynamicTcpPort parameter for option "Dynamically determine port" - - Change Get-WmiObject to Get-CimInstance in Resource and associated pester file -- Added CHANGELOG.md file -- Added issue template file (ISSUE_TEMPLATE.md) for "New Issue" and pull request template file (PULL_REQUEST_TEMPLATE.md) for "New Pull Request" -- Add Contributing.md file -- Changes to xSQLServerSetup - - Now `Features` parameter is case-insensitive. -- BREAKING CHANGE: Removed xSQLServerPowerPlan from this module. The resource has been moved to [xComputerManagement](https://github.com/PowerShell/xComputerManagement) and is now called xPowerPlan. -- Changes and enhancements in xSQLServerDatabaseRole - - BREAKING CHANGE: Fixed so the same user can now be added to a role in one or more databases, and/or one or more instances. Now the parameters `SQLServer` and `SQLInstanceName` are mandatory. - - Enhanced so the same user can now be added to more than one role -- BREAKING CHANGE: Renamed xSQLAlias to xSQLServerAlias to align wíth naming convention. -- Changes to xSQLServerAlwaysOnService - - Added RestartTimeout parameter - - Fixed bug where the SQL Agent service did not get restarted after the IsHadrEnabled property was set. - - BREAKING CHANGE: The mandatory parameters now include Ensure, SQLServer, and SQLInstanceName. SQLServer and SQLInstanceName are keys which will be used to uniquely identify the resource which allows AlwaysOn to be enabled on multiple instances on the same machine. -- Moved Restart-SqlService from MSFT_xSQLServerConfiguration.psm1 to xSQLServerHelper.psm1. -' + ReleaseNotes = '- Improvements how tests are initiated in AppVeyor + - Removed previous workaround (issue ' } # End of PSData hashtable @@ -91,3 +57,4 @@ PrivateData = @{ + From eaf23cbb405060e91407a4bbe237069d64f20ab0 Mon Sep 17 00:00:00 2001 From: Katie Keim Date: Wed, 25 Jan 2017 16:07:56 -0800 Subject: [PATCH 31/31] Fix release notes in module manifest --- xSQLServer.psd1 | 105 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/xSQLServer.psd1 b/xSQLServer.psd1 index d1f53e837..0020e8a4e 100644 --- a/xSQLServer.psd1 +++ b/xSQLServer.psd1 @@ -48,7 +48,110 @@ PrivateData = @{ # ReleaseNotes of this module ReleaseNotes = '- Improvements how tests are initiated in AppVeyor - - Removed previous workaround (issue ' + - Removed previous workaround (issue 201) from unit tests. + - Changes in appveyor.yml so that SQL modules are removed before common test is run. + - Now the deploy step are no longer failing when merging code into Dev. Neither is the deploy step failing if a contributor had AppVeyor connected to the fork of xSQLServer and pushing code to the fork. +- Changes to README.md + - Changed the contributing section to help new contributors. + - Added links for each resource so it is easier to navigate to the parameter list for each resource. + - Moved the list of resources in alphabetical order. + - Moved each resource parameter list into alphabetical order. + - Removed old text mentioning System Center. + - Now the correct product name is written in the installation section, and a typo was also fixed. + - Fixed a typo in the Requirements section. + - Added link to Examples folder in the Examples section. + - Change the layout of the README.md to closer match the one of PSDscResources + - Added more detailed text explaining what operating systemes WMF5.0 can be installed on. + - Verified all resource schema files with the README.md and fixed some errors (descriptions was not verified). + - Added security requirements section for resource xSQLServerEndpoint and xSQLAOGroupEnsure. +- Changes to xSQLServerSetup + - The resource no longer uses Win32_Product WMI class when evaluating if SQL Server Management Studio is installed. See article [kb974524](https://support.microsoft.com/en-us/kb/974524) for more information. + - Now it uses CIM cmdlets to get information from WMI classes. + - Resolved all of the PSScriptAnalyzer warnings that was triggered in the common tests. + - Improvement for service accounts to enable support for Managed Service Accounts as well as other nt authority accounts + - Changes to the helper function Copy-ItemWithRoboCopy + - Robocopy is now started using Start-Process and the error handling has been improved. + - Robocopy now removes files at the destination path if they no longer exists at the source. + - Robocopy copies using unbuffered I/O when available (recommended for large files). + - Added a more descriptive text for the parameter `SourceCredential` to further explain how the parameter work. + - BREAKING CHANGE: Removed parameter SourceFolder. + - BREAKING CHANGE: Removed default value "$PSScriptRoot\..\..\" from parameter SourcePath. + - Old code, that no longer filled any function, has been replaced. + - Function `ResolvePath` has been replaced with `[Environment]::ExpandEnvironmentVariables($SourcePath)` so that environment variables still can be used in Source Path. + - Function `NetUse` has been replaced with `New-SmbMapping` and `Remove-SmbMapping`. + - Renamed function `GetSQLVersion` to `Get-SqlMajorVersion`. + - BREAKING CHANGE: Renamed parameter PID to ProductKey to avoid collision with automatic variable $PID +- Changes to xSQLServerScript + - All credential parameters now also has the type [System.Management.Automation.Credential()] to better work with PowerShell 4.0. + - It is now possible to configure two instances on the same node, with the same script. + - Added to the description text for the parameter `Credential` describing how to authenticate using Windows Authentication. + - Added examples to show how to authenticate using either SQL or Windows authentication. + - A recent issue showed that there is a known problem running this resource using PowerShell 4.0. For more information, see [issue #273](https://github.com/PowerShell/xSQLServer/issues/273) +- Changes to xSQLServerFirewall + - BREAKING CHANGE: Removed parameter SourceFolder. + - BREAKING CHANGE: Removed default value "$PSScriptRoot\..\..\" from parameter SourcePath. + - Old code, that no longer filled any function, has been replaced. + - Function `ResolvePath` has been replaced with `[Environment]::ExpandEnvironmentVariables($SourcePath)` so that environment variables still can be used in Source Path. + - Adding new optional parameter SourceCredential that can be used to authenticate against SourcePath. + - Solved PSSA rules errors in the code. + - Get-TargetResource no longer return $true when no products was installed. +- Changes to the unit test for resource + - xSQLServerSetup + - Added test coverage for helper function Copy-ItemWithRoboCopy +- Changes to xSQLServerLogin + - Removed ShouldProcess statements + - Added the ability to enforce password policies on SQL logins +- Added common test (xSQLServerCommon.Tests) for xSQLServer module + - Now all markdown files will be style checked when tests are running in AppVeyor after sending in a pull request. + - Now all [Examples](/Examples/Resources) will be tested by compiling to a .mof file after sending in a pull request. +- Changes to xSQLServerDatabaseOwner + - The example "SetDatabaseOwner" can now compile, it wrongly had a `DependsOn` in the example. +- Changes to SQLServerRole + - The examples "AddServerRole" and "RemoveServerRole" can now compile, it wrongly had a `DependsOn` in the example. +- Changes to CONTRIBUTING.md + - Added section "Tests for examples files" + - Added section "Tests for style check of Markdown files" + - Added section "Documentation with Markdown" + - Added texts to section "Tests" +- Changes to xSQLServerHelper + - added functions + - Get-SqlDatabaseRecoveryModel + - Set-SqlDatabaseRecoveryModel +- Examples + - xSQLServerDatabaseRecoveryModel + - 1-SetDatabaseRecoveryModel.ps1 + - xSQLServerDatabasePermission + - 1-GrantDatabasePermissions.ps1 + - 2-RevokeDatabasePermissions.ps1 + - 3-DenyDatabasePermissions.ps1 + - xSQLServerFirewall + - 1-CreateInboundFirewallRules + - 2-RemoveInboundFirewallRules +- Added tests for resources + - xSQLServerDatabaseRecoveryModel + - xSQLServerDatabasePermissions + - xSQLServerFirewall +- Changes to xSQLServerDatabaseRecoveryModel + - BREAKING CHANGE: Renamed xSQLDatabaseRecoveryModel to xSQLServerDatabaseRecoveryModel to align wíth naming convention. + - BREAKING CHANGE: The mandatory parameters now include SQLServer, and SQLInstanceName. +- Changes to xSQLServerDatabasePermission + - BREAKING CHANGE: Renamed xSQLServerDatabasePermissions to xSQLServerDatabasePermission to align wíth naming convention. + - BREAKING CHANGE: The mandatory parameters now include PermissionState, SQLServer, and SQLInstanceName. +- Added support for clustered installations to xSQLServerSetup + - Migrated relevant code from xSQLServerFailoverClusterSetup + - Removed Get-WmiObject usage + - Clustered storage mapping now supports asymmetric cluster storage + - Added support for multi-subnet clusters + - Added localized error messages for cluster object mapping + - Updated README.md to reflect new parameters +- Updated description for xSQLServerFailoverClusterSetup to indicate it is deprecated. +- xPDT helper module + - Function GetxPDTVariable was removed since it no longer was used by any resources. + - File xPDT.xml was removed since it was not used by any resources, and did not provide any value to the module. +- Changes xSQLServerHelper moduled + - Removed the globally defined `$VerbosePreference = Continue` from xSQLServerHelper. + - Fixed a typo in a variable name in the function New-ListenerADObject. + - Now Restart-SqlService will correctly show the services it restarts. Also fixed PSSA warnings.' } # End of PSData hashtable