From f5724c7c881318079ef937441a774a16759cb6a4 Mon Sep 17 00:00:00 2001 From: Fiander <51764122+Fiander@users.noreply.github.com> Date: Sun, 6 Dec 2020 09:29:13 +0100 Subject: [PATCH] SqlTraceFlag: New resource (#1640) - SqlServerDsc - Added new resource SqlTraceFlag to set or changes TraceFlags on SQL Server. This resource is based on @Zuldans code but with SqlServerDsc integrated SMO. Credits: https://github.com/Zuldan/cSQLServerTraceFlag --- CHANGELOG.md | 46 +- .../DSC_SqlTraceFlag/DSC_SqlTraceFlag.psm1 | 446 ++++++++++ .../DSC_SqlTraceFlag.schema.mof | 11 + .../DSCResources/DSC_SqlTraceFlag/README.md | 16 + .../en-US/DSC_SqlTraceFlag.strings.psd1 | 10 + .../SqlTraceFlag/1-SetTraceFlags.ps1 | 29 + .../2-SetTraceFlagsIncludeExclude.ps1 | 30 + .../SqlTraceFlag/3-RemoveTraceFlags.ps1 | 28 + source/SqlServerDsc.psd1 | 1 + tests/Unit/DSC_SqlTraceFlag.Tests.ps1 | 824 ++++++++++++++++++ tests/Unit/Stubs/SMO.cs | 14 + 11 files changed, 1435 insertions(+), 20 deletions(-) create mode 100644 source/DSCResources/DSC_SqlTraceFlag/DSC_SqlTraceFlag.psm1 create mode 100644 source/DSCResources/DSC_SqlTraceFlag/DSC_SqlTraceFlag.schema.mof create mode 100644 source/DSCResources/DSC_SqlTraceFlag/README.md create mode 100644 source/DSCResources/DSC_SqlTraceFlag/en-US/DSC_SqlTraceFlag.strings.psd1 create mode 100644 source/Examples/Resources/SqlTraceFlag/1-SetTraceFlags.ps1 create mode 100644 source/Examples/Resources/SqlTraceFlag/2-SetTraceFlagsIncludeExclude.ps1 create mode 100644 source/Examples/Resources/SqlTraceFlag/3-RemoveTraceFlags.ps1 create mode 100644 tests/Unit/DSC_SqlTraceFlag.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index a86094dc9..ab9918fdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,28 +5,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- WaitForAG - - BREAKING CHANGE: Fix for issue ([issue #1569](https://github.com/dsccommunity/SqlServerDsc/issues/1569)) - The resource now waits for the Availability Group to become Available. - - Two parameters where added to test get and set resource at instance level. -- SqlRole - - Major overhaul of resource. - - BREAKING CHANGE: Removed decision making from get-TargetResource; this - prevented a simple solution for issue #550. it now just tels if a role - exists or not. And what members are in that role. MembersToInclude and - MembersToExclude now always return $null. - - Added sanitize function (`Get-CorrectedMemberParameters`) to make it - so for the sysadmin role SA does not get altered ([issue #550](https://github.com/dsccommunity/SqlServerDsc/issues/550)). - - Added lots of tests. -- SqlSetup - - Added a note to the documentation that the parameter `BrowserSvcStartupType` - cannot be used for configurations that utilize the `'InstallFailoverCluster'` - action ([issue #1627](https://github.com/dsccommunity/SqlServerDsc/issues/1627)). - - Minor change to the evaluation of the parameter `BrowserSvcStartupType`, - if it has an assigned a value or not. - ### Added +- SqlServerDsc + - Added new resource SqlTraceFlag to set or changes TraceFlags on SQL Server. + This resource is based on @Zuldans code but with SqlServerDsc integrated SMO. + Credits: https://github.com/Zuldan/cSQLServerTraceFlag + - Added a lot of test scripts to validated the code. - SqlEndpoint - Added support for the Service Broker Endpoint ([issue #498](https://github.com/dsccommunity/SqlServerDsc/issues/498)). - SqlDatabaseRole @@ -46,8 +31,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SqlReplication - The resource are now using the helper function `Get-SqlInstanceMajorVersion` ([issue #1408](https://github.com/dsccommunity/SqlServerDsc/issues/1408)). +- SqlRole + - Major overhaul of resource. + - BREAKING CHANGE: Removed decision making from get-TargetResource; this + prevented a simple solution for issue #550. it now just tels if a role + exists or not. And what members are in that role. MembersToInclude and + MembersToExclude now always return $null. + - Added sanitize function (`Get-CorrectedMemberParameters`) to make it + so for the sysadmin role SA does not get altered ([issue #550](https://github.com/dsccommunity/SqlServerDsc/issues/550)). + - Added lots of tests. +- SqlWaitForAG + - BREAKING CHANGE: Fix for issue ([issue #1569](https://github.com/dsccommunity/SqlServerDsc/issues/1569)) + The resource now waits for the Availability Group to become Available. + - Two parameters where added to test get and set resource at instance level. +- SqlSetup + - Minor change to the evaluation of the parameter `BrowserSvcStartupType`, + if it has an assigned a value or not. ### Fixed + - SqlDatabaseRole - Fixed check to see if the role and user existed in the database. The previous logic would always indicate the role or user was not found unless @@ -57,6 +59,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SqlAlwaysOnService - Updated Get-TargetResource to return all defined schema properties ([issue #150](https://github.com/dsccommunity/SqlServerDsc/issues/1501)). +- SqlSetup + - Added a note to the documentation that the parameter `BrowserSvcStartupType` + cannot be used for configurations that utilize the `'InstallFailoverCluster'` + action ([issue #1627](https://github.com/dsccommunity/SqlServerDsc/issues/1627)). ## [14.2.1] - 2020-08-14 diff --git a/source/DSCResources/DSC_SqlTraceFlag/DSC_SqlTraceFlag.psm1 b/source/DSCResources/DSC_SqlTraceFlag/DSC_SqlTraceFlag.psm1 new file mode 100644 index 000000000..a766decb7 --- /dev/null +++ b/source/DSCResources/DSC_SqlTraceFlag/DSC_SqlTraceFlag.psm1 @@ -0,0 +1,446 @@ +$script:sqlServerDscHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\SqlServerDsc.Common' +$script:resourceHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common' + +Import-Module -Name $script:sqlServerDscHelperModulePath +Import-Module -Name $script:resourceHelperModulePath + +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' + +<# + .SYNOPSIS + This function gets the actual sql server TraceFlags. + + .PARAMETER ServerName + The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerName = $env:COMPUTERNAME, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName + ) + + Write-Verbose -Message ( + $script:localizedData.GetConfiguration -f $InstanceName + ) + + $sqlManagement = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer' -ArgumentList $ServerName + + $serviceNames = Get-SqlServiceName -InstanceName $InstanceName + + if ($sqlManagement) + { + $databaseEngineService = $sqlManagement.Services | + Where-Object -FilterScript { $PSItem.Name -eq $serviceNames.SQLEngineName } + + if ($databaseEngineService) + { + $traceFlags = $databaseEngineService.StartupParameters.Split(';') | + Where-Object -FilterScript { $PSItem -like '-T*' } | + ForEach-Object { + $PSItem.TrimStart('-T') + } + } + else + { + $errorMessage = $script:localizedData.NotConnectedToWMI -f $InstanceName, $ServerName + New-InvalidOperationException -Message $errorMessage + } + } + else + { + $errorMessage = $script:localizedData.NotConnectedToComputerManagement -f $ServerName + New-InvalidOperationException -Message $errorMessage + } + + return @{ + ServerName = $ServerName + InstanceName = $InstanceName + TraceFlags = $traceFlags + TraceFlagsToInclude = $null + TraceFlagsToExclude = $null + RestartService = $null + RestartTimeout = $null + } +} + +<# + .SYNOPSIS + This function sets the sql server TraceFlags. + + .PARAMETER ServerName + The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER TraceFlags + The TraceFlags the SQL server engine startup parameters should contain. + This parameter can not be used together with TraceFlagsToInclude and TraceFlagsToExclude. + This parameter will replace all the current trace flags with the specified trace flags. + + .PARAMETER TraceFlagsToInclude + The TraceFlags the SQL server engine startup parameters should include. + This parameter can not be used together with TraceFlags. + + .PARAMETER TraceFlagsToExclude + The TraceFlags the SQL server engine startup parameters should exclude. + This parameter can not be used together with TraceFlags. + + .PARAMETER RestartService + If set, the sql server instance gets a reset after setting parameters. + after restart the sql server agent is in the original state as before restart. + + .PARAMETER RestartTimeout + The time the resource waits while the sql server services are restarted. + Defaults to 120 seconds. +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerName = $env:COMPUTERNAME, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $InstanceName, + + [Parameter()] + [System.UInt32[]] + $TraceFlags, + + [Parameter()] + [System.UInt32[]] + $TraceFlagsToInclude, + + [Parameter()] + [System.UInt32[]] + $TraceFlagsToExclude, + + [Parameter()] + [System.Boolean] + $RestartService = $false, + + [Parameter()] + [System.UInt32] + $RestartTimeout = 120 + ) + + Write-Verbose -Message ( + $script:localizedData.SetConfiguration -f $InstanceName + ) + + $assertBoundParameterParameters = @{ + BoundParameterList = $PSBoundParameters + MutuallyExclusiveList1 = @( + 'TraceFlags' + ) + MutuallyExclusiveList2 = @( + 'TraceFlagsToInclude', 'TraceFlagsToExclude' + ) + } + + Assert-BoundParameter @assertBoundParameterParameters + + $getTargetResourceParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + } + + $wishTraceFlags = [System.Collections.ArrayList]::new() + + if ($PSBoundParameters.ContainsKey('TraceFlags')) + { + $wishTraceFlags.AddRange($TraceFlags) + } + else + { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $wishTraceFlags.AddRange($getTargetResourceResult.TraceFlags) + + if ($PSBoundParameters.ContainsKey('TraceFlagsToInclude')) + { + foreach ($traceFlagToInclude in $TraceFlagsToInclude) + { + if ($getTargetResourceResult.TraceFlags -notcontains $traceFlagToInclude) + { + $wishTraceFlags.Add($traceFlagToInclude) + } + } + } + + if ($PSBoundParameters.ContainsKey('TraceFlagsToExclude')) + { + foreach ($traceFlagToExclude in $TraceFlagsToExclude) + { + if ($getTargetResourceResult.TraceFlags -contains $traceFlagToExclude) + { + $wishTraceFlags.Remove([string]$traceFlagToExclude) + } + } + } + } + + # Add '-T' dash to flag. + $traceFlagList = $wishTraceFlags | + ForEach-Object { + "-T$PSItem" + } + + if ($traceFlagList -eq '') + { + $traceFlagList = $null + } + + $serviceNames = Get-SqlServiceName -InstanceName $InstanceName + + $sqlManagement = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer' -ArgumentList $ServerName + + if ($sqlManagement) + { + $databaseEngineService = $sqlManagement.Services | + Where-Object -FilterScript { $PSItem.Name -eq $serviceNames.SQLEngineName } + + if ($databaseEngineService) + { + # Extract startup parameters. + [System.Collections.ArrayList] $parameterList = $databaseEngineService.StartupParameters.Split(';') + + # Removing flags that are not wanted + foreach ($parameter in $databaseEngineService.StartupParameters.Split(';')) + { + if ($parameter -like '-T*' -and $parameter -notin $traceFlagList) + { + $parameterList.Remove($parameter) | Out-Null + } + } + + # Add missing flags. + foreach ($flag in $traceFlagList) + { + if ($flag -notin $parameterList) + { + $parameterList.Add($flag) | Out-Null + } + } + + # Merge flags back into startup parameters. + $databaseEngineService.StartupParameters = $parameterList -join ';' + $databaseEngineService.Alter() + + if ($PSBoundParameters.ContainsKey('RestartService')) + { + if ($RestartService -eq $true) + { + Restart-SqlService -ServerName $ServerName -InstanceName $InstanceName -Timeout $RestartTimeout + } + } + } + } +} + +<# + .SYNOPSIS + This function tests the sql server TraceFlags. + + .PARAMETER ServerName + The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER TraceFlags + The TraceFlags the SQL server engine startup parameters should contain. + This parameter can not be used together with TraceFlagsToInclude and TraceFlagsToExclude. + This parameter will replace all the current trace flags with the specified trace flags. + + .PARAMETER TraceFlagsToInclude + The TraceFlags the SQL server engine startup parameters should include. + This parameter can not be used together with TraceFlags. + + .PARAMETER TraceFlagsToExclude + The TraceFlags the SQL server engine startup parameters should exclude. + This parameter can not be used together with TraceFlags. + + .PARAMETER RestartService + If set, the sql server instance gets a reset after setting parameters. + after restart the sql server agent is in the original state as before restart. + + .PARAMETER RestartTimeout + The time the resource waits while the sql server services are restarted. + Defaults to 120 seconds. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerName = $env:COMPUTERNAME, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $InstanceName, + + [Parameter()] + [System.Uint32[]] + $TraceFlags, + + [Parameter()] + [System.Uint32[]] + $TraceFlagsToInclude, + + [Parameter()] + [System.Uint32[]] + $TraceFlagsToExclude, + + [Parameter()] + [System.Boolean] + $RestartService = $false, + + [Parameter()] + [System.UInt32] + $RestartTimeout = 120 + ) + + Write-Verbose -Message ( + $script:localizedData.TestConfiguration -f $InstanceName + ) + + $assertBoundParameterParameters = @{ + BoundParameterList = $PSBoundParameters + MutuallyExclusiveList1 = @( + 'TraceFlags' + ) + MutuallyExclusiveList2 = @( + 'TraceFlagsToInclude', 'TraceFlagsToExclude' + ) + } + + Assert-BoundParameter @assertBoundParameterParameters + + $getTargetResourceParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + } + + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $isInDesiredState = $true + + if ($PSBoundParameters.ContainsKey('TraceFlags')) + { + if ($TraceFlags.Length -eq 0) + { + if ($getTargetResourceResult.TraceFlags.Count -gt 0) + { + $isInDesiredState = $false + } + } + else + { + # Compare $TraceFlags to the Actual TraceFlags ($getTargetResourceResult.TraceFlags) to see if they contain the same values. + $nullIfTheSame = Compare-Object -ReferenceObject $getTargetResourceResult.TraceFlags -DifferenceObject $TraceFlags + if ($null -ne $nullIfTheSame) + { + Write-Verbose -Message ( + $script:localizedData.DesiredTraceFlagNotPresent ` + -f $($TraceFlags -join ','), $($getTargetResourceResult.TraceFlags -join ',') + ) + + $isInDesiredState = $false + } + } + } + else + { + if ($PSBoundParameters.ContainsKey('TraceFlagsToInclude')) + { + foreach ($traceFlagToInclude in $TraceFlagsToInclude) + { + if ($getTargetResourceResult.TraceFlags -notcontains $traceFlagToInclude) + { + Write-Verbose -Message ( + $script:localizedData.TraceFlagNotPresent ` + -f $traceFlagToInclude + ) + + $isInDesiredState = $false + } + } + } + + if ($PSBoundParameters.ContainsKey('TraceFlagsToExclude')) + { + foreach ($traceFlagToExclude in $TraceFlagsToExclude) + { + if ($getTargetResourceResult.TraceFlags -contains $traceFlagToExclude) + { + Write-Verbose -Message ( + $script:localizedData.TraceFlagPresent ` + -f $traceFlagToExclude + ) + + $isInDesiredState = $false + } + } + } + } + + return $isInDesiredState +} + +<# + .SYNOPSIS + This function returns the serviceNames of an sql instance. + + .PARAMETER InstanceName + The name of the SQL instance of whoose service names are beeing returned. +#> +function Get-SqlServiceName +{ + param + ( + [Parameter()] + [System.String] + $InstanceName = 'MSSQLServer' + ) + + if ($InstanceName -eq 'MSSQLSERVER') + { + $sqlEngineName = 'MSSQLSERVER' + $sqlAgentName = 'SQLSERVERAGENT' + } + else + { + $sqlEngineName = 'MSSQL${0}' -f $InstanceName + $sqlAgentName = 'SQLAgent${0}' -f $InstanceName + } + + return @{ + SQLEngineName = $sqlEngineName + SQLAgentName = $sqlAgentName + } +} diff --git a/source/DSCResources/DSC_SqlTraceFlag/DSC_SqlTraceFlag.schema.mof b/source/DSCResources/DSC_SqlTraceFlag/DSC_SqlTraceFlag.schema.mof new file mode 100644 index 000000000..f8d1bc58b --- /dev/null +++ b/source/DSCResources/DSC_SqlTraceFlag/DSC_SqlTraceFlag.schema.mof @@ -0,0 +1,11 @@ +[ClassVersion("1.0.0.0"), FriendlyName("SqlTraceFlag")] +class DSC_SqlTraceFlag : OMI_BaseResource +{ + [Write, Description("The host name of the _SQL Server_ to be configured. Default value is `$env:COMPUTERNAME`.")] String ServerName; + [Key, Description("The name of the _SQL Server_ instance to be configured.")] String InstanceName; + [Write, Description("An array of trace flags that startup options should have. This parameter will replace all the current trace flags with the specified trace flags.")] Uint32 TraceFlags[]; + [Write, Description("An array of trace flags to be added to the existing trace flags.")] Uint32 TraceFlagsToInclude[]; + [Write, Description("An array of trace flags to be removed from the existing trace flags.")] Uint32 TraceFlagsToExclude[]; + [Write, Description("Forces a restart of the Database Engine service and dependent services after the desired state is set. Default values is $false.")] Boolean RestartService; + [Write, Description("The time the resource waits while the sql server services are restarted. Defaults to 120 seconds")] Uint32 RestartTimeout; +}; diff --git a/source/DSCResources/DSC_SqlTraceFlag/README.md b/source/DSCResources/DSC_SqlTraceFlag/README.md new file mode 100644 index 000000000..e60bbff0e --- /dev/null +++ b/source/DSCResources/DSC_SqlTraceFlag/README.md @@ -0,0 +1,16 @@ +# Description + +The `SqlTraceFlag` DSC resource will remove or set one or more trace flags on a sql server engine. + +## Requirements + +* Target machine must be running Windows Server 2012 or later. +* Target machine must be running SQL Server Database Engine 2012 or later. + +## Security Requirements + +* The account running this resource must have admin access to the Windows Server. + +## Known issues + +All issues are not listed here, see [here for all open issues](https://github.com/dsccommunity/SqlServerDsc/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+SqlTraceFlag). diff --git a/source/DSCResources/DSC_SqlTraceFlag/en-US/DSC_SqlTraceFlag.strings.psd1 b/source/DSCResources/DSC_SqlTraceFlag/en-US/DSC_SqlTraceFlag.strings.psd1 new file mode 100644 index 000000000..d96c193ea --- /dev/null +++ b/source/DSCResources/DSC_SqlTraceFlag/en-US/DSC_SqlTraceFlag.strings.psd1 @@ -0,0 +1,10 @@ +ConvertFrom-StringData @' + GetConfiguration = Get the current TraceFlags that are set on instance {0}. + SetConfiguration = Set the TraceFlags that are needed on instance {0}. + TestConfiguration = Determines the current state of the TraceFlags Compared to the desired TraceFlags '{0}'. + NotConnectedToComputerManagement = Was unable to connect to ComputerManagement '{0}'. + NotConnectedToWMI = Was unable to connect to WMI information '{0}' in '{1}'. + DesiredTraceFlagNotPresent = TraceFlag does not match the actual TraceFlags on the instance. Expected '{0}', but was '{1}'. + TraceFlagPresent = traceflag {0} is present. + TraceFlagNotPresent = traceflag {0} is not present. +'@ diff --git a/source/Examples/Resources/SqlTraceFlag/1-SetTraceFlags.ps1 b/source/Examples/Resources/SqlTraceFlag/1-SetTraceFlags.ps1 new file mode 100644 index 000000000..1bfa1f336 --- /dev/null +++ b/source/Examples/Resources/SqlTraceFlag/1-SetTraceFlags.ps1 @@ -0,0 +1,29 @@ +<# + .DESCRIPTION + This example shows how to set TraceFlags where all existing + TraceFlags are overwriten by these +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost + { + SqlTraceFlag 'Set_SqlTraceFlags' + { + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + TraceFlags = 834, 1117, 1118, 2371, 3226 + RestartService = $true + + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/source/Examples/Resources/SqlTraceFlag/2-SetTraceFlagsIncludeExclude.ps1 b/source/Examples/Resources/SqlTraceFlag/2-SetTraceFlagsIncludeExclude.ps1 new file mode 100644 index 000000000..9e9a3ab6f --- /dev/null +++ b/source/Examples/Resources/SqlTraceFlag/2-SetTraceFlagsIncludeExclude.ps1 @@ -0,0 +1,30 @@ +<# + .DESCRIPTION + This example shows how to set TraceFlags while keeping all existing + traceflags. Also one existing traceflag is removed. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost + { + SqlTraceFlag 'Set_SqlTraceFlagsIncludeExclude' + { + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + TraceFlagsToInclude = 834, 1117, 1118, 2371, 3226 + TraceFlagsToExclude = 1112 + RestartService = $true + + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/source/Examples/Resources/SqlTraceFlag/3-RemoveTraceFlags.ps1 b/source/Examples/Resources/SqlTraceFlag/3-RemoveTraceFlags.ps1 new file mode 100644 index 000000000..41fb01f9d --- /dev/null +++ b/source/Examples/Resources/SqlTraceFlag/3-RemoveTraceFlags.ps1 @@ -0,0 +1,28 @@ +<# + .DESCRIPTION + This example shows how to clear all TraceFlags. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost + { + SqlTraceFlag 'Remove_SqlTraceFlags' + { + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + RestartService = $true + TraceFlags = @() + + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/source/SqlServerDsc.psd1 b/source/SqlServerDsc.psd1 index 5efb99c50..73234f008 100644 --- a/source/SqlServerDsc.psd1 +++ b/source/SqlServerDsc.psd1 @@ -74,6 +74,7 @@ 'SqlSecureConnection' 'SqlServiceAccount' 'SqlSetup' + 'SqlTraceFlag' 'SqlWaitForAG' 'SqlWindowsFirewall' ) diff --git a/tests/Unit/DSC_SqlTraceFlag.Tests.ps1 b/tests/Unit/DSC_SqlTraceFlag.Tests.ps1 new file mode 100644 index 000000000..7d5375c9f --- /dev/null +++ b/tests/Unit/DSC_SqlTraceFlag.Tests.ps1 @@ -0,0 +1,824 @@ +<# + .SYNOPSIS + Automated unit test for DSC_SqlTraceFlag DSC resource. + +#> + +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + +if (-not (Test-BuildCategory -Type 'Unit')) +{ + return +} + +$script:dscModuleName = 'SqlServerDsc' +$script:dscResourceName = 'DSC_SqlTraceFlag' + +function Invoke-TestSetup +{ + try + { + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' + } + + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Unit' +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} + +Invoke-TestSetup + +try +{ + InModuleScope $script:dscResourceName { + Describe "DSC_SqlTraceFlag\Get-TargetResource" -Tag 'Get' { + BeforeAll { + $mockServerName = 'TestServer' + $mockFakeServerName = 'FakeServer' + $mockInstanceName1 = 'MSSQLSERVER' + $mockInstanceName1Agent = 'SQLSERVERAGENT' + $mockInstanceName2 = 'INST00' + $mockInstanceName2Agent = 'SQLAgent$INST00' + $mockInstanceName3 = 'INST01' + $mockInstanceName3Agent = 'SQLAgent$INST01' + + $mockInvalidOperationForAlterMethod = $false + + $mockServerInstances = [System.Collections.ArrayList]::new() + $mockServerInstances.Add($mockInstanceName1) | Out-Null + $mockServerInstances.Add($mockInstanceName2) | Out-Null + + # The Trailing spaces in these here strings are ment to be there. Do not remove! + $mockStartupParametersInstance1 = @" +-dC:\Program Files\Microsoft SQL Server\MSSQL15.INST00\MSSQL\DATA\master.mdf;-eC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\Log\ERRORLOG;-lC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\DATA\mastlog.ldf;-T3226;-T1802 +"@ + $mockStartupParametersInstance2 = @" +-dC:\Program Files\Microsoft SQL Server\MSSQL15.INST00\MSSQL\DATA\master.mdf;-eC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\Log\ERRORLOG;-lC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\DATA\mastlog.ldf +"@ + + # Default parameters that are used for the It-blocks + $mockDefaultParameters1 = @{ + InstanceName = $mockInstanceName1 + ServerName = $mockServerName + } + + $mockInst00Parameters = @{ + InstanceName = $mockInstanceName2 + ServerName = $mockServerName + } + + $mockInst01Parameters = @{ + InstanceName = $mockInstanceName3 + ServerName = $mockServerName + } + + $mockNonExistServerParameters = @{ + InstanceName = $mockInstanceName1 + ServerName = $mockFakeServerName + } + + $mockNewObject_ParameterFilter_RealServerName = { + $ServerName -eq $mockServerName + } + + $mockNewObject_ParameterFilter_FakeServerName = { + $ServerName -eq $mockFakeServerName + } + + $script:mockMethodAlterRan = $false + $script:mockMethodAlterValue = '' + + #region Function mocks + $mockSmoWmiManagedComputer = { + $mockServerObjectHashtable = @{ + State = "Existing" + Name = $mockServerName + ServerInstances = $mockServerInstances + } + + class service + { + [string]$Name + [string]$ServiceState + [string]$StartupParameters + } + + $Services = [System.Collections.ArrayList]::new() + + $service1 = [service]::new() + $service1.Name = $mockInstanceName1 + $service1.ServiceState = "Running" + $service1.StartupParameters = $mockStartupParametersInstance1 + + $Services.Add($service1) | Out-Null + + $service2 = [service]::new() + $service2.Name = $mockInstanceName1Agent + $service2.ServiceState = "Running" + $service2.StartupParameters = "" + + $Services.Add($service2) | Out-Null + + $service3 = [service]::new() + $service3.Name = 'MSSQL${0}' -f $mockInstanceName2 + $service3.ServiceState = "Running" + $service3.StartupParameters = $mockStartupParametersInstance2 + + $Services.Add($service3) | Out-Null + + $service4 = [service]::new() + $service4.Name = 'SQLAgent${0}' -f $mockInstanceName2Agent + $service4.ServiceState = "Stopped" + $service4.StartupParameters = "" + + $Services.Add($service4) | Out-Null + + $ServerServices = [System.Collections.ArrayList]::new() + + foreach ($mockService in $Services) + { + $mockService | Add-Member -MemberType ScriptMethod -Name Alter -Value { + $script:mockMethodAlterRan = $true + $script:mockMethodAlterValue = $this.StartupParameters + } -PassThru + $ServerServices.Add( $mockService) | Out-Null + } + + $mockServerObjectHashtable += @{ + Services = $ServerServices + } + $mockServerObject = [PSCustomObject]$mockServerObjectHashtable + + return @($mockServerObject) + } + Mock -CommandName New-Object -MockWith $mockSmoWmiManagedComputer -ParameterFilter $mockNewObject_ParameterFilter_RealServerName -Verifiable + } + + Context 'For the default instance' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + } + It 'Should return a ManagedComputer object.' { + $result = Get-TargetResource @testParameters + + $result.ServerName | Should -Be $mockServerName -Because 'ServerName must be correct' + $result.InstanceName | Should -Be $mockInstanceName1 -Because 'InstanceName must be correct' + $result.TraceFlags | Should -Be '3226' ,'1802' -Because 'TraceFlags must be correct' + $result.TraceFlags.Count | Should -Be 2 -Because 'number of TraceFlags must be correct' + } + It 'Should not throw' { + {Get-TargetResource @testParameters} | Should -Not -Throw + } + } + + Context 'For a named instance' { + BeforeAll { + $testParameters = $mockInst00Parameters + } + It 'Should return a ManagedComputer object.' { + $result = Get-TargetResource @testParameters + + $result.ServerName | Should -Be $mockServerName -Because 'ServerName must be correct' + $result.InstanceName | Should -Be $mockInstanceName2 -Because 'InstanceName must be correct' + $result.TraceFlags | Should -BeNullOrEmpty -Because 'TraceFlags must be correct' + $result.TraceFlags.Count | Should -Be 0 -Because 'number of TraceFlags must be correct' + } + + It 'Should not throw' { + {Get-TargetResource @testParameters} | Should -Not -Throw + } + } + + Context 'For a nonexist instance' { + BeforeAll { + $testParameters = $mockInst01Parameters + } + It 'Should throw for incorect parameters' { + {Get-TargetResource @testParameters} | + Should -Throw -ExpectedMessage ("Was unable to connect to WMI information '{0}' in '{1}'." -f $mockInstanceName3, $mockServerName) + } + } + + Context 'For a nonexist server' { + BeforeAll { + $testParameters = $mockNonExistServerParameters + Mock -CommandName New-Object -MockWith { + return $null + } -ParameterFilter $mockNewObject_ParameterFilter_FakeServerName -Verifiable + } + It 'Should throw for incorect parameters' { + {Get-TargetResource @testParameters} | + Should -Throw -ExpectedMessage ("Was unable to connect to ComputerManagement '{0}'." -f $mockFakeServerName) + } + } + } + + Describe "DSC_SqlTraceFlag\Test-TargetResource" -Tag 'Test' { + BeforeAll { + $mockServerName = 'TestServer' + $mockFakeServerName = 'FakeServer' + $mockInstanceName1 = 'MSSQLSERVER' + $mockInstanceName1Agent = 'SQLSERVERAGENT' + $mockInstanceName2 = 'INST00' + $mockInstanceName2Agent = 'SQLAgent$INST00' + $mockInstanceName3 = 'INST01' + $mockInstanceName3Agent = 'SQLAgent$INST01' + + $mockInvalidOperationForAlterMethod = $false + + $mockServerInstances = [System.Collections.ArrayList]::new() + $mockServerInstances.Add($mockInstanceName1) | Out-Null + $mockServerInstances.Add($mockInstanceName2) | Out-Null + + # The Trailing spaces in these here strings are ment to be there. Do not remove! + $mockStartupParametersInstance1 = @" +-dC:\Program Files\Microsoft SQL Server\MSSQL15.INST00\MSSQL\DATA\master.mdf;-eC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\Log\ERRORLOG;-lC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\DATA\mastlog.ldf;-T3226;-T1802 +"@ + $mockStartupParametersInstance2 = @" +-dC:\Program Files\Microsoft SQL Server\MSSQL15.INST00\MSSQL\DATA\master.mdf;-eC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\Log\ERRORLOG;-lC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\DATA\mastlog.ldf +"@ + + # Default parameters that are used for the It-blocks + $mockDefaultParameters1 = @{ + InstanceName = $mockInstanceName1 + ServerName = $mockServerName + } + + $mockInst00Parameters = @{ + InstanceName = $mockInstanceName2 + ServerName = $mockServerName + } + + $mockInst01Parameters = @{ + InstanceName = $mockInstanceName3 + ServerName = $mockServerName + } + + $mockNonExistServerParameters = @{ + InstanceName = $mockInstanceName1 + ServerName = $mockFakeServerName + } + + $mockNewObject_ParameterFilter_RealServerName = { + $ServerName -eq $mockServerName + } + + $mockNewObject_ParameterFilter_FakeServerName = { + $ServerName -eq $mockFakeServerName + } + + $script:mockMethodAlterRan = $false + $script:mockMethodAlterValue = '' + + #region Function mocks + $mockSmoWmiManagedComputer = { + $mockServerObjectHashtable = @{ + State = "Existing" + Name = $mockServerName + ServerInstances = $mockServerInstances + } + + class service + { + [string]$Name + [string]$ServiceState + [string]$StartupParameters + } + + $Services = [System.Collections.ArrayList]::new() + + $service1 = [service]::new() + $service1.Name = $mockInstanceName1 + $service1.ServiceState = "Running" + $service1.StartupParameters = $mockStartupParametersInstance1 + + $Services.Add($service1) | Out-Null + + $service2 = [service]::new() + $service2.Name = $mockInstanceName1Agent + $service2.ServiceState = "Running" + $service2.StartupParameters = "" + + $Services.Add($service2) | Out-Null + + $service3 = [service]::new() + $service3.Name = 'MSSQL${0}' -f $mockInstanceName2 + $service3.ServiceState = "Running" + $service3.StartupParameters = $mockStartupParametersInstance2 + + $Services.Add($service3) | Out-Null + + $service4 = [service]::new() + $service4.Name = 'SQLAgent${0}' -f $mockInstanceName2Agent + $service4.ServiceState = "Stopped" + $service4.StartupParameters = "" + + $Services.Add($service4) | Out-Null + + $ServerServices = [System.Collections.ArrayList]::new() + + foreach ($mockService in $Services) + { + $mockService | Add-Member -MemberType ScriptMethod -Name Alter -Value { + $script:mockMethodAlterRan = $true + $script:mockMethodAlterValue = $this.StartupParameters + } -PassThru + $ServerServices.Add( $mockService) | Out-Null + } + + $mockServerObjectHashtable += @{ + Services = $ServerServices + } + $mockServerObject = [PSCustomObject]$mockServerObjectHashtable + + return @($mockServerObject) + } + Mock -CommandName New-Object -MockWith $mockSmoWmiManagedComputer -Verifiable + } + + Context 'When the system is not in the desired state and TraceFlags is empty' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlags = @() + } + } + + It 'Should return false when Traceflags on the instance exist' { + $result = Test-TargetResource @testParameters + $result | Should -BeFalse + } + + It 'Should be executed once' { + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope Context + } + } + + Context 'When the system is in the desired state and TraceFlags is empty' { + BeforeAll { + $testParameters = $mockInst00Parameters + $testParameters += @{ + TraceFlags = @() + } + } + + It 'Should return true when no Traceflags on the instance exist' { + $result = Test-TargetResource @testParameters + $result | Should -BeTrue + } + + It 'Should be executed once' { + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope Context + } + } + + Context 'When the system is not in the desired state and ensure is set to Present and `$TraceFlags does not match the actual TraceFlags' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlags = '3228' + } + } + + It 'Should return false when Traceflags do not match the actual TraceFlags' { + $result = Test-TargetResource @testParameters + $result | Should -BeFalse + } + + It 'Should be executed once' { + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope Context + } + } + + Context 'When the system is not in the desired state and ensure is set to Present and `$TraceFlagsToInclude are not in the actual TraceFlags' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlagsToInclude = '3228' + } + } + + It 'Should return false when TraceflagsToInclude are not in the actual TraceFlags' { + $result = Test-TargetResource @testParameters + $result | Should -BeFalse + } + + It 'Should be executed once' { + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope Context + } + } + + Context 'When the system is in the desired state and ensure is set to Present and `$TraceFlagsToInclude are in the actual TraceFlags' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlagsToInclude = '3226' + } + } + + It 'Should return false when TraceflagsToInclude are in the actual TraceFlags' { + $result = Test-TargetResource @testParameters + $result | Should -BeTrue + } + + It 'Should be executed once' { + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope Context + } + } + + Context 'When the system is not in the desired state and ensure is set to Present and `$TraceFlagsToExclude are in the actual TraceFlags' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlagsToExclude = '3226' + } + } + + It 'Should return false when TraceflagsToExclude are in the actual TraceFlags' { + $result = Test-TargetResource @testParameters + $result | Should -BeFalse + } + + It 'Should be executed once' { + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope Context + } + } + + Context 'When the system is in the desired state and ensure is set to Present and `$TraceFlagsToExclude are not in the actual TraceFlags' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlagsToExclude = '3228' + } + } + + It 'Should return true when TraceflagsToExclude are not in the actual TraceFlags' { + $result = Test-TargetResource @testParameters + $result | Should -BeTrue + } + + It 'Should be executed once' { + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope Context + } + } + + + Context 'When both the parameters TraceFlags and TraceFlagsToInclude are assigned a value.' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlags = '3228' + TraceFlagsToInclude = '3228' + } + } + + It 'Should throw the correct error' { + { Test-TargetResource @testParameters } | Should -Throw '(DRC0010)' + } + + It 'Should not be executed' { + Assert-MockCalled -CommandName New-Object -Exactly -Times 0 -Scope Context + } + } + + Context 'When both the parameters TraceFlags and TraceFlagsToExclude are assigned a value.' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlags = '3228' + TraceFlagsToExclude = '3228' + } + } + + It 'Should throw the correct error' { + { Test-TargetResource @testParameters } | Should -Throw '(DRC0010)' + } + + It 'Should not be executed' { + Assert-MockCalled -CommandName New-Object -Exactly -Times 0 -Scope Context + } + } + + } + + Describe "DSC_SqlTraceFlag\Set-TargetResource" -Tag 'Set' { + BeforeAll { + $mockServerName = 'TestServer' + $mockFakeServerName = 'FakeServer' + $mockInstanceName1 = 'MSSQLSERVER' + $mockInstanceName1Agent = 'SQLSERVERAGENT' + $mockInstanceName2 = 'INST00' + $mockInstanceName2Agent = 'SQLAgent$INST00' + $mockInstanceName3 = 'INST01' + $mockInstanceName3Agent = 'SQLAgent$INST01' + + $mockInvalidOperationForAlterMethod = $false + + $mockServerInstances = [System.Collections.ArrayList]::new() + $mockServerInstances.Add($mockInstanceName1) | Out-Null + $mockServerInstances.Add($mockInstanceName2) | Out-Null + + # The Trailing spaces in these here strings are ment to be there. Do not remove! + $mockStartupParametersInstance1 = @" +-dC:\Program Files\Microsoft SQL Server\MSSQL15.INST00\MSSQL\DATA\master.mdf;-eC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\Log\ERRORLOG;-lC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\DATA\mastlog.ldf;-T3226;-T1802 +"@ + $mockStartupParametersInstance2 = @" +-dC:\Program Files\Microsoft SQL Server\MSSQL15.INST00\MSSQL\DATA\master.mdf;-eC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\Log\ERRORLOG;-lC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\DATA\mastlog.ldf +"@ + + # Default parameters that are used for the It-blocks + $mockDefaultParameters1 = @{ + InstanceName = $mockInstanceName1 + ServerName = $mockServerName + } + + $mockInst00Parameters = @{ + InstanceName = $mockInstanceName2 + ServerName = $mockServerName + } + + $mockInst01Parameters = @{ + InstanceName = $mockInstanceName3 + ServerName = $mockServerName + } + + $mockNonExistServerParameters = @{ + InstanceName = $mockInstanceName1 + ServerName = $mockFakeServerName + } + + $mockNewObject_ParameterFilter_RealServerName = { + $ServerName -eq $mockServerName + } + + $mockNewObject_ParameterFilter_FakeServerName = { + $ServerName -eq $mockFakeServerName + } + + $script:mockMethodAlterRan = $false + $script:mockMethodAlterValue = '' + + #region Function mocks + $mockSmoWmiManagedComputer = { + $mockServerObjectHashtable = @{ + State = "Existing" + Name = $mockServerName + ServerInstances = $mockServerInstances + } + + class service + { + [string]$Name + [string]$ServiceState + [string]$StartupParameters + } + + $Services = [System.Collections.ArrayList]::new() + + $service1 = [service]::new() + $service1.Name = $mockInstanceName1 + $service1.ServiceState = "Running" + $service1.StartupParameters = $mockStartupParametersInstance1 + + $Services.Add($service1) | Out-Null + + $service2 = [service]::new() + $service2.Name = $mockInstanceName1Agent + $service2.ServiceState = "Running" + $service2.StartupParameters = "" + + $Services.Add($service2) | Out-Null + + $service3 = [service]::new() + $service3.Name = 'MSSQL${0}' -f $mockInstanceName2 + $service3.ServiceState = "Running" + $service3.StartupParameters = $mockStartupParametersInstance2 + + $Services.Add($service3) | Out-Null + + $service4 = [service]::new() + $service4.Name = 'SQLAgent${0}' -f $mockInstanceName2Agent + $service4.ServiceState = "Stopped" + $service4.StartupParameters = "" + + $Services.Add($service4) | Out-Null + + $ServerServices = [System.Collections.ArrayList]::new() + + foreach ($mockService in $Services) + { + $mockService | Add-Member -MemberType ScriptMethod -Name Alter -Value { + $script:mockMethodAlterRan = $true + $script:mockMethodAlterValue = $this.StartupParameters + } -PassThru + $ServerServices.Add( $mockService) | Out-Null + } + + $mockServerObjectHashtable += @{ + Services = $ServerServices + } + $mockServerObject = [PSCustomObject]$mockServerObjectHashtable + + return @($mockServerObject) + } + Mock -CommandName New-Object -MockWith $mockSmoWmiManagedComputer -Verifiable + Mock -CommandName Restart-SqlService -ModuleName $script:dscResourceName -Verifiable + } + + Context 'When the system is not in the desired state and ensure is set to Absent' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlags = @() + } + } + + It 'Should not throw when calling the alter method' { + { Set-TargetResource @testParameters } | Should -Not -Throw + $script:mockMethodAlterRan | Should -BeTrue -Because 'Alter should run' + $script:mockMethodAlterValue | Should -Be @" +-dC:\Program Files\Microsoft SQL Server\MSSQL15.INST00\MSSQL\DATA\master.mdf;-eC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\Log\ERRORLOG;-lC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\DATA\mastlog.ldf +"@ -Because 'Alter must change the value correct' + + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It + } + + } + + Context 'When the system is not in the desired state and ensure is set to Present and `$TraceFlags does not match the actual TraceFlags' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlags = '3228' + } + } + + It 'Should not throw when calling the alter method' { + { Set-TargetResource @testParameters } | Should -Not -Throw + $script:mockMethodAlterRan | Should -BeTrue -Because 'Alter should run' + $script:mockMethodAlterValue | Should -Be @" +-dC:\Program Files\Microsoft SQL Server\MSSQL15.INST00\MSSQL\DATA\master.mdf;-eC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\Log\ERRORLOG;-lC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\DATA\mastlog.ldf;-T3228 +"@ -Because 'Alter must change the value correct' + + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state and ensure is set to Present and `$TraceFlagsToInclude is not in TraceFlags' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlagsToInclude = '3228' + } + } + + It 'Should not throw when calling the alter method' { + { Set-TargetResource @testParameters } | Should -Not -Throw + $script:mockMethodAlterRan | Should -BeTrue + $script:mockMethodAlterValue | Should -Be @" +-dC:\Program Files\Microsoft SQL Server\MSSQL15.INST00\MSSQL\DATA\master.mdf;-eC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\Log\ERRORLOG;-lC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\DATA\mastlog.ldf;-T3226;-T1802;-T3228 +"@ + + Assert-MockCalled -CommandName New-Object -Exactly -Times 2 -Scope It + } + } + + Context 'When the system is not in the desired state and ensure is set to Present and `$TraceFlagsToExclude is in TraceFlags' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlagsToExclude = '1802' + } + } + + It 'Should not throw when calling the alter method' { + { Set-TargetResource @testParameters } | Should -Not -Throw + $script:mockMethodAlterRan | Should -BeTrue + $script:mockMethodAlterValue | Should -Be @" +-dC:\Program Files\Microsoft SQL Server\MSSQL15.INST00\MSSQL\DATA\master.mdf;-eC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\Log\ERRORLOG;-lC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\DATA\mastlog.ldf;-T3226 +"@ + + Assert-MockCalled -CommandName New-Object -Exactly -Times 2 -Scope It + } + } + + Context 'When the system is not in the desired state and ensure is set to Present and `$TraceFlags does not match the actual TraceFlags' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlags = '3228' + RestartService = $true + } + } + + It 'Should not throw when calling the restart method' { + { Set-TargetResource @testParameters } | Should -Not -Throw + $script:mockMethodAlterRan | Should -BeTrue + $script:mockMethodAlterValue | Should -Be @" +-dC:\Program Files\Microsoft SQL Server\MSSQL15.INST00\MSSQL\DATA\master.mdf;-eC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\Log\ERRORLOG;-lC:\Program Files\Microsoft SQL +Server\MSSQL15.INST00\MSSQL\DATA\mastlog.ldf;-T3228 +"@ + + Assert-MockCalled -CommandName Restart-SqlService -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It + } + } + + Context 'When both the parameters TraceFlags and TraceFlagsToInclude are assigned a value.' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlags = '3228' + TraceFlagsToInclude = '3228' + } + } + + It 'Should throw the correct error' { + { Set-TargetResource @testParameters} | Should -Throw '(DRC0010)' + } + + It 'Should not be executed' { + Assert-MockCalled -CommandName New-Object -Exactly -Times 0 -Scope Context + } + } + + Context 'When both the parameters TraceFlags and TraceFlagsToExclude are assigned a value.' { + BeforeAll { + $testParameters = $mockDefaultParameters1 + $testParameters += @{ + TraceFlags = '3228' + TraceFlagsToExclude = '3228' + } + } + + It 'Should throw the correct error' { + { Set-TargetResource @testParameters } | Should -Throw '(DRC0010)' + } + + It 'Should not be executed' { + Assert-MockCalled -CommandName New-Object -Exactly -Times 0 -Scope Context + } + } + + Context 'For a nonexist instance' { + BeforeAll { + $testParameters = $mockInst01Parameters + } + It 'Should throw for incorect parameters' { + {Set-TargetResource @testParameters} | + Should -Throw -ExpectedMessage ("Was unable to connect to WMI information '{0}' in '{1}'." -f $mockInstanceName3, $mockServerName) + } + } + + Context 'For a nonexist server' { + BeforeAll { + $testParameters = $mockNonExistServerParameters + Mock -CommandName New-Object -MockWith { + return $null + } -ParameterFilter $mockNewObject_ParameterFilter_FakeServerName -Verifiable + } + It 'Should throw for incorect parameters' { + {Set-TargetResource @testParameters} | + Should -Throw -ExpectedMessage ("Was unable to connect to ComputerManagement '{0}'." -f $mockFakeServerName) + } + } + } + + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index d6149f15d..de84deb51 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -969,4 +969,18 @@ public enum ManagedServiceType } #endregion + + #region Public Classes + + // TypeName: Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer + // Used by: + // DSC_SqlTraceFlag.Tests.ps1 + public class ManagedComputer + { + public string Name; + public string State; + public ArrayList ServerInstances; + public ArrayList Services; + } + #endregion }